/src/mozilla-central/layout/base/nsLayoutUtils.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsLayoutUtils.h" |
8 | | |
9 | | #include "mozilla/ArrayUtils.h" |
10 | | #include "mozilla/BasicEvents.h" |
11 | | #include "mozilla/ClearOnShutdown.h" |
12 | | #include "mozilla/EffectCompositor.h" |
13 | | #include "mozilla/EffectSet.h" |
14 | | #include "mozilla/EventDispatcher.h" |
15 | | #include "mozilla/FloatingPoint.h" |
16 | | #include "mozilla/gfx/gfxVars.h" |
17 | | #include "mozilla/gfx/PathHelpers.h" |
18 | | #include "mozilla/layers/PAPZ.h" |
19 | | #include "mozilla/Likely.h" |
20 | | #include "mozilla/Maybe.h" |
21 | | #include "mozilla/MemoryReporting.h" |
22 | | #include "mozilla/dom/ContentChild.h" |
23 | | #include "mozilla/ServoStyleSetInlines.h" |
24 | | #include "mozilla/StaticPrefs.h" |
25 | | #include "mozilla/Unused.h" |
26 | | #include "nsCharTraits.h" |
27 | | #include "nsDocument.h" |
28 | | #include "nsFontMetrics.h" |
29 | | #include "nsPresContext.h" |
30 | | #include "nsIContent.h" |
31 | | #include "nsFrameList.h" |
32 | | #include "nsGenericHTMLElement.h" |
33 | | #include "nsGkAtoms.h" |
34 | | #include "nsAtom.h" |
35 | | #include "nsCaret.h" |
36 | | #include "nsCSSPseudoElements.h" |
37 | | #include "nsCSSAnonBoxes.h" |
38 | | #include "nsCSSColorUtils.h" |
39 | | #include "nsView.h" |
40 | | #include "nsViewManager.h" |
41 | | #include "nsPlaceholderFrame.h" |
42 | | #include "nsIScrollableFrame.h" |
43 | | #include "nsSubDocumentFrame.h" |
44 | | #include "nsDisplayList.h" |
45 | | #include "nsRegion.h" |
46 | | #include "nsCSSFrameConstructor.h" |
47 | | #include "nsBlockFrame.h" |
48 | | #include "nsBidiPresUtils.h" |
49 | | #include "imgIContainer.h" |
50 | | #include "ImageOps.h" |
51 | | #include "ImageRegion.h" |
52 | | #include "gfxRect.h" |
53 | | #include "gfxContext.h" |
54 | | #include "gfxContext.h" |
55 | | #include "nsIInterfaceRequestorUtils.h" |
56 | | #include "nsCSSRendering.h" |
57 | | #include "nsTextFragment.h" |
58 | | #include "nsStyleConsts.h" |
59 | | #include "nsPIDOMWindow.h" |
60 | | #include "nsIDocShell.h" |
61 | | #include "nsIWidget.h" |
62 | | #include "gfxMatrix.h" |
63 | | #include "gfxPrefs.h" |
64 | | #include "gfxTypes.h" |
65 | | #include "nsTArray.h" |
66 | | #include "mozilla/dom/HTMLCanvasElement.h" |
67 | | #include "nsICanvasRenderingContextInternal.h" |
68 | | #include "gfxPlatform.h" |
69 | | #include <algorithm> |
70 | | #include <limits> |
71 | | #include "mozilla/dom/AnonymousContent.h" |
72 | | #include "mozilla/dom/HTMLBodyElement.h" |
73 | | #include "mozilla/dom/HTMLMediaElementBinding.h" |
74 | | #include "mozilla/dom/HTMLVideoElement.h" |
75 | | #include "mozilla/dom/HTMLImageElement.h" |
76 | | #include "mozilla/dom/DOMRect.h" |
77 | | #include "mozilla/dom/DOMStringList.h" |
78 | | #include "mozilla/dom/KeyframeEffect.h" |
79 | | #include "mozilla/dom/SVGPathData.h" |
80 | | #include "mozilla/layers/APZCCallbackHelper.h" |
81 | | #include "imgIRequest.h" |
82 | | #include "nsIImageLoadingContent.h" |
83 | | #include "nsCOMPtr.h" |
84 | | #include "nsCSSProps.h" |
85 | | #include "nsListControlFrame.h" |
86 | | #include "mozilla/dom/Element.h" |
87 | | #include "nsCanvasFrame.h" |
88 | | #include "gfxDrawable.h" |
89 | | #include "gfxEnv.h" |
90 | | #include "gfxUtils.h" |
91 | | #include "nsDataHashtable.h" |
92 | | #include "nsTableWrapperFrame.h" |
93 | | #include "nsTextFrame.h" |
94 | | #include "nsFontInflationData.h" |
95 | | #include "nsSVGIntegrationUtils.h" |
96 | | #include "nsSVGUtils.h" |
97 | | #include "SVGImageContext.h" |
98 | | #include "SVGTextFrame.h" |
99 | | #include "nsStyleStructInlines.h" |
100 | | #include "nsStyleTransformMatrix.h" |
101 | | #include "nsIFrameInlines.h" |
102 | | #include "ImageContainer.h" |
103 | | #include "nsComputedDOMStyle.h" |
104 | | #include "ActiveLayerTracker.h" |
105 | | #include "mozilla/gfx/2D.h" |
106 | | #include "gfx2DGlue.h" |
107 | | #include "mozilla/LookAndFeel.h" |
108 | | #include "UnitTransforms.h" |
109 | | #include "TiledLayerBuffer.h" // For TILEDLAYERBUFFER_TILE_SIZE |
110 | | #include "ClientLayerManager.h" |
111 | | #include "nsRefreshDriver.h" |
112 | | #include "nsIContentViewer.h" |
113 | | #include "LayersLogging.h" |
114 | | #include "mozilla/Preferences.h" |
115 | | #include "nsFrameSelection.h" |
116 | | #include "FrameLayerBuilder.h" |
117 | | #include "mozilla/layers/APZUtils.h" // for apz::CalculatePendingDisplayPort |
118 | | #include "mozilla/layers/CompositorBridgeChild.h" |
119 | | #include "mozilla/Telemetry.h" |
120 | | #include "mozilla/EventDispatcher.h" |
121 | | #include "mozilla/StyleAnimationValue.h" |
122 | | #include "mozilla/ServoStyleSet.h" |
123 | | #include "mozilla/WheelHandlingHelper.h" // for WheelHandlingUtils |
124 | | #include "RegionBuilder.h" |
125 | | #include "SVGViewportElement.h" |
126 | | #include "DisplayItemClip.h" |
127 | | #include "mozilla/layers/StackingContextHelper.h" |
128 | | #include "mozilla/layers/WebRenderLayerManager.h" |
129 | | #include "prenv.h" |
130 | | #include "RetainedDisplayListBuilder.h" |
131 | | #include "DisplayListChecker.h" |
132 | | #include "TextDrawTarget.h" |
133 | | #include "nsDeckFrame.h" |
134 | | #include "mozilla/dom/InspectorFontFace.h" |
135 | | |
136 | | #ifdef MOZ_XUL |
137 | | #include "nsXULPopupManager.h" |
138 | | #endif |
139 | | |
140 | | #include "GeckoProfiler.h" |
141 | | #include "nsAnimationManager.h" |
142 | | #include "nsTransitionManager.h" |
143 | | #include "mozilla/RestyleManager.h" |
144 | | #include "LayoutLogging.h" |
145 | | |
146 | | // Make sure getpid() works. |
147 | | #ifdef XP_WIN |
148 | | #include <process.h> |
149 | | #define getpid _getpid |
150 | | #else |
151 | | #include <unistd.h> |
152 | | #endif |
153 | | |
154 | | using namespace mozilla; |
155 | | using namespace mozilla::dom; |
156 | | using namespace mozilla::image; |
157 | | using namespace mozilla::layers; |
158 | | using namespace mozilla::layout; |
159 | | using namespace mozilla::gfx; |
160 | | using mozilla::dom::HTMLMediaElement_Binding::HAVE_NOTHING; |
161 | | using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA; |
162 | | |
163 | 0 | #define INTERCHARACTER_RUBY_ENABLED_PREF_NAME "layout.css.ruby.intercharacter.enabled" |
164 | 0 | #define CONTENT_SELECT_ENABLED_PREF_NAME "dom.select_popup_in_content.enabled" |
165 | | |
166 | | // The time in number of frames that we estimate for a refresh driver |
167 | | // to be quiescent |
168 | 3 | #define DEFAULT_QUIESCENT_FRAMES 2 |
169 | | // The time (milliseconds) we estimate is needed between the end of an |
170 | | // idle time and the next Tick. |
171 | 3 | #define DEFAULT_IDLE_PERIOD_TIME_LIMIT 1.0f |
172 | | |
173 | | #ifdef DEBUG |
174 | | // TODO: remove, see bug 598468. |
175 | | bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false; |
176 | | #endif // DEBUG |
177 | | |
178 | | typedef FrameMetrics::ViewID ViewID; |
179 | | typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox; |
180 | | |
181 | | /* static */ uint32_t nsLayoutUtils::sFontSizeInflationEmPerLine; |
182 | | /* static */ uint32_t nsLayoutUtils::sFontSizeInflationMinTwips; |
183 | | /* static */ uint32_t nsLayoutUtils::sFontSizeInflationLineThreshold; |
184 | | /* static */ int32_t nsLayoutUtils::sFontSizeInflationMappingIntercept; |
185 | | /* static */ uint32_t nsLayoutUtils::sFontSizeInflationMaxRatio; |
186 | | /* static */ bool nsLayoutUtils::sFontSizeInflationForceEnabled; |
187 | | /* static */ bool nsLayoutUtils::sFontSizeInflationDisabledInMasterProcess; |
188 | | /* static */ uint32_t nsLayoutUtils::sSystemFontScale; |
189 | | /* static */ uint32_t nsLayoutUtils::sZoomMaxPercent; |
190 | | /* static */ uint32_t nsLayoutUtils::sZoomMinPercent; |
191 | | /* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled; |
192 | | /* static */ bool nsLayoutUtils::sInterruptibleReflowEnabled; |
193 | | /* static */ bool nsLayoutUtils::sSVGTransformBoxEnabled; |
194 | | /* static */ uint32_t nsLayoutUtils::sIdlePeriodDeadlineLimit; |
195 | | /* static */ uint32_t nsLayoutUtils::sQuiescentFramesBeforeIdlePeriod; |
196 | | |
197 | | static ViewID sScrollIdCounter = FrameMetrics::START_SCROLL_ID; |
198 | | |
199 | | typedef nsDataHashtable<nsUint64HashKey, nsIContent*> ContentMap; |
200 | | static ContentMap* sContentMap = nullptr; |
201 | 0 | static ContentMap& GetContentMap() { |
202 | 0 | if (!sContentMap) { |
203 | 0 | sContentMap = new ContentMap(); |
204 | 0 | } |
205 | 0 | return *sContentMap; |
206 | 0 | } |
207 | | |
208 | | template<typename TestType> |
209 | | static bool |
210 | | HasMatchingAnimations(EffectSet* aEffects, TestType&& aTest) |
211 | 0 | { |
212 | 0 | for (KeyframeEffect* effect : *aEffects) { |
213 | 0 | if (aTest(*effect)) { |
214 | 0 | return true; |
215 | 0 | } |
216 | 0 | } |
217 | 0 |
|
218 | 0 | return false; |
219 | 0 | } Unexecuted instantiation: Unified_cpp_layout_base1.cpp:bool HasMatchingAnimations<nsLayoutUtils::HasCurrentTransitions(nsIFrame const*)::$_12&>(mozilla::EffectSet*, nsLayoutUtils::HasCurrentTransitions(nsIFrame const*)::$_12&) Unexecuted instantiation: Unified_cpp_layout_base1.cpp:bool HasMatchingAnimations<nsLayoutUtils::HasAnimationOfProperty(mozilla::EffectSet*, nsCSSPropertyID)::$_13>(mozilla::EffectSet*, nsLayoutUtils::HasAnimationOfProperty(mozilla::EffectSet*, nsCSSPropertyID)::$_13&&) Unexecuted instantiation: Unified_cpp_layout_base1.cpp:bool HasMatchingAnimations<nsLayoutUtils::HasAnimationOfProperty(nsIFrame const*, nsCSSPropertyID)::$_14&>(mozilla::EffectSet*, nsLayoutUtils::HasAnimationOfProperty(nsIFrame const*, nsCSSPropertyID)::$_14&) Unexecuted instantiation: Unified_cpp_layout_base1.cpp:bool HasMatchingAnimations<nsLayoutUtils::HasEffectiveAnimation(nsIFrame const*, nsCSSPropertyID)::$_15>(mozilla::EffectSet*, nsLayoutUtils::HasEffectiveAnimation(nsIFrame const*, nsCSSPropertyID)::$_15&&) |
220 | | |
221 | | template<typename TestType> |
222 | | static bool |
223 | | HasMatchingAnimations(const nsIFrame* aFrame, TestType&& aTest) |
224 | 0 | { |
225 | 0 | EffectSet* effects = EffectSet::GetEffectSet(aFrame); |
226 | 0 | if (!effects) { |
227 | 0 | return false; |
228 | 0 | } |
229 | 0 | |
230 | 0 | return HasMatchingAnimations(effects, aTest); |
231 | 0 | } Unexecuted instantiation: Unified_cpp_layout_base1.cpp:bool HasMatchingAnimations<nsLayoutUtils::HasCurrentTransitions(nsIFrame const*)::$_12>(nsIFrame const*, nsLayoutUtils::HasCurrentTransitions(nsIFrame const*)::$_12&&) Unexecuted instantiation: Unified_cpp_layout_base1.cpp:bool HasMatchingAnimations<nsLayoutUtils::HasAnimationOfProperty(nsIFrame const*, nsCSSPropertyID)::$_14>(nsIFrame const*, nsLayoutUtils::HasAnimationOfProperty(nsIFrame const*, nsCSSPropertyID)::$_14&&) |
232 | | |
233 | | bool |
234 | | nsLayoutUtils::HasCurrentTransitions(const nsIFrame* aFrame) |
235 | 0 | { |
236 | 0 | return HasMatchingAnimations(aFrame, |
237 | 0 | [](KeyframeEffect& aEffect) |
238 | 0 | { |
239 | 0 | // Since |aEffect| is current, it must have an associated Animation |
240 | 0 | // so we don't need to null-check the result of GetAnimation(). |
241 | 0 | return aEffect.IsCurrent() && aEffect.GetAnimation()->AsCSSTransition(); |
242 | 0 | } |
243 | 0 | ); |
244 | 0 | } |
245 | | |
246 | | static bool |
247 | | MayHaveAnimationOfProperty(EffectSet* effects, nsCSSPropertyID aProperty) |
248 | 0 | { |
249 | 0 | MOZ_ASSERT(effects); |
250 | 0 |
|
251 | 0 | if (aProperty == eCSSProperty_transform && |
252 | 0 | !effects->MayHaveTransformAnimation()) { |
253 | 0 | return false; |
254 | 0 | } |
255 | 0 | if (aProperty == eCSSProperty_opacity && |
256 | 0 | !effects->MayHaveOpacityAnimation()) { |
257 | 0 | return false; |
258 | 0 | } |
259 | 0 | |
260 | 0 | return true; |
261 | 0 | } |
262 | | |
263 | | static bool |
264 | | MayHaveAnimationOfProperty(const nsIFrame* aFrame, nsCSSPropertyID aProperty) |
265 | 0 | { |
266 | 0 | switch (aProperty) { |
267 | 0 | case eCSSProperty_transform: |
268 | 0 | return aFrame->MayHaveTransformAnimation(); |
269 | 0 | case eCSSProperty_opacity: |
270 | 0 | return aFrame->MayHaveOpacityAnimation(); |
271 | 0 | default: |
272 | 0 | MOZ_ASSERT_UNREACHABLE("unexpected property"); |
273 | 0 | return false; |
274 | 0 | } |
275 | 0 | } |
276 | | |
277 | | bool |
278 | | nsLayoutUtils::HasAnimationOfProperty(EffectSet* aEffectSet, |
279 | | nsCSSPropertyID aProperty) |
280 | 0 | { |
281 | 0 | if (!aEffectSet || !MayHaveAnimationOfProperty(aEffectSet, aProperty)) { |
282 | 0 | return false; |
283 | 0 | } |
284 | 0 | |
285 | 0 | return HasMatchingAnimations(aEffectSet, |
286 | 0 | [&aProperty](KeyframeEffect& aEffect) |
287 | 0 | { |
288 | 0 | return (aEffect.IsInEffect() || aEffect.IsCurrent()) && |
289 | 0 | aEffect.HasAnimationOfProperty(aProperty); |
290 | 0 | } |
291 | 0 | ); |
292 | 0 | } |
293 | | |
294 | | bool |
295 | | nsLayoutUtils::HasAnimationOfProperty(const nsIFrame* aFrame, |
296 | | nsCSSPropertyID aProperty) |
297 | 0 | { |
298 | 0 | if (!MayHaveAnimationOfProperty(aFrame, aProperty)) { |
299 | 0 | return false; |
300 | 0 | } |
301 | 0 | |
302 | 0 | return HasMatchingAnimations(aFrame, |
303 | 0 | [&aProperty](KeyframeEffect& aEffect) |
304 | 0 | { |
305 | 0 | return (aEffect.IsInEffect() || aEffect.IsCurrent()) && |
306 | 0 | aEffect.HasAnimationOfProperty(aProperty); |
307 | 0 | } |
308 | 0 | ); |
309 | 0 |
|
310 | 0 | } |
311 | | |
312 | | bool |
313 | | nsLayoutUtils::HasEffectiveAnimation(const nsIFrame* aFrame, |
314 | | nsCSSPropertyID aProperty) |
315 | 0 | { |
316 | 0 | EffectSet* effects = EffectSet::GetEffectSet(aFrame); |
317 | 0 | if (!effects || !MayHaveAnimationOfProperty(effects, aProperty)) { |
318 | 0 | return false; |
319 | 0 | } |
320 | 0 | |
321 | 0 | |
322 | 0 | return HasMatchingAnimations(effects, |
323 | 0 | [&aProperty](KeyframeEffect& aEffect) |
324 | 0 | { |
325 | 0 | return (aEffect.IsInEffect() || aEffect.IsCurrent()) && |
326 | 0 | aEffect.HasEffectiveAnimationOfProperty(aProperty); |
327 | 0 | } |
328 | 0 | ); |
329 | 0 | } |
330 | | |
331 | | static float |
332 | | GetSuitableScale(float aMaxScale, float aMinScale, |
333 | | nscoord aVisibleDimension, nscoord aDisplayDimension) |
334 | 0 | { |
335 | 0 | float displayVisibleRatio = float(aDisplayDimension) / |
336 | 0 | float(aVisibleDimension); |
337 | 0 | // We want to rasterize based on the largest scale used during the |
338 | 0 | // transform animation, unless that would make us rasterize something |
339 | 0 | // larger than the screen. But we never want to go smaller than the |
340 | 0 | // minimum scale over the animation. |
341 | 0 | if (FuzzyEqualsMultiplicative(displayVisibleRatio, aMaxScale, .01f)) { |
342 | 0 | // Using aMaxScale may make us rasterize something a fraction larger than |
343 | 0 | // the screen. However, if aMaxScale happens to be the final scale of a |
344 | 0 | // transform animation it is better to use aMaxScale so that for the |
345 | 0 | // fraction of a second before we delayerize the composited texture it has |
346 | 0 | // a better chance of being pixel aligned and composited without resampling |
347 | 0 | // (avoiding visually clunky delayerization). |
348 | 0 | return aMaxScale; |
349 | 0 | } |
350 | 0 | return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale); |
351 | 0 | } |
352 | | |
353 | | static inline void |
354 | | UpdateMinMaxScale(const nsIFrame* aFrame, |
355 | | const AnimationValue& aValue, |
356 | | Size& aMinScale, |
357 | | Size& aMaxScale) |
358 | 0 | { |
359 | 0 | Size size = aValue.GetScaleValue(aFrame); |
360 | 0 | aMaxScale.width = std::max<float>(aMaxScale.width, size.width); |
361 | 0 | aMaxScale.height = std::max<float>(aMaxScale.height, size.height); |
362 | 0 | aMinScale.width = std::min<float>(aMinScale.width, size.width); |
363 | 0 | aMinScale.height = std::min<float>(aMinScale.height, size.height); |
364 | 0 | } |
365 | | |
366 | | static void |
367 | | GetMinAndMaxScaleForAnimationProperty(const nsIFrame* aFrame, |
368 | | nsTArray<RefPtr<dom::Animation>>& aAnimations, |
369 | | Size& aMaxScale, |
370 | | Size& aMinScale) |
371 | 0 | { |
372 | 0 | for (dom::Animation* anim : aAnimations) { |
373 | 0 | // This method is only expected to be passed animations that are running on |
374 | 0 | // the compositor and we only pass playing animations to the compositor, |
375 | 0 | // which are, by definition, "relevant" animations (animations that are |
376 | 0 | // not yet finished or which are filling forwards). |
377 | 0 | MOZ_ASSERT(anim->IsRelevant()); |
378 | 0 |
|
379 | 0 | dom::KeyframeEffect* effect = |
380 | 0 | anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr; |
381 | 0 | MOZ_ASSERT(effect, "A playing animation should have a keyframe effect"); |
382 | 0 | for (size_t propIdx = effect->Properties().Length(); propIdx-- != 0; ) { |
383 | 0 | const AnimationProperty& prop = effect->Properties()[propIdx]; |
384 | 0 | if (prop.mProperty != eCSSProperty_transform) { |
385 | 0 | continue; |
386 | 0 | } |
387 | 0 | |
388 | 0 | // We need to factor in the scale of the base style if the base style |
389 | 0 | // will be used on the compositor. |
390 | 0 | AnimationValue baseStyle = effect->BaseStyle(prop.mProperty); |
391 | 0 | if (!baseStyle.IsNull()) { |
392 | 0 | UpdateMinMaxScale(aFrame, baseStyle, aMinScale, aMaxScale); |
393 | 0 | } |
394 | 0 |
|
395 | 0 | for (const AnimationPropertySegment& segment : prop.mSegments) { |
396 | 0 | // In case of add or accumulate composite, StyleAnimationValue does |
397 | 0 | // not have a valid value. |
398 | 0 | if (segment.HasReplaceableFromValue()) { |
399 | 0 | UpdateMinMaxScale(aFrame, segment.mFromValue, aMinScale, aMaxScale); |
400 | 0 | } |
401 | 0 | if (segment.HasReplaceableToValue()) { |
402 | 0 | UpdateMinMaxScale(aFrame, segment.mToValue, aMinScale, aMaxScale); |
403 | 0 | } |
404 | 0 | } |
405 | 0 | } |
406 | 0 | } |
407 | 0 | } |
408 | | |
409 | | Size |
410 | | nsLayoutUtils::ComputeSuitableScaleForAnimation(const nsIFrame* aFrame, |
411 | | const nsSize& aVisibleSize, |
412 | | const nsSize& aDisplaySize) |
413 | 0 | { |
414 | 0 | Size maxScale(std::numeric_limits<float>::min(), |
415 | 0 | std::numeric_limits<float>::min()); |
416 | 0 | Size minScale(std::numeric_limits<float>::max(), |
417 | 0 | std::numeric_limits<float>::max()); |
418 | 0 |
|
419 | 0 | nsTArray<RefPtr<dom::Animation>> compositorAnimations = |
420 | 0 | EffectCompositor::GetAnimationsForCompositor(aFrame, |
421 | 0 | eCSSProperty_transform); |
422 | 0 | GetMinAndMaxScaleForAnimationProperty(aFrame, compositorAnimations, |
423 | 0 | maxScale, minScale); |
424 | 0 |
|
425 | 0 | if (maxScale.width == std::numeric_limits<float>::min()) { |
426 | 0 | // We didn't encounter a transform |
427 | 0 | return Size(1.0, 1.0); |
428 | 0 | } |
429 | 0 | |
430 | 0 | return Size(GetSuitableScale(maxScale.width, minScale.width, |
431 | 0 | aVisibleSize.width, aDisplaySize.width), |
432 | 0 | GetSuitableScale(maxScale.height, minScale.height, |
433 | 0 | aVisibleSize.height, aDisplaySize.height)); |
434 | 0 | } |
435 | | |
436 | | bool |
437 | | nsLayoutUtils::AreAsyncAnimationsEnabled() |
438 | 0 | { |
439 | 0 | static bool sAreAsyncAnimationsEnabled; |
440 | 0 | static bool sAsyncPrefCached = false; |
441 | 0 |
|
442 | 0 | if (!sAsyncPrefCached) { |
443 | 0 | sAsyncPrefCached = true; |
444 | 0 | Preferences::AddBoolVarCache(&sAreAsyncAnimationsEnabled, |
445 | 0 | "layers.offmainthreadcomposition.async-animations"); |
446 | 0 | } |
447 | 0 |
|
448 | 0 | return sAreAsyncAnimationsEnabled && |
449 | 0 | gfxPlatform::OffMainThreadCompositingEnabled(); |
450 | 0 | } |
451 | | |
452 | | bool |
453 | | nsLayoutUtils::IsAnimationLoggingEnabled() |
454 | 0 | { |
455 | 0 | static bool sShouldLog; |
456 | 0 | static bool sShouldLogPrefCached; |
457 | 0 |
|
458 | 0 | if (!sShouldLogPrefCached) { |
459 | 0 | sShouldLogPrefCached = true; |
460 | 0 | Preferences::AddBoolVarCache(&sShouldLog, |
461 | 0 | "layers.offmainthreadcomposition.log-animations"); |
462 | 0 | } |
463 | 0 |
|
464 | 0 | return sShouldLog; |
465 | 0 | } |
466 | | |
467 | | bool |
468 | | nsLayoutUtils::AreRetainedDisplayListsEnabled() |
469 | 0 | { |
470 | 0 | if (XRE_IsContentProcess()) { |
471 | 0 | return gfxPrefs::LayoutRetainDisplayList(); |
472 | 0 | } |
473 | 0 | |
474 | 0 | if (XRE_IsE10sParentProcess()) { |
475 | 0 | return gfxPrefs::LayoutRetainDisplayListChrome(); |
476 | 0 | } |
477 | 0 | |
478 | 0 | // Retained display lists require e10s. |
479 | 0 | return false; |
480 | 0 | } |
481 | | |
482 | | bool |
483 | | nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(nsIFrame* aFrame) |
484 | 0 | { |
485 | 0 | const nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame); |
486 | 0 | MOZ_ASSERT(displayRoot); |
487 | 0 | return displayRoot->HasProperty(RetainedDisplayListBuilder::Cached()); |
488 | 0 | } |
489 | | |
490 | | bool |
491 | | nsLayoutUtils::GPUImageScalingEnabled() |
492 | 0 | { |
493 | 0 | static bool sGPUImageScalingEnabled; |
494 | 0 | static bool sGPUImageScalingPrefInitialised = false; |
495 | 0 |
|
496 | 0 | if (!sGPUImageScalingPrefInitialised) { |
497 | 0 | sGPUImageScalingPrefInitialised = true; |
498 | 0 | sGPUImageScalingEnabled = |
499 | 0 | Preferences::GetBool("layout.gpu-image-scaling.enabled", false); |
500 | 0 | } |
501 | 0 |
|
502 | 0 | return sGPUImageScalingEnabled; |
503 | 0 | } |
504 | | |
505 | | bool |
506 | | nsLayoutUtils::AnimatedImageLayersEnabled() |
507 | 0 | { |
508 | 0 | static bool sAnimatedImageLayersEnabled; |
509 | 0 | static bool sAnimatedImageLayersPrefCached = false; |
510 | 0 |
|
511 | 0 | if (!sAnimatedImageLayersPrefCached) { |
512 | 0 | sAnimatedImageLayersPrefCached = true; |
513 | 0 | Preferences::AddBoolVarCache(&sAnimatedImageLayersEnabled, |
514 | 0 | "layout.animated-image-layers.enabled", |
515 | 0 | false); |
516 | 0 | } |
517 | 0 |
|
518 | 0 | return sAnimatedImageLayersEnabled; |
519 | 0 | } |
520 | | |
521 | | bool |
522 | | nsLayoutUtils::IsInterCharacterRubyEnabled() |
523 | 0 | { |
524 | 0 | static bool sInterCharacterRubyEnabled; |
525 | 0 | static bool sInterCharacterRubyEnabledPrefCached = false; |
526 | 0 |
|
527 | 0 | if (!sInterCharacterRubyEnabledPrefCached) { |
528 | 0 | sInterCharacterRubyEnabledPrefCached = true; |
529 | 0 | Preferences::AddBoolVarCache(&sInterCharacterRubyEnabled, |
530 | 0 | INTERCHARACTER_RUBY_ENABLED_PREF_NAME, |
531 | 0 | false); |
532 | 0 | } |
533 | 0 |
|
534 | 0 | return sInterCharacterRubyEnabled; |
535 | 0 | } |
536 | | |
537 | | bool |
538 | | nsLayoutUtils::IsContentSelectEnabled() |
539 | 0 | { |
540 | 0 | static bool sContentSelectEnabled; |
541 | 0 | static bool sContentSelectEnabledPrefCached = false; |
542 | 0 |
|
543 | 0 | if (!sContentSelectEnabledPrefCached) { |
544 | 0 | sContentSelectEnabledPrefCached = true; |
545 | 0 | Preferences::AddBoolVarCache(&sContentSelectEnabled, |
546 | 0 | CONTENT_SELECT_ENABLED_PREF_NAME, |
547 | 0 | false); |
548 | 0 | } |
549 | 0 |
|
550 | 0 | return sContentSelectEnabled; |
551 | 0 | } |
552 | | |
553 | | void |
554 | | nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame, |
555 | | nsOverflowAreas& aOverflowAreas, |
556 | | FrameChildListIDs aSkipChildLists) |
557 | 0 | { |
558 | 0 | // Iterate over all children except pop-ups. |
559 | 0 | FrameChildListIDs skip = aSkipChildLists | |
560 | 0 | nsIFrame::kSelectPopupList | nsIFrame::kPopupList; |
561 | 0 | for (nsIFrame::ChildListIterator childLists(aFrame); |
562 | 0 | !childLists.IsDone(); childLists.Next()) { |
563 | 0 | if (skip.Contains(childLists.CurrentID())) { |
564 | 0 | continue; |
565 | 0 | } |
566 | 0 | |
567 | 0 | nsFrameList children = childLists.CurrentList(); |
568 | 0 | for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) { |
569 | 0 | nsIFrame* child = e.get(); |
570 | 0 | nsOverflowAreas childOverflow = |
571 | 0 | child->GetOverflowAreas() + child->GetPosition(); |
572 | 0 | aOverflowAreas.UnionWith(childOverflow); |
573 | 0 | } |
574 | 0 | } |
575 | 0 | } |
576 | | |
577 | | static void DestroyViewID(void* aObject, nsAtom* aPropertyName, |
578 | | void* aPropertyValue, void* aData) |
579 | 0 | { |
580 | 0 | ViewID* id = static_cast<ViewID*>(aPropertyValue); |
581 | 0 | GetContentMap().Remove(*id); |
582 | 0 | delete id; |
583 | 0 | } |
584 | | |
585 | | /** |
586 | | * A namespace class for static layout utilities. |
587 | | */ |
588 | | |
589 | | bool |
590 | | nsLayoutUtils::FindIDFor(const nsIContent* aContent, ViewID* aOutViewId) |
591 | 0 | { |
592 | 0 | void* scrollIdProperty = aContent->GetProperty(nsGkAtoms::RemoteId); |
593 | 0 | if (scrollIdProperty) { |
594 | 0 | *aOutViewId = *static_cast<ViewID*>(scrollIdProperty); |
595 | 0 | return true; |
596 | 0 | } |
597 | 0 | return false; |
598 | 0 | } |
599 | | |
600 | | ViewID |
601 | | nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent) |
602 | 0 | { |
603 | 0 | ViewID scrollId; |
604 | 0 |
|
605 | 0 | if (!FindIDFor(aContent, &scrollId)) { |
606 | 0 | scrollId = sScrollIdCounter++; |
607 | 0 | aContent->SetProperty(nsGkAtoms::RemoteId, new ViewID(scrollId), |
608 | 0 | DestroyViewID); |
609 | 0 | GetContentMap().Put(scrollId, aContent); |
610 | 0 | } |
611 | 0 |
|
612 | 0 | return scrollId; |
613 | 0 | } |
614 | | |
615 | | nsIContent* |
616 | | nsLayoutUtils::FindContentFor(ViewID aId) |
617 | 0 | { |
618 | 0 | MOZ_ASSERT(aId != FrameMetrics::NULL_SCROLL_ID, |
619 | 0 | "Cannot find a content element in map for null IDs."); |
620 | 0 | nsIContent* content; |
621 | 0 | bool exists = GetContentMap().Get(aId, &content); |
622 | 0 |
|
623 | 0 | if (exists) { |
624 | 0 | return content; |
625 | 0 | } else { |
626 | 0 | return nullptr; |
627 | 0 | } |
628 | 0 | } |
629 | | |
630 | | static nsIFrame* |
631 | | GetScrollFrameFromContent(nsIContent* aContent) |
632 | 0 | { |
633 | 0 | nsIFrame* frame = aContent->GetPrimaryFrame(); |
634 | 0 | if (aContent->OwnerDoc()->GetRootElement() == aContent) { |
635 | 0 | nsIPresShell* presShell = frame ? frame->PresShell() : nullptr; |
636 | 0 | if (!presShell) { |
637 | 0 | presShell = aContent->OwnerDoc()->GetShell(); |
638 | 0 | } |
639 | 0 | // We want the scroll frame, the root scroll frame differs from all |
640 | 0 | // others in that the primary frame is not the scroll frame. |
641 | 0 | nsIFrame* rootScrollFrame = presShell ? presShell->GetRootScrollFrame() : nullptr; |
642 | 0 | if (rootScrollFrame) { |
643 | 0 | frame = rootScrollFrame; |
644 | 0 | } |
645 | 0 | } |
646 | 0 | return frame; |
647 | 0 | } |
648 | | |
649 | | nsIScrollableFrame* |
650 | | nsLayoutUtils::FindScrollableFrameFor(ViewID aId) |
651 | 0 | { |
652 | 0 | nsIContent* content = FindContentFor(aId); |
653 | 0 | if (!content) { |
654 | 0 | return nullptr; |
655 | 0 | } |
656 | 0 | |
657 | 0 | nsIFrame* scrollFrame = GetScrollFrameFromContent(content); |
658 | 0 | return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr; |
659 | 0 | } |
660 | | |
661 | | ViewID |
662 | | nsLayoutUtils::FindIDForScrollableFrame(nsIScrollableFrame* aScrollable) |
663 | 0 | { |
664 | 0 | if (!aScrollable) { |
665 | 0 | return FrameMetrics::NULL_SCROLL_ID; |
666 | 0 | } |
667 | 0 | |
668 | 0 | nsIFrame* scrollFrame = do_QueryFrame(aScrollable); |
669 | 0 | nsIContent* scrollContent = scrollFrame->GetContent(); |
670 | 0 |
|
671 | 0 | FrameMetrics::ViewID scrollId; |
672 | 0 | if (scrollContent && |
673 | 0 | nsLayoutUtils::FindIDFor(scrollContent, &scrollId)) { |
674 | 0 | return scrollId; |
675 | 0 | } |
676 | 0 | |
677 | 0 | return FrameMetrics::NULL_SCROLL_ID; |
678 | 0 | } |
679 | | |
680 | | static nsRect |
681 | | ApplyRectMultiplier(nsRect aRect, float aMultiplier) |
682 | 0 | { |
683 | 0 | if (aMultiplier == 1.0f) { |
684 | 0 | return aRect; |
685 | 0 | } |
686 | 0 | float newWidth = aRect.width * aMultiplier; |
687 | 0 | float newHeight = aRect.height * aMultiplier; |
688 | 0 | float newX = aRect.x - ((newWidth - aRect.width) / 2.0f); |
689 | 0 | float newY = aRect.y - ((newHeight - aRect.height) / 2.0f); |
690 | 0 | // Rounding doesn't matter too much here, do a round-in |
691 | 0 | return nsRect(ceil(newX), ceil(newY), floor(newWidth), floor(newHeight)); |
692 | 0 | } |
693 | | |
694 | | bool |
695 | | nsLayoutUtils::UsesAsyncScrolling(nsIFrame* aFrame) |
696 | 0 | { |
697 | | #ifdef MOZ_WIDGET_ANDROID |
698 | | // We always have async scrolling for android |
699 | | return true; |
700 | | #endif |
701 | |
|
702 | 0 | return AsyncPanZoomEnabled(aFrame); |
703 | 0 | } |
704 | | |
705 | | bool |
706 | | nsLayoutUtils::AsyncPanZoomEnabled(nsIFrame* aFrame) |
707 | 0 | { |
708 | 0 | // We use this as a shortcut, since if the compositor will never use APZ, |
709 | 0 | // no widget will either. |
710 | 0 | if (!gfxPlatform::AsyncPanZoomEnabled()) { |
711 | 0 | return false; |
712 | 0 | } |
713 | 0 | |
714 | 0 | nsIFrame *frame = nsLayoutUtils::GetDisplayRootFrame(aFrame); |
715 | 0 | nsIWidget* widget = frame->GetNearestWidget(); |
716 | 0 | if (!widget) { |
717 | 0 | return false; |
718 | 0 | } |
719 | 0 | return widget->AsyncPanZoomEnabled(); |
720 | 0 | } |
721 | | |
722 | | float |
723 | 0 | nsLayoutUtils::GetCurrentAPZResolutionScale(nsIPresShell* aShell) { |
724 | 0 | return aShell ? aShell->GetCumulativeNonRootScaleResolution() : 1.0; |
725 | 0 | } |
726 | | |
727 | | // Return the maximum displayport size, based on the LayerManager's maximum |
728 | | // supported texture size. The result is in app units. |
729 | | static nscoord |
730 | | GetMaxDisplayPortSize(nsIContent* aContent, nsPresContext* aFallbackPrescontext) |
731 | 0 | { |
732 | 0 | MOZ_ASSERT(!gfxPrefs::LayersTilesEnabled(), "Do not clamp displayports if tiling is enabled"); |
733 | 0 |
|
734 | 0 | // Pick a safe maximum displayport size for sanity purposes. This is the |
735 | 0 | // lowest maximum texture size on tileless-platforms (Windows, D3D10). |
736 | 0 | // If the gfx.max-texture-size pref is set, further restrict the displayport |
737 | 0 | // size to fit within that, because the compositor won't upload stuff larger |
738 | 0 | // than this size. |
739 | 0 | nscoord safeMaximum = aFallbackPrescontext |
740 | 0 | ? aFallbackPrescontext->DevPixelsToAppUnits( |
741 | 0 | std::min(8192, gfxPlatform::MaxTextureSize())) |
742 | 0 | : nscoord_MAX; |
743 | 0 |
|
744 | 0 | nsIFrame* frame = aContent->GetPrimaryFrame(); |
745 | 0 | if (!frame) { |
746 | 0 | return safeMaximum; |
747 | 0 | } |
748 | 0 | frame = nsLayoutUtils::GetDisplayRootFrame(frame); |
749 | 0 |
|
750 | 0 | nsIWidget* widget = frame->GetNearestWidget(); |
751 | 0 | if (!widget) { |
752 | 0 | return safeMaximum; |
753 | 0 | } |
754 | 0 | LayerManager* lm = widget->GetLayerManager(); |
755 | 0 | if (!lm) { |
756 | 0 | return safeMaximum; |
757 | 0 | } |
758 | 0 | nsPresContext* presContext = frame->PresContext(); |
759 | 0 |
|
760 | 0 | int32_t maxSizeInDevPixels = lm->GetMaxTextureSize(); |
761 | 0 | if (maxSizeInDevPixels < 0 || maxSizeInDevPixels == INT_MAX) { |
762 | 0 | return safeMaximum; |
763 | 0 | } |
764 | 0 | maxSizeInDevPixels = std::min(maxSizeInDevPixels, gfxPlatform::MaxTextureSize()); |
765 | 0 | return presContext->DevPixelsToAppUnits(maxSizeInDevPixels); |
766 | 0 | } |
767 | | |
768 | | static nsRect |
769 | | GetDisplayPortFromRectData(nsIContent* aContent, |
770 | | DisplayPortPropertyData* aRectData, |
771 | | float aMultiplier) |
772 | 0 | { |
773 | 0 | // In the case where the displayport is set as a rect, we assume it is |
774 | 0 | // already aligned and clamped as necessary. The burden to do that is |
775 | 0 | // on the setter of the displayport. In practice very few places set the |
776 | 0 | // displayport directly as a rect (mostly tests). We still do need to |
777 | 0 | // expand it by the multiplier though. |
778 | 0 | return ApplyRectMultiplier(aRectData->mRect, aMultiplier); |
779 | 0 | } |
780 | | |
781 | | static nsRect |
782 | | GetDisplayPortFromMarginsData(nsIContent* aContent, |
783 | | DisplayPortMarginsPropertyData* aMarginsData, |
784 | | float aMultiplier) |
785 | 0 | { |
786 | 0 | // In the case where the displayport is set via margins, we apply the margins |
787 | 0 | // to a base rect. Then we align the expanded rect based on the alignment |
788 | 0 | // requested, further expand the rect by the multiplier, and finally, clamp it |
789 | 0 | // to the size of the scrollable rect. |
790 | 0 |
|
791 | 0 | nsRect base; |
792 | 0 | if (nsRect* baseData = static_cast<nsRect*>(aContent->GetProperty(nsGkAtoms::DisplayPortBase))) { |
793 | 0 | base = *baseData; |
794 | 0 | } else { |
795 | 0 | // In theory we shouldn't get here, but we do sometimes (see bug 1212136). |
796 | 0 | // Fall through for graceful handling. |
797 | 0 | } |
798 | 0 |
|
799 | 0 | nsIFrame* frame = GetScrollFrameFromContent(aContent); |
800 | 0 | if (!frame) { |
801 | 0 | // Turns out we can't really compute it. Oops. We still should return |
802 | 0 | // something sane. Note that since we can't clamp the rect without a |
803 | 0 | // frame, we don't apply the multiplier either as it can cause the result |
804 | 0 | // to leak outside the scrollable area. |
805 | 0 | NS_WARNING("Attempting to get a displayport from a content with no primary frame!"); |
806 | 0 | return base; |
807 | 0 | } |
808 | 0 |
|
809 | 0 | bool isRoot = false; |
810 | 0 | if (aContent->OwnerDoc()->GetRootElement() == aContent) { |
811 | 0 | isRoot = true; |
812 | 0 | } |
813 | 0 |
|
814 | 0 | nsPoint scrollPos; |
815 | 0 | if (nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame()) { |
816 | 0 | scrollPos = scrollableFrame->GetScrollPosition(); |
817 | 0 | } |
818 | 0 |
|
819 | 0 | nsPresContext* presContext = frame->PresContext(); |
820 | 0 | int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); |
821 | 0 |
|
822 | 0 | LayoutDeviceToScreenScale2D res(presContext->PresShell()->GetCumulativeResolution() |
823 | 0 | * nsLayoutUtils::GetTransformToAncestorScale(frame)); |
824 | 0 |
|
825 | 0 | // Calculate the expanded scrollable rect, which we'll be clamping the |
826 | 0 | // displayport to. |
827 | 0 | nsRect expandedScrollableRect = |
828 | 0 | nsLayoutUtils::CalculateExpandedScrollableRect(frame); |
829 | 0 |
|
830 | 0 | // GetTransformToAncestorScale() can return 0. In this case, just return the |
831 | 0 | // base rect (clamped to the expanded scrollable rect), as other calculations |
832 | 0 | // would run into divisions by zero. |
833 | 0 | if (res == LayoutDeviceToScreenScale2D(0, 0)) { |
834 | 0 | // Make sure the displayport remains within the scrollable rect. |
835 | 0 | return base.MoveInsideAndClamp(expandedScrollableRect - scrollPos); |
836 | 0 | } |
837 | 0 | |
838 | 0 | // First convert the base rect to screen pixels |
839 | 0 | LayoutDeviceToScreenScale2D parentRes = res; |
840 | 0 | if (isRoot) { |
841 | 0 | // the base rect for root scroll frames is specified in the parent document |
842 | 0 | // coordinate space, so it doesn't include the local resolution. |
843 | 0 | float localRes = presContext->PresShell()->GetResolution(); |
844 | 0 | parentRes.xScale /= localRes; |
845 | 0 | parentRes.yScale /= localRes; |
846 | 0 | } |
847 | 0 | ScreenRect screenRect = LayoutDeviceRect::FromAppUnits(base, auPerDevPixel) |
848 | 0 | * parentRes; |
849 | 0 |
|
850 | 0 | // Note on the correctness of applying the alignment in Screen space: |
851 | 0 | // The correct space to apply the alignment in would be Layer space, but |
852 | 0 | // we don't necessarily know the scale to convert to Layer space at this |
853 | 0 | // point because Layout may not yet have chosen the resolution at which to |
854 | 0 | // render (it chooses that in FrameLayerBuilder, but this can be called |
855 | 0 | // during display list building). Therefore, we perform the alignment in |
856 | 0 | // Screen space, which basically assumes that Layout chose to render at |
857 | 0 | // screen resolution; since this is what Layout does most of the time, |
858 | 0 | // this is a good approximation. A proper solution would involve moving |
859 | 0 | // the choosing of the resolution to display-list building time. |
860 | 0 | ScreenSize alignment; |
861 | 0 |
|
862 | 0 | nsIPresShell* presShell = presContext->PresShell(); |
863 | 0 | MOZ_ASSERT(presShell); |
864 | 0 |
|
865 | 0 | if (presShell->IsDisplayportSuppressed()) { |
866 | 0 | alignment = ScreenSize(1, 1); |
867 | 0 | } else if (gfxPrefs::LayersTilesEnabled()) { |
868 | 0 | // Don't align to tiles if they are too large, because we could expand |
869 | 0 | // the displayport by a lot which can take more paint time. It's a tradeoff |
870 | 0 | // though because if we don't align to tiles we have more waste on upload. |
871 | 0 | IntSize tileSize = gfxVars::TileSize(); |
872 | 0 | alignment = ScreenSize(std::min(256, tileSize.width), std::min(256, tileSize.height)); |
873 | 0 | } else { |
874 | 0 | // If we're not drawing with tiles then we need to be careful about not |
875 | 0 | // hitting the max texture size and we only need 1 draw call per layer |
876 | 0 | // so we can align to a smaller multiple. |
877 | 0 | alignment = ScreenSize(128, 128); |
878 | 0 | } |
879 | 0 |
|
880 | 0 | // Avoid division by zero. |
881 | 0 | if (alignment.width == 0) { |
882 | 0 | alignment.width = 128; |
883 | 0 | } |
884 | 0 | if (alignment.height == 0) { |
885 | 0 | alignment.height = 128; |
886 | 0 | } |
887 | 0 |
|
888 | 0 | if (gfxPrefs::LayersTilesEnabled()) { |
889 | 0 | // Expand the rect by the margins |
890 | 0 | screenRect.Inflate(aMarginsData->mMargins); |
891 | 0 | } else { |
892 | 0 | // Calculate the displayport to make sure we fit within the max texture size |
893 | 0 | // when not tiling. |
894 | 0 | nscoord maxSizeAppUnits = GetMaxDisplayPortSize(aContent, presContext); |
895 | 0 | MOZ_ASSERT(maxSizeAppUnits < nscoord_MAX); |
896 | 0 |
|
897 | 0 | // The alignment code can round up to 3 tiles, we want to make sure |
898 | 0 | // that the displayport can grow by up to 3 tiles without going |
899 | 0 | // over the max texture size. |
900 | 0 | const int MAX_ALIGN_ROUNDING = 3; |
901 | 0 |
|
902 | 0 | // Find the maximum size in screen pixels. |
903 | 0 | int32_t maxSizeDevPx = presContext->AppUnitsToDevPixels(maxSizeAppUnits); |
904 | 0 | int32_t maxWidthScreenPx = floor(double(maxSizeDevPx) * res.xScale) - |
905 | 0 | MAX_ALIGN_ROUNDING * alignment.width; |
906 | 0 | int32_t maxHeightScreenPx = floor(double(maxSizeDevPx) * res.yScale) - |
907 | 0 | MAX_ALIGN_ROUNDING * alignment.height; |
908 | 0 |
|
909 | 0 | // For each axis, inflate the margins up to the maximum size. |
910 | 0 | const ScreenMargin& margins = aMarginsData->mMargins; |
911 | 0 | if (screenRect.height < maxHeightScreenPx) { |
912 | 0 | int32_t budget = maxHeightScreenPx - screenRect.height; |
913 | 0 | // Scale the margins down to fit into the budget if necessary, maintaining |
914 | 0 | // their relative ratio. |
915 | 0 | float scale = 1.0f; |
916 | 0 | if (float(budget) < margins.TopBottom()) { |
917 | 0 | scale = float(budget) / margins.TopBottom(); |
918 | 0 | } |
919 | 0 | float top = margins.top * scale; |
920 | 0 | float bottom = margins.bottom * scale; |
921 | 0 | screenRect.y -= top; |
922 | 0 | screenRect.height += top + bottom; |
923 | 0 | } |
924 | 0 | if (screenRect.width < maxWidthScreenPx) { |
925 | 0 | int32_t budget = maxWidthScreenPx - screenRect.width; |
926 | 0 | float scale = 1.0f; |
927 | 0 | if (float(budget) < margins.LeftRight()) { |
928 | 0 | scale = float(budget) / margins.LeftRight(); |
929 | 0 | } |
930 | 0 | float left = margins.left * scale; |
931 | 0 | float right = margins.right * scale; |
932 | 0 | screenRect.x -= left; |
933 | 0 | screenRect.width += left + right; |
934 | 0 | } |
935 | 0 | } |
936 | 0 |
|
937 | 0 | ScreenPoint scrollPosScreen = LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel) |
938 | 0 | * res; |
939 | 0 |
|
940 | 0 | // Round-out the display port to the nearest alignment (tiles) |
941 | 0 | screenRect += scrollPosScreen; |
942 | 0 | float x = alignment.width * floor(screenRect.x / alignment.width); |
943 | 0 | float y = alignment.height * floor(screenRect.y / alignment.height); |
944 | 0 | float w = alignment.width * ceil(screenRect.width / alignment.width + 1); |
945 | 0 | float h = alignment.height * ceil(screenRect.height / alignment.height + 1); |
946 | 0 | screenRect = ScreenRect(x, y, w, h); |
947 | 0 | screenRect -= scrollPosScreen; |
948 | 0 |
|
949 | 0 | // Convert the aligned rect back into app units. |
950 | 0 | nsRect result = LayoutDeviceRect::ToAppUnits(screenRect / res, auPerDevPixel); |
951 | 0 |
|
952 | 0 | // If we have non-zero margins, expand the displayport for the low-res buffer |
953 | 0 | // if that's what we're drawing. If we have zero margins, we want the |
954 | 0 | // displayport to reflect the scrollport. |
955 | 0 | if (aMarginsData->mMargins != ScreenMargin()) { |
956 | 0 | result = ApplyRectMultiplier(result, aMultiplier); |
957 | 0 | } |
958 | 0 |
|
959 | 0 | // Make sure the displayport remains within the scrollable rect. |
960 | 0 | result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos); |
961 | 0 |
|
962 | 0 | return result; |
963 | 0 | } |
964 | | |
965 | | static bool |
966 | | HasVisibleAnonymousContents(nsIDocument* aDoc) |
967 | 0 | { |
968 | 0 | for (RefPtr<AnonymousContent>& ac : aDoc->GetAnonymousContents()) { |
969 | 0 | // We check to see if the anonymous content node has a frame. If it doesn't, |
970 | 0 | // that means that's not visible to the user because e.g. it's display:none. |
971 | 0 | // For now we assume that if it has a frame, it is visible. We might be able |
972 | 0 | // to refine this further by adding complexity if it turns out this condition |
973 | 0 | // results in a lot of false positives. |
974 | 0 | if (ac->ContentNode().GetPrimaryFrame()) { |
975 | 0 | return true; |
976 | 0 | } |
977 | 0 | } |
978 | 0 | return false; |
979 | 0 | } |
980 | | |
981 | | bool |
982 | | nsLayoutUtils::ShouldDisableApzForElement(nsIContent* aContent) |
983 | 0 | { |
984 | 0 | if (!aContent) { |
985 | 0 | return false; |
986 | 0 | } |
987 | 0 | |
988 | 0 | nsIDocument* doc = aContent->GetComposedDoc(); |
989 | 0 | nsIPresShell* rootShell = APZCCallbackHelper::GetRootContentDocumentPresShellForContent(aContent); |
990 | 0 | if (rootShell) { |
991 | 0 | if (nsIDocument* rootDoc = rootShell->GetDocument()) { |
992 | 0 | nsIContent* rootContent = rootShell->GetRootScrollFrame() |
993 | 0 | ? rootShell->GetRootScrollFrame()->GetContent() |
994 | 0 | : rootDoc->GetDocumentElement(); |
995 | 0 | // For the AccessibleCaret: disable APZ on any scrollable subframes that |
996 | 0 | // are not the root scrollframe of a document, if the document has any |
997 | 0 | // visible anonymous contents. |
998 | 0 | // If we find this is triggering in too many scenarios then we might |
999 | 0 | // want to tighten this check further. The main use cases for which we want |
1000 | 0 | // to disable APZ as of this writing are listed in bug 1316318. |
1001 | 0 | if (aContent != rootContent && HasVisibleAnonymousContents(rootDoc)) { |
1002 | 0 | return true; |
1003 | 0 | } |
1004 | 0 | } |
1005 | 0 | } |
1006 | 0 | |
1007 | 0 | if (!doc) { |
1008 | 0 | return false; |
1009 | 0 | } |
1010 | 0 | return gfxPrefs::APZDisableForScrollLinkedEffects() && |
1011 | 0 | doc->HasScrollLinkedEffect(); |
1012 | 0 | } |
1013 | | |
1014 | | static bool |
1015 | | GetDisplayPortData(nsIContent* aContent, |
1016 | | DisplayPortPropertyData** aOutRectData, |
1017 | | DisplayPortMarginsPropertyData** aOutMarginsData) |
1018 | 0 | { |
1019 | 0 | MOZ_ASSERT(aOutRectData && aOutMarginsData); |
1020 | 0 |
|
1021 | 0 | *aOutRectData = |
1022 | 0 | static_cast<DisplayPortPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPort)); |
1023 | 0 | *aOutMarginsData = |
1024 | 0 | static_cast<DisplayPortMarginsPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); |
1025 | 0 |
|
1026 | 0 | if (!*aOutRectData && !*aOutMarginsData) { |
1027 | 0 | // This content element has no displayport data at all |
1028 | 0 | return false; |
1029 | 0 | } |
1030 | 0 | |
1031 | 0 | if (*aOutRectData && *aOutMarginsData) { |
1032 | 0 | // choose margins if equal priority |
1033 | 0 | if ((*aOutRectData)->mPriority > (*aOutMarginsData)->mPriority) { |
1034 | 0 | *aOutMarginsData = nullptr; |
1035 | 0 | } else { |
1036 | 0 | *aOutRectData = nullptr; |
1037 | 0 | } |
1038 | 0 | } |
1039 | 0 |
|
1040 | 0 | NS_ASSERTION((*aOutRectData == nullptr) != (*aOutMarginsData == nullptr), |
1041 | 0 | "Only one of aOutRectData or aOutMarginsData should be set!"); |
1042 | 0 |
|
1043 | 0 | return true; |
1044 | 0 | } |
1045 | | |
1046 | | bool |
1047 | | nsLayoutUtils::IsMissingDisplayPortBaseRect(nsIContent* aContent) |
1048 | 0 | { |
1049 | 0 | DisplayPortPropertyData* rectData = nullptr; |
1050 | 0 | DisplayPortMarginsPropertyData* marginsData = nullptr; |
1051 | 0 |
|
1052 | 0 | if (GetDisplayPortData(aContent, &rectData, &marginsData) && marginsData) { |
1053 | 0 | return !aContent->GetProperty(nsGkAtoms::DisplayPortBase); |
1054 | 0 | } |
1055 | 0 | |
1056 | 0 | return false; |
1057 | 0 | } |
1058 | | |
1059 | | enum class MaxSizeExceededBehaviour { |
1060 | | // Ask GetDisplayPortImpl to assert if the calculated displayport exceeds |
1061 | | // the maximum allowed size. |
1062 | | eAssert, |
1063 | | // Ask GetDisplayPortImpl to pretend like there's no displayport at all, if |
1064 | | // the calculated displayport exceeds the maximum allowed size. |
1065 | | eDrop, |
1066 | | }; |
1067 | | |
1068 | | static bool |
1069 | | GetDisplayPortImpl(nsIContent* aContent, nsRect* aResult, float aMultiplier, |
1070 | | MaxSizeExceededBehaviour aBehaviour = MaxSizeExceededBehaviour::eAssert) |
1071 | 0 | { |
1072 | 0 | DisplayPortPropertyData* rectData = nullptr; |
1073 | 0 | DisplayPortMarginsPropertyData* marginsData = nullptr; |
1074 | 0 |
|
1075 | 0 | if (!GetDisplayPortData(aContent, &rectData, &marginsData)) { |
1076 | 0 | return false; |
1077 | 0 | } |
1078 | 0 | |
1079 | 0 | if (!aResult) { |
1080 | 0 | // We have displayport data, but the caller doesn't want the actual |
1081 | 0 | // rect, so we don't need to actually compute it. |
1082 | 0 | return true; |
1083 | 0 | } |
1084 | 0 | |
1085 | 0 | bool isDisplayportSuppressed = false; |
1086 | 0 |
|
1087 | 0 | nsIFrame* frame = aContent->GetPrimaryFrame(); |
1088 | 0 | if (frame) { |
1089 | 0 | nsPresContext* presContext = frame->PresContext(); |
1090 | 0 | MOZ_ASSERT(presContext); |
1091 | 0 | nsIPresShell* presShell = presContext->PresShell(); |
1092 | 0 | MOZ_ASSERT(presShell); |
1093 | 0 | isDisplayportSuppressed = presShell->IsDisplayportSuppressed(); |
1094 | 0 | } |
1095 | 0 |
|
1096 | 0 | nsRect result; |
1097 | 0 | if (rectData) { |
1098 | 0 | result = GetDisplayPortFromRectData(aContent, rectData, aMultiplier); |
1099 | 0 | } else if (isDisplayportSuppressed || |
1100 | 0 | nsLayoutUtils::ShouldDisableApzForElement(aContent)) { |
1101 | 0 | DisplayPortMarginsPropertyData noMargins(ScreenMargin(), 1); |
1102 | 0 | result = GetDisplayPortFromMarginsData(aContent, &noMargins, aMultiplier); |
1103 | 0 | } else { |
1104 | 0 | result = GetDisplayPortFromMarginsData(aContent, marginsData, aMultiplier); |
1105 | 0 | } |
1106 | 0 |
|
1107 | 0 | if (!gfxPrefs::LayersTilesEnabled()) { |
1108 | 0 | // Perform the desired error handling if the displayport dimensions |
1109 | 0 | // exceeds the maximum allowed size |
1110 | 0 | nscoord maxSize = GetMaxDisplayPortSize(aContent, nullptr); |
1111 | 0 | if (result.width > maxSize || result.height > maxSize) { |
1112 | 0 | switch (aBehaviour) { |
1113 | 0 | case MaxSizeExceededBehaviour::eAssert: |
1114 | 0 | NS_ASSERTION(false, "Displayport must be a valid texture size"); |
1115 | 0 | break; |
1116 | 0 | case MaxSizeExceededBehaviour::eDrop: |
1117 | 0 | return false; |
1118 | 0 | } |
1119 | 0 | } |
1120 | 0 | } |
1121 | 0 | |
1122 | 0 | *aResult = result; |
1123 | 0 | return true; |
1124 | 0 | } |
1125 | | |
1126 | | static void |
1127 | | TranslateFromScrollPortToScrollFrame(nsIContent* aContent, nsRect* aRect) |
1128 | 0 | { |
1129 | 0 | MOZ_ASSERT(aRect); |
1130 | 0 | nsIFrame* frame = GetScrollFrameFromContent(aContent); |
1131 | 0 | nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr; |
1132 | 0 | if (scrollableFrame) { |
1133 | 0 | *aRect += scrollableFrame->GetScrollPortRect().TopLeft(); |
1134 | 0 | } |
1135 | 0 | } |
1136 | | |
1137 | | bool |
1138 | | nsLayoutUtils::GetDisplayPort(nsIContent* aContent, nsRect *aResult, |
1139 | | RelativeTo aRelativeTo /* = RelativeTo::ScrollPort */) |
1140 | 0 | { |
1141 | 0 | float multiplier = |
1142 | 0 | gfxPrefs::UseLowPrecisionBuffer() ? 1.0f / gfxPrefs::LowPrecisionResolution() : 1.0f; |
1143 | 0 | bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, multiplier); |
1144 | 0 | if (aResult && usingDisplayPort && aRelativeTo == RelativeTo::ScrollFrame) { |
1145 | 0 | TranslateFromScrollPortToScrollFrame(aContent, aResult); |
1146 | 0 | } |
1147 | 0 | return usingDisplayPort; |
1148 | 0 | } |
1149 | | |
1150 | | bool |
1151 | 0 | nsLayoutUtils::HasDisplayPort(nsIContent* aContent) { |
1152 | 0 | return GetDisplayPort(aContent, nullptr); |
1153 | 0 | } |
1154 | | |
1155 | | /* static */ bool |
1156 | | nsLayoutUtils::GetDisplayPortForVisibilityTesting( |
1157 | | nsIContent* aContent, |
1158 | | nsRect* aResult, |
1159 | | RelativeTo aRelativeTo /* = RelativeTo::ScrollPort */) |
1160 | 0 | { |
1161 | 0 | MOZ_ASSERT(aResult); |
1162 | 0 | // Since the base rect might not have been updated very recently, it's |
1163 | 0 | // possible to end up with an extra-large displayport at this point, if the |
1164 | 0 | // zoom level is changed by a lot. Instead of using the default behaviour of |
1165 | 0 | // asserting, we can just ignore the displayport if that happens, as this |
1166 | 0 | // call site is best-effort. |
1167 | 0 | bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, 1.0f, |
1168 | 0 | MaxSizeExceededBehaviour::eDrop); |
1169 | 0 | if (usingDisplayPort && aRelativeTo == RelativeTo::ScrollFrame) { |
1170 | 0 | TranslateFromScrollPortToScrollFrame(aContent, aResult); |
1171 | 0 | } |
1172 | 0 | return usingDisplayPort; |
1173 | 0 | } |
1174 | | |
1175 | | void |
1176 | | nsLayoutUtils::InvalidateForDisplayPortChange(nsIContent* aContent, |
1177 | | bool aHadDisplayPort, |
1178 | | const nsRect& aOldDisplayPort, |
1179 | | const nsRect& aNewDisplayPort, |
1180 | | RepaintMode aRepaintMode) |
1181 | 0 | { |
1182 | 0 | if (aRepaintMode != RepaintMode::Repaint) { |
1183 | 0 | return; |
1184 | 0 | } |
1185 | 0 | |
1186 | 0 | bool changed = !aHadDisplayPort || |
1187 | 0 | !aOldDisplayPort.IsEqualEdges(aNewDisplayPort); |
1188 | 0 |
|
1189 | 0 | nsIFrame* frame = GetScrollFrameFromContent(aContent); |
1190 | 0 | if (frame) { |
1191 | 0 | frame = do_QueryFrame(frame->GetScrollTargetFrame()); |
1192 | 0 | } |
1193 | 0 |
|
1194 | 0 | if (changed && frame) { |
1195 | 0 | // It is important to call SchedulePaint on the same frame that we set the dirty |
1196 | 0 | // rect properties on so we can find the frame later to remove the properties. |
1197 | 0 | frame->SchedulePaint(); |
1198 | 0 |
|
1199 | 0 | if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || |
1200 | 0 | !nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(frame)) { |
1201 | 0 | return; |
1202 | 0 | } |
1203 | 0 | |
1204 | 0 | nsRect* rect = |
1205 | 0 | frame->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); |
1206 | 0 |
|
1207 | 0 | if (!rect) { |
1208 | 0 | rect = new nsRect(); |
1209 | 0 | frame->SetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); |
1210 | 0 | frame->SetHasOverrideDirtyRegion(true); |
1211 | 0 |
|
1212 | 0 | nsIFrame* rootFrame = frame->PresShell()->GetRootFrame(); |
1213 | 0 | MOZ_ASSERT(rootFrame); |
1214 | 0 |
|
1215 | 0 | RetainedDisplayListData* data = |
1216 | 0 | GetOrSetRetainedDisplayListData(rootFrame); |
1217 | 0 | data->Flags(frame) |= RetainedDisplayListData::FrameFlags::HasProps; |
1218 | 0 | } |
1219 | 0 |
|
1220 | 0 | if (aHadDisplayPort) { |
1221 | 0 | // We only need to build a display list for any new areas added |
1222 | 0 | nsRegion newRegion(aNewDisplayPort); |
1223 | 0 | newRegion.SubOut(aOldDisplayPort); |
1224 | 0 | rect->UnionRect(*rect, newRegion.GetBounds()); |
1225 | 0 | } else { |
1226 | 0 | rect->UnionRect(*rect, aNewDisplayPort); |
1227 | 0 | } |
1228 | 0 | } |
1229 | 0 | } |
1230 | | |
1231 | | bool |
1232 | | nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent, |
1233 | | nsIPresShell* aPresShell, |
1234 | | const ScreenMargin& aMargins, |
1235 | | uint32_t aPriority, |
1236 | | RepaintMode aRepaintMode) |
1237 | 0 | { |
1238 | 0 | MOZ_ASSERT(aContent); |
1239 | 0 | MOZ_ASSERT(aContent->GetComposedDoc() == aPresShell->GetDocument()); |
1240 | 0 |
|
1241 | 0 | DisplayPortMarginsPropertyData* currentData = |
1242 | 0 | static_cast<DisplayPortMarginsPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); |
1243 | 0 | if (currentData && currentData->mPriority > aPriority) { |
1244 | 0 | return false; |
1245 | 0 | } |
1246 | 0 | |
1247 | 0 | nsRect oldDisplayPort; |
1248 | 0 | bool hadDisplayPort = GetHighResolutionDisplayPort(aContent, &oldDisplayPort); |
1249 | 0 |
|
1250 | 0 | aContent->SetProperty(nsGkAtoms::DisplayPortMargins, |
1251 | 0 | new DisplayPortMarginsPropertyData( |
1252 | 0 | aMargins, aPriority), |
1253 | 0 | nsINode::DeleteProperty<DisplayPortMarginsPropertyData>); |
1254 | 0 |
|
1255 | 0 | nsRect newDisplayPort; |
1256 | 0 | DebugOnly<bool> hasDisplayPort = GetHighResolutionDisplayPort(aContent, &newDisplayPort); |
1257 | 0 | MOZ_ASSERT(hasDisplayPort); |
1258 | 0 |
|
1259 | 0 | if (gfxPrefs::LayoutUseContainersForRootFrames()) { |
1260 | 0 | nsIFrame* rootScrollFrame = aPresShell->GetRootScrollFrame(); |
1261 | 0 | if (rootScrollFrame && |
1262 | 0 | aContent == rootScrollFrame->GetContent() && |
1263 | 0 | nsLayoutUtils::UsesAsyncScrolling(rootScrollFrame)) |
1264 | 0 | { |
1265 | 0 | // We are setting a root displayport for a document. |
1266 | 0 | // If we have APZ, then set a special flag on the pres shell so |
1267 | 0 | // that we don't get scrollbars drawn. |
1268 | 0 | aPresShell->SetIgnoreViewportScrolling(true); |
1269 | 0 | } |
1270 | 0 | } |
1271 | 0 |
|
1272 | 0 | InvalidateForDisplayPortChange(aContent, hadDisplayPort, oldDisplayPort, |
1273 | 0 | newDisplayPort, aRepaintMode); |
1274 | 0 |
|
1275 | 0 | nsIFrame* frame = GetScrollFrameFromContent(aContent); |
1276 | 0 | nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr; |
1277 | 0 | if (!scrollableFrame) { |
1278 | 0 | return true; |
1279 | 0 | } |
1280 | 0 | |
1281 | 0 | scrollableFrame->TriggerDisplayPortExpiration(); |
1282 | 0 |
|
1283 | 0 | // Display port margins changing means that the set of visible frames may |
1284 | 0 | // have drastically changed. Check if we should schedule an update. |
1285 | 0 | hadDisplayPort = |
1286 | 0 | scrollableFrame->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(&oldDisplayPort); |
1287 | 0 |
|
1288 | 0 | bool needVisibilityUpdate = !hadDisplayPort; |
1289 | 0 | // Check if the total size has changed by a large factor. |
1290 | 0 | if (!needVisibilityUpdate) { |
1291 | 0 | if ((newDisplayPort.width > 2 * oldDisplayPort.width) || |
1292 | 0 | (oldDisplayPort.width > 2 * newDisplayPort.width) || |
1293 | 0 | (newDisplayPort.height > 2 * oldDisplayPort.height) || |
1294 | 0 | (oldDisplayPort.height > 2 * newDisplayPort.height)) { |
1295 | 0 | needVisibilityUpdate = true; |
1296 | 0 | } |
1297 | 0 | } |
1298 | 0 | // Check if it's moved by a significant amount. |
1299 | 0 | if (!needVisibilityUpdate) { |
1300 | 0 | if (nsRect* baseData = static_cast<nsRect*>(aContent->GetProperty(nsGkAtoms::DisplayPortBase))) { |
1301 | 0 | nsRect base = *baseData; |
1302 | 0 | if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) || |
1303 | 0 | (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) > base.width) || |
1304 | 0 | (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) || |
1305 | 0 | (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) > base.height)) { |
1306 | 0 | needVisibilityUpdate = true; |
1307 | 0 | } |
1308 | 0 | } |
1309 | 0 | } |
1310 | 0 | if (needVisibilityUpdate) { |
1311 | 0 | aPresShell->ScheduleApproximateFrameVisibilityUpdateNow(); |
1312 | 0 | } |
1313 | 0 |
|
1314 | 0 | return true; |
1315 | 0 | } |
1316 | | |
1317 | | void |
1318 | | nsLayoutUtils::SetDisplayPortBase(nsIContent* aContent, const nsRect& aBase) |
1319 | 0 | { |
1320 | 0 | aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase), |
1321 | 0 | nsINode::DeleteProperty<nsRect>); |
1322 | 0 | } |
1323 | | |
1324 | | void |
1325 | | nsLayoutUtils::SetDisplayPortBaseIfNotSet(nsIContent* aContent, const nsRect& aBase) |
1326 | 0 | { |
1327 | 0 | if (!aContent->GetProperty(nsGkAtoms::DisplayPortBase)) { |
1328 | 0 | SetDisplayPortBase(aContent, aBase); |
1329 | 0 | } |
1330 | 0 | } |
1331 | | |
1332 | | bool |
1333 | | nsLayoutUtils::GetCriticalDisplayPort(nsIContent* aContent, nsRect* aResult) |
1334 | 0 | { |
1335 | 0 | if (gfxPrefs::UseLowPrecisionBuffer()) { |
1336 | 0 | return GetDisplayPortImpl(aContent, aResult, 1.0f); |
1337 | 0 | } |
1338 | 0 | return false; |
1339 | 0 | } |
1340 | | |
1341 | | bool |
1342 | | nsLayoutUtils::HasCriticalDisplayPort(nsIContent* aContent) |
1343 | 0 | { |
1344 | 0 | return GetCriticalDisplayPort(aContent, nullptr); |
1345 | 0 | } |
1346 | | |
1347 | | bool |
1348 | | nsLayoutUtils::GetHighResolutionDisplayPort(nsIContent* aContent, nsRect* aResult) |
1349 | 0 | { |
1350 | 0 | if (gfxPrefs::UseLowPrecisionBuffer()) { |
1351 | 0 | return GetCriticalDisplayPort(aContent, aResult); |
1352 | 0 | } |
1353 | 0 | return GetDisplayPort(aContent, aResult); |
1354 | 0 | } |
1355 | | |
1356 | | void |
1357 | | nsLayoutUtils::RemoveDisplayPort(nsIContent* aContent) |
1358 | 0 | { |
1359 | 0 | aContent->DeleteProperty(nsGkAtoms::DisplayPort); |
1360 | 0 | aContent->DeleteProperty(nsGkAtoms::DisplayPortMargins); |
1361 | 0 | } |
1362 | | |
1363 | | nsContainerFrame* |
1364 | | nsLayoutUtils::LastContinuationWithChild(nsContainerFrame* aFrame) |
1365 | 0 | { |
1366 | 0 | MOZ_ASSERT(aFrame, "NULL frame pointer"); |
1367 | 0 | for (auto f = aFrame->LastContinuation(); f; f = f->GetPrevContinuation()) { |
1368 | 0 | for (nsIFrame::ChildListIterator lists(f); !lists.IsDone(); lists.Next()) { |
1369 | 0 | if (MOZ_LIKELY(!lists.CurrentList().IsEmpty())) { |
1370 | 0 | return static_cast<nsContainerFrame*>(f); |
1371 | 0 | } |
1372 | 0 | } |
1373 | 0 | } |
1374 | 0 | return aFrame; |
1375 | 0 | } |
1376 | | |
1377 | | //static |
1378 | | FrameChildListID |
1379 | | nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame) |
1380 | 0 | { |
1381 | 0 | nsIFrame::ChildListID id = nsIFrame::kPrincipalList; |
1382 | 0 |
|
1383 | 0 | MOZ_DIAGNOSTIC_ASSERT(!(aChildFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)); |
1384 | 0 |
|
1385 | 0 | if (aChildFrame->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) { |
1386 | 0 | nsIFrame* pif = aChildFrame->GetPrevInFlow(); |
1387 | 0 | if (pif->GetParent() == aChildFrame->GetParent()) { |
1388 | 0 | id = nsIFrame::kExcessOverflowContainersList; |
1389 | 0 | } |
1390 | 0 | else { |
1391 | 0 | id = nsIFrame::kOverflowContainersList; |
1392 | 0 | } |
1393 | 0 | } else { |
1394 | 0 | LayoutFrameType childType = aChildFrame->Type(); |
1395 | 0 | if (LayoutFrameType::MenuPopup == childType) { |
1396 | 0 | nsIFrame* parent = aChildFrame->GetParent(); |
1397 | 0 | MOZ_ASSERT(parent, "nsMenuPopupFrame can't be the root frame"); |
1398 | 0 | if (parent) { |
1399 | 0 | if (parent->IsPopupSetFrame()) { |
1400 | 0 | id = nsIFrame::kPopupList; |
1401 | 0 | } else { |
1402 | 0 | nsIFrame* firstPopup = parent->GetChildList(nsIFrame::kPopupList).FirstChild(); |
1403 | 0 | MOZ_ASSERT(!firstPopup || !firstPopup->GetNextSibling(), |
1404 | 0 | "We assume popupList only has one child, but it has more."); |
1405 | 0 | id = firstPopup == aChildFrame |
1406 | 0 | ? nsIFrame::kPopupList |
1407 | 0 | : nsIFrame::kPrincipalList; |
1408 | 0 | } |
1409 | 0 | } else { |
1410 | 0 | id = nsIFrame::kPrincipalList; |
1411 | 0 | } |
1412 | 0 | } else if (LayoutFrameType::TableColGroup == childType) { |
1413 | 0 | id = nsIFrame::kColGroupList; |
1414 | 0 | } else if (aChildFrame->IsTableCaption()) { |
1415 | 0 | id = nsIFrame::kCaptionList; |
1416 | 0 | } else { |
1417 | 0 | id = nsIFrame::kPrincipalList; |
1418 | 0 | } |
1419 | 0 | } |
1420 | 0 |
|
1421 | | #ifdef DEBUG |
1422 | | // Verify that the frame is actually in that child list or in the |
1423 | | // corresponding overflow list. |
1424 | | nsContainerFrame* parent = aChildFrame->GetParent(); |
1425 | | bool found = parent->GetChildList(id).ContainsFrame(aChildFrame); |
1426 | | if (!found) { |
1427 | | found = parent->GetChildList(nsIFrame::kOverflowList) |
1428 | | .ContainsFrame(aChildFrame); |
1429 | | MOZ_ASSERT(found, "not in child list"); |
1430 | | } |
1431 | | #endif |
1432 | |
|
1433 | 0 | return id; |
1434 | 0 | } |
1435 | | |
1436 | | static Element* |
1437 | | GetPseudo(const nsIContent* aContent, nsAtom* aPseudoProperty) |
1438 | 0 | { |
1439 | 0 | MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty || |
1440 | 0 | aPseudoProperty == nsGkAtoms::afterPseudoProperty); |
1441 | 0 | if (!aContent->MayHaveAnonymousChildren()) { |
1442 | 0 | return nullptr; |
1443 | 0 | } |
1444 | 0 | return static_cast<Element*>(aContent->GetProperty(aPseudoProperty)); |
1445 | 0 | } |
1446 | | |
1447 | | /*static*/ Element* |
1448 | | nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent) |
1449 | 0 | { |
1450 | 0 | return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty); |
1451 | 0 | } |
1452 | | |
1453 | | /*static*/ nsIFrame* |
1454 | | nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent) |
1455 | 0 | { |
1456 | 0 | Element* pseudo = GetBeforePseudo(aContent); |
1457 | 0 | return pseudo ? pseudo->GetPrimaryFrame() : nullptr; |
1458 | 0 | } |
1459 | | |
1460 | | /*static*/ Element* |
1461 | | nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent) |
1462 | 0 | { |
1463 | 0 | return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty); |
1464 | 0 | } |
1465 | | |
1466 | | /*static*/ nsIFrame* |
1467 | | nsLayoutUtils::GetAfterFrame(const nsIContent* aContent) |
1468 | 0 | { |
1469 | 0 | Element* pseudo = GetAfterPseudo(aContent); |
1470 | 0 | return pseudo ? pseudo->GetPrimaryFrame() : nullptr; |
1471 | 0 | } |
1472 | | |
1473 | | // static |
1474 | | nsIFrame* |
1475 | | nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, |
1476 | | LayoutFrameType aFrameType, |
1477 | | nsIFrame* aStopAt) |
1478 | 0 | { |
1479 | 0 | for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { |
1480 | 0 | if (frame->Type() == aFrameType) { |
1481 | 0 | return frame; |
1482 | 0 | } |
1483 | 0 | if (frame == aStopAt) { |
1484 | 0 | break; |
1485 | 0 | } |
1486 | 0 | } |
1487 | 0 | return nullptr; |
1488 | 0 | } |
1489 | | |
1490 | | /* static */ nsIFrame* |
1491 | | nsLayoutUtils::GetPageFrame(nsIFrame* aFrame) |
1492 | 0 | { |
1493 | 0 | return GetClosestFrameOfType(aFrame, LayoutFrameType::Page); |
1494 | 0 | } |
1495 | | |
1496 | | // static |
1497 | | nsIFrame* |
1498 | | nsLayoutUtils::GetStyleFrame(nsIFrame* aFrame) |
1499 | 0 | { |
1500 | 0 | if (aFrame->IsTableWrapperFrame()) { |
1501 | 0 | nsIFrame* inner = aFrame->PrincipalChildList().FirstChild(); |
1502 | 0 | // inner may be null, if aFrame is mid-destruction |
1503 | 0 | return inner; |
1504 | 0 | } |
1505 | 0 | |
1506 | 0 | return aFrame; |
1507 | 0 | } |
1508 | | |
1509 | | nsIFrame* |
1510 | | nsLayoutUtils::GetStyleFrame(const nsIContent* aContent) |
1511 | 0 | { |
1512 | 0 | nsIFrame *frame = aContent->GetPrimaryFrame(); |
1513 | 0 | if (!frame) { |
1514 | 0 | return nullptr; |
1515 | 0 | } |
1516 | 0 | |
1517 | 0 | return nsLayoutUtils::GetStyleFrame(frame); |
1518 | 0 | } |
1519 | | |
1520 | | /* static */ nsIFrame* |
1521 | | nsLayoutUtils::GetRealPrimaryFrameFor(const nsIContent* aContent) |
1522 | 0 | { |
1523 | 0 | nsIFrame *frame = aContent->GetPrimaryFrame(); |
1524 | 0 | if (!frame) { |
1525 | 0 | return nullptr; |
1526 | 0 | } |
1527 | 0 | |
1528 | 0 | return nsPlaceholderFrame::GetRealFrameFor(frame); |
1529 | 0 | } |
1530 | | |
1531 | | nsIFrame* |
1532 | 0 | nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) { |
1533 | 0 | NS_ASSERTION(aFrame->IsPlaceholderFrame(), "Must have a placeholder here"); |
1534 | 0 | if (aFrame->GetStateBits() & PLACEHOLDER_FOR_FLOAT) { |
1535 | 0 | nsIFrame *outOfFlowFrame = |
1536 | 0 | nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame); |
1537 | 0 | NS_ASSERTION(outOfFlowFrame && outOfFlowFrame->IsFloating(), |
1538 | 0 | "How did that happen?"); |
1539 | 0 | return outOfFlowFrame; |
1540 | 0 | } |
1541 | 0 |
|
1542 | 0 | return nullptr; |
1543 | 0 | } |
1544 | | |
1545 | | // static |
1546 | | nsIFrame* |
1547 | | nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame, |
1548 | | nsPoint* aExtraOffset) |
1549 | 0 | { |
1550 | 0 | nsIFrame* p = aFrame->GetParent(); |
1551 | 0 | if (p) |
1552 | 0 | return p; |
1553 | 0 | |
1554 | 0 | nsView* v = aFrame->GetView(); |
1555 | 0 | if (!v) |
1556 | 0 | return nullptr; |
1557 | 0 | v = v->GetParent(); // anonymous inner view |
1558 | 0 | if (!v) |
1559 | 0 | return nullptr; |
1560 | 0 | if (aExtraOffset) { |
1561 | 0 | *aExtraOffset += v->GetPosition(); |
1562 | 0 | } |
1563 | 0 | v = v->GetParent(); // subdocumentframe's view |
1564 | 0 | return v ? v->GetFrame() : nullptr; |
1565 | 0 | } |
1566 | | |
1567 | | // static |
1568 | | bool |
1569 | | nsLayoutUtils::IsProperAncestorFrameCrossDoc(nsIFrame* aAncestorFrame, nsIFrame* aFrame, |
1570 | | nsIFrame* aCommonAncestor) |
1571 | 0 | { |
1572 | 0 | if (aFrame == aAncestorFrame) |
1573 | 0 | return false; |
1574 | 0 | return IsAncestorFrameCrossDoc(aAncestorFrame, aFrame, aCommonAncestor); |
1575 | 0 | } |
1576 | | |
1577 | | // static |
1578 | | bool |
1579 | | nsLayoutUtils::IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame, const nsIFrame* aFrame, |
1580 | | const nsIFrame* aCommonAncestor) |
1581 | 0 | { |
1582 | 0 | for (const nsIFrame* f = aFrame; f != aCommonAncestor; |
1583 | 0 | f = GetCrossDocParentFrame(f)) { |
1584 | 0 | if (f == aAncestorFrame) |
1585 | 0 | return true; |
1586 | 0 | } |
1587 | 0 | return aCommonAncestor == aAncestorFrame; |
1588 | 0 | } |
1589 | | |
1590 | | // static |
1591 | | bool |
1592 | | nsLayoutUtils::IsProperAncestorFrame(nsIFrame* aAncestorFrame, nsIFrame* aFrame, |
1593 | | nsIFrame* aCommonAncestor) |
1594 | 0 | { |
1595 | 0 | if (aFrame == aAncestorFrame) |
1596 | 0 | return false; |
1597 | 0 | for (nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) { |
1598 | 0 | if (f == aAncestorFrame) |
1599 | 0 | return true; |
1600 | 0 | } |
1601 | 0 | return aCommonAncestor == aAncestorFrame; |
1602 | 0 | } |
1603 | | |
1604 | | // static |
1605 | | int32_t |
1606 | | nsLayoutUtils::DoCompareTreePosition(nsIContent* aContent1, |
1607 | | nsIContent* aContent2, |
1608 | | int32_t aIf1Ancestor, |
1609 | | int32_t aIf2Ancestor, |
1610 | | const nsIContent* aCommonAncestor) |
1611 | 0 | { |
1612 | 0 | MOZ_ASSERT(aContent1, "aContent1 must not be null"); |
1613 | 0 | MOZ_ASSERT(aContent2, "aContent2 must not be null"); |
1614 | 0 |
|
1615 | 0 | AutoTArray<nsINode*, 32> content1Ancestors; |
1616 | 0 | nsINode* c1; |
1617 | 0 | for (c1 = aContent1; |
1618 | 0 | c1 && c1 != aCommonAncestor; |
1619 | 0 | c1 = c1->GetParentOrHostNode()) { |
1620 | 0 | content1Ancestors.AppendElement(c1); |
1621 | 0 | } |
1622 | 0 | if (!c1 && aCommonAncestor) { |
1623 | 0 | // So, it turns out aCommonAncestor was not an ancestor of c1. Oops. |
1624 | 0 | // Never mind. We can continue as if aCommonAncestor was null. |
1625 | 0 | aCommonAncestor = nullptr; |
1626 | 0 | } |
1627 | 0 |
|
1628 | 0 | AutoTArray<nsINode*, 32> content2Ancestors; |
1629 | 0 | nsINode* c2; |
1630 | 0 | for (c2 = aContent2; |
1631 | 0 | c2 && c2 != aCommonAncestor; |
1632 | 0 | c2 = c2->GetParentOrHostNode()) { |
1633 | 0 | content2Ancestors.AppendElement(c2); |
1634 | 0 | } |
1635 | 0 | if (!c2 && aCommonAncestor) { |
1636 | 0 | // So, it turns out aCommonAncestor was not an ancestor of c2. |
1637 | 0 | // We need to retry with no common ancestor hint. |
1638 | 0 | return DoCompareTreePosition(aContent1, aContent2, |
1639 | 0 | aIf1Ancestor, aIf2Ancestor, nullptr); |
1640 | 0 | } |
1641 | 0 | |
1642 | 0 | int last1 = content1Ancestors.Length() - 1; |
1643 | 0 | int last2 = content2Ancestors.Length() - 1; |
1644 | 0 | nsINode* content1Ancestor = nullptr; |
1645 | 0 | nsINode* content2Ancestor = nullptr; |
1646 | 0 | while (last1 >= 0 && last2 >= 0 |
1647 | 0 | && ((content1Ancestor = content1Ancestors.ElementAt(last1)) == |
1648 | 0 | (content2Ancestor = content2Ancestors.ElementAt(last2)))) { |
1649 | 0 | last1--; |
1650 | 0 | last2--; |
1651 | 0 | } |
1652 | 0 |
|
1653 | 0 | if (last1 < 0) { |
1654 | 0 | if (last2 < 0) { |
1655 | 0 | NS_ASSERTION(aContent1 == aContent2, "internal error?"); |
1656 | 0 | return 0; |
1657 | 0 | } |
1658 | 0 | // aContent1 is an ancestor of aContent2 |
1659 | 0 | return aIf1Ancestor; |
1660 | 0 | } |
1661 | 0 | |
1662 | 0 | if (last2 < 0) { |
1663 | 0 | // aContent2 is an ancestor of aContent1 |
1664 | 0 | return aIf2Ancestor; |
1665 | 0 | } |
1666 | 0 | |
1667 | 0 | // content1Ancestor != content2Ancestor, so they must be siblings with the same parent |
1668 | 0 | nsINode* parent = content1Ancestor->GetParentOrHostNode(); |
1669 | | #ifdef DEBUG |
1670 | | // TODO: remove the uglyness, see bug 598468. |
1671 | | NS_ASSERTION(gPreventAssertInCompareTreePosition || parent, |
1672 | | "no common ancestor at all???"); |
1673 | | #endif // DEBUG |
1674 | 0 | if (!parent) { // different documents?? |
1675 | 0 | return 0; |
1676 | 0 | } |
1677 | 0 | |
1678 | 0 | int32_t index1 = parent->ComputeIndexOf(content1Ancestor); |
1679 | 0 | int32_t index2 = parent->ComputeIndexOf(content2Ancestor); |
1680 | 0 | if (index1 < 0 || index2 < 0) { |
1681 | 0 | // one of them must be anonymous; we can't determine the order |
1682 | 0 | return 0; |
1683 | 0 | } |
1684 | 0 | |
1685 | 0 | return index1 - index2; |
1686 | 0 | } |
1687 | | |
1688 | | // static |
1689 | | nsIFrame* |
1690 | | nsLayoutUtils::FillAncestors(nsIFrame* aFrame, |
1691 | | nsIFrame* aStopAtAncestor, |
1692 | | nsTArray<nsIFrame*>* aAncestors) |
1693 | 0 | { |
1694 | 0 | while (aFrame && aFrame != aStopAtAncestor) { |
1695 | 0 | aAncestors->AppendElement(aFrame); |
1696 | 0 | aFrame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame); |
1697 | 0 | } |
1698 | 0 | return aFrame; |
1699 | 0 | } |
1700 | | |
1701 | | // Return true if aFrame1 is after aFrame2 |
1702 | | static bool IsFrameAfter(nsIFrame* aFrame1, nsIFrame* aFrame2) |
1703 | 0 | { |
1704 | 0 | nsIFrame* f = aFrame2; |
1705 | 0 | do { |
1706 | 0 | f = f->GetNextSibling(); |
1707 | 0 | if (f == aFrame1) |
1708 | 0 | return true; |
1709 | 0 | } while (f); |
1710 | 0 | return false; |
1711 | 0 | } |
1712 | | |
1713 | | // static |
1714 | | int32_t |
1715 | | nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1, |
1716 | | nsIFrame* aFrame2, |
1717 | | int32_t aIf1Ancestor, |
1718 | | int32_t aIf2Ancestor, |
1719 | | nsIFrame* aCommonAncestor) |
1720 | 0 | { |
1721 | 0 | MOZ_ASSERT(aFrame1, "aFrame1 must not be null"); |
1722 | 0 | MOZ_ASSERT(aFrame2, "aFrame2 must not be null"); |
1723 | 0 |
|
1724 | 0 | AutoTArray<nsIFrame*,20> frame2Ancestors; |
1725 | 0 | nsIFrame* nonCommonAncestor = |
1726 | 0 | FillAncestors(aFrame2, aCommonAncestor, &frame2Ancestors); |
1727 | 0 |
|
1728 | 0 | return DoCompareTreePosition(aFrame1, aFrame2, frame2Ancestors, |
1729 | 0 | aIf1Ancestor, aIf2Ancestor, |
1730 | 0 | nonCommonAncestor ? aCommonAncestor : nullptr); |
1731 | 0 | } |
1732 | | |
1733 | | // static |
1734 | | int32_t |
1735 | | nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1, |
1736 | | nsIFrame* aFrame2, |
1737 | | nsTArray<nsIFrame*>& aFrame2Ancestors, |
1738 | | int32_t aIf1Ancestor, |
1739 | | int32_t aIf2Ancestor, |
1740 | | nsIFrame* aCommonAncestor) |
1741 | 0 | { |
1742 | 0 | MOZ_ASSERT(aFrame1, "aFrame1 must not be null"); |
1743 | 0 | MOZ_ASSERT(aFrame2, "aFrame2 must not be null"); |
1744 | 0 |
|
1745 | 0 | nsPresContext* presContext = aFrame1->PresContext(); |
1746 | 0 | if (presContext != aFrame2->PresContext()) { |
1747 | 0 | NS_ERROR("no common ancestor at all, different documents"); |
1748 | 0 | return 0; |
1749 | 0 | } |
1750 | 0 |
|
1751 | 0 | AutoTArray<nsIFrame*,20> frame1Ancestors; |
1752 | 0 | if (aCommonAncestor && |
1753 | 0 | !FillAncestors(aFrame1, aCommonAncestor, &frame1Ancestors)) { |
1754 | 0 | // We reached the root of the frame tree ... if aCommonAncestor was set, |
1755 | 0 | // it is wrong |
1756 | 0 | return DoCompareTreePosition(aFrame1, aFrame2, |
1757 | 0 | aIf1Ancestor, aIf2Ancestor, nullptr); |
1758 | 0 | } |
1759 | 0 | |
1760 | 0 | int32_t last1 = int32_t(frame1Ancestors.Length()) - 1; |
1761 | 0 | int32_t last2 = int32_t(aFrame2Ancestors.Length()) - 1; |
1762 | 0 | while (last1 >= 0 && last2 >= 0 && |
1763 | 0 | frame1Ancestors[last1] == aFrame2Ancestors[last2]) { |
1764 | 0 | last1--; |
1765 | 0 | last2--; |
1766 | 0 | } |
1767 | 0 |
|
1768 | 0 | if (last1 < 0) { |
1769 | 0 | if (last2 < 0) { |
1770 | 0 | NS_ASSERTION(aFrame1 == aFrame2, "internal error?"); |
1771 | 0 | return 0; |
1772 | 0 | } |
1773 | 0 | // aFrame1 is an ancestor of aFrame2 |
1774 | 0 | return aIf1Ancestor; |
1775 | 0 | } |
1776 | 0 | |
1777 | 0 | if (last2 < 0) { |
1778 | 0 | // aFrame2 is an ancestor of aFrame1 |
1779 | 0 | return aIf2Ancestor; |
1780 | 0 | } |
1781 | 0 | |
1782 | 0 | nsIFrame* ancestor1 = frame1Ancestors[last1]; |
1783 | 0 | nsIFrame* ancestor2 = aFrame2Ancestors[last2]; |
1784 | 0 | // Now we should be able to walk sibling chains to find which one is first |
1785 | 0 | if (IsFrameAfter(ancestor2, ancestor1)) |
1786 | 0 | return -1; |
1787 | 0 | if (IsFrameAfter(ancestor1, ancestor2)) |
1788 | 0 | return 1; |
1789 | 0 | NS_WARNING("Frames were in different child lists???"); |
1790 | 0 | return 0; |
1791 | 0 | } |
1792 | | |
1793 | | // static |
1794 | 0 | nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) { |
1795 | 0 | if (!aFrame) { |
1796 | 0 | return nullptr; |
1797 | 0 | } |
1798 | 0 | |
1799 | 0 | nsIFrame* next; |
1800 | 0 | while ((next = aFrame->GetNextSibling()) != nullptr) { |
1801 | 0 | aFrame = next; |
1802 | 0 | } |
1803 | 0 | return aFrame; |
1804 | 0 | } |
1805 | | |
1806 | | // static |
1807 | | nsView* |
1808 | 0 | nsLayoutUtils::FindSiblingViewFor(nsView* aParentView, nsIFrame* aFrame) { |
1809 | 0 | nsIFrame* parentViewFrame = aParentView->GetFrame(); |
1810 | 0 | nsIContent* parentViewContent = parentViewFrame ? parentViewFrame->GetContent() : nullptr; |
1811 | 0 | for (nsView* insertBefore = aParentView->GetFirstChild(); insertBefore; |
1812 | 0 | insertBefore = insertBefore->GetNextSibling()) { |
1813 | 0 | nsIFrame* f = insertBefore->GetFrame(); |
1814 | 0 | if (!f) { |
1815 | 0 | // this view could be some anonymous view attached to a meaningful parent |
1816 | 0 | for (nsView* searchView = insertBefore->GetParent(); searchView; |
1817 | 0 | searchView = searchView->GetParent()) { |
1818 | 0 | f = searchView->GetFrame(); |
1819 | 0 | if (f) { |
1820 | 0 | break; |
1821 | 0 | } |
1822 | 0 | } |
1823 | 0 | NS_ASSERTION(f, "Can't find a frame anywhere!"); |
1824 | 0 | } |
1825 | 0 | if (!f || !aFrame->GetContent() || !f->GetContent() || |
1826 | 0 | CompareTreePosition(aFrame->GetContent(), f->GetContent(), parentViewContent) > 0) { |
1827 | 0 | // aFrame's content is after f's content (or we just don't know), |
1828 | 0 | // so put our view before f's view |
1829 | 0 | return insertBefore; |
1830 | 0 | } |
1831 | 0 | } |
1832 | 0 | return nullptr; |
1833 | 0 | } |
1834 | | |
1835 | | //static |
1836 | | nsIScrollableFrame* |
1837 | | nsLayoutUtils::GetScrollableFrameFor(const nsIFrame *aScrolledFrame) |
1838 | 0 | { |
1839 | 0 | nsIFrame *frame = aScrolledFrame->GetParent(); |
1840 | 0 | nsIScrollableFrame *sf = do_QueryFrame(frame); |
1841 | 0 | return (sf && sf->GetScrolledFrame() == aScrolledFrame) ? sf : nullptr; |
1842 | 0 | } |
1843 | | |
1844 | | /* static */ void |
1845 | | nsLayoutUtils::SetFixedPositionLayerData(Layer* aLayer, |
1846 | | const nsIFrame* aViewportFrame, |
1847 | | const nsRect& aAnchorRect, |
1848 | | const nsIFrame* aFixedPosFrame, |
1849 | | nsPresContext* aPresContext, |
1850 | 0 | const ContainerLayerParameters& aContainerParameters) { |
1851 | 0 | // Find out the rect of the viewport frame relative to the reference frame. |
1852 | 0 | // This, in conjunction with the container scale, will correspond to the |
1853 | 0 | // coordinate-space of the built layer. |
1854 | 0 | float factor = aPresContext->AppUnitsPerDevPixel(); |
1855 | 0 | Rect anchorRect(NSAppUnitsToFloatPixels(aAnchorRect.x, factor) * |
1856 | 0 | aContainerParameters.mXScale, |
1857 | 0 | NSAppUnitsToFloatPixels(aAnchorRect.y, factor) * |
1858 | 0 | aContainerParameters.mYScale, |
1859 | 0 | NSAppUnitsToFloatPixels(aAnchorRect.width, factor) * |
1860 | 0 | aContainerParameters.mXScale, |
1861 | 0 | NSAppUnitsToFloatPixels(aAnchorRect.height, factor) * |
1862 | 0 | aContainerParameters.mYScale); |
1863 | 0 | // Need to transform anchorRect from the container layer's coordinate system |
1864 | 0 | // into aLayer's coordinate system. |
1865 | 0 | Matrix transform2d; |
1866 | 0 | if (aLayer->GetTransform().Is2D(&transform2d)) { |
1867 | 0 | transform2d.Invert(); |
1868 | 0 | anchorRect = transform2d.TransformBounds(anchorRect); |
1869 | 0 | } else { |
1870 | 0 | NS_ERROR("3D transform found between fixedpos content and its viewport (should never happen)"); |
1871 | 0 | anchorRect = Rect(0,0,0,0); |
1872 | 0 | } |
1873 | 0 |
|
1874 | 0 | // Work out the anchor point for this fixed position layer. We assume that |
1875 | 0 | // any positioning set (left/top/right/bottom) indicates that the |
1876 | 0 | // corresponding side of its container should be the anchor point, |
1877 | 0 | // defaulting to top-left. |
1878 | 0 | LayerPoint anchor(anchorRect.x, anchorRect.y); |
1879 | 0 |
|
1880 | 0 | int32_t sides = eSideBitsNone; |
1881 | 0 | if (aFixedPosFrame != aViewportFrame) { |
1882 | 0 | const nsStylePosition* position = aFixedPosFrame->StylePosition(); |
1883 | 0 | if (position->mOffset.GetRightUnit() != eStyleUnit_Auto) { |
1884 | 0 | sides |= eSideBitsRight; |
1885 | 0 | if (position->mOffset.GetLeftUnit() != eStyleUnit_Auto) { |
1886 | 0 | sides |= eSideBitsLeft; |
1887 | 0 | anchor.x = anchorRect.x + anchorRect.width / 2.f; |
1888 | 0 | } else { |
1889 | 0 | anchor.x = anchorRect.XMost(); |
1890 | 0 | } |
1891 | 0 | } else if (position->mOffset.GetLeftUnit() != eStyleUnit_Auto) { |
1892 | 0 | sides |= eSideBitsLeft; |
1893 | 0 | } |
1894 | 0 | if (position->mOffset.GetBottomUnit() != eStyleUnit_Auto) { |
1895 | 0 | sides |= eSideBitsBottom; |
1896 | 0 | if (position->mOffset.GetTopUnit() != eStyleUnit_Auto) { |
1897 | 0 | sides |= eSideBitsTop; |
1898 | 0 | anchor.y = anchorRect.y + anchorRect.height / 2.f; |
1899 | 0 | } else { |
1900 | 0 | anchor.y = anchorRect.YMost(); |
1901 | 0 | } |
1902 | 0 | } else if (position->mOffset.GetTopUnit() != eStyleUnit_Auto) { |
1903 | 0 | sides |= eSideBitsTop; |
1904 | 0 | } |
1905 | 0 | } |
1906 | 0 |
|
1907 | 0 | ViewID id = ScrollIdForRootScrollFrame(aPresContext); |
1908 | 0 | aLayer->SetFixedPositionData(id, anchor, sides); |
1909 | 0 | } |
1910 | | |
1911 | | FrameMetrics::ViewID |
1912 | | nsLayoutUtils::ScrollIdForRootScrollFrame(nsPresContext* aPresContext) |
1913 | 0 | { |
1914 | 0 | ViewID id = FrameMetrics::NULL_SCROLL_ID; |
1915 | 0 | if (nsIFrame* rootScrollFrame = aPresContext->PresShell()->GetRootScrollFrame()) { |
1916 | 0 | if (nsIContent* content = rootScrollFrame->GetContent()) { |
1917 | 0 | id = FindOrCreateIDFor(content); |
1918 | 0 | } |
1919 | 0 | } |
1920 | 0 | return id; |
1921 | 0 | } |
1922 | | |
1923 | | bool |
1924 | | nsLayoutUtils::ViewportHasDisplayPort(nsPresContext* aPresContext) |
1925 | 0 | { |
1926 | 0 | nsIFrame* rootScrollFrame = |
1927 | 0 | aPresContext->PresShell()->GetRootScrollFrame(); |
1928 | 0 | return rootScrollFrame && |
1929 | 0 | nsLayoutUtils::HasDisplayPort(rootScrollFrame->GetContent()); |
1930 | 0 | } |
1931 | | |
1932 | | bool |
1933 | | nsLayoutUtils::IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame) |
1934 | 0 | { |
1935 | 0 | // Fixed-pos frames are parented by the viewport frame or the page content frame. |
1936 | 0 | // We'll assume that printing/print preview don't have displayports for their |
1937 | 0 | // pages! |
1938 | 0 | nsIFrame* parent = aFrame->GetParent(); |
1939 | 0 | if (!parent || parent->GetParent() || |
1940 | 0 | aFrame->StyleDisplay()->mPosition != NS_STYLE_POSITION_FIXED) { |
1941 | 0 | return false; |
1942 | 0 | } |
1943 | 0 | return ViewportHasDisplayPort(aFrame->PresContext()); |
1944 | 0 | } |
1945 | | |
1946 | | // static |
1947 | | nsIScrollableFrame* |
1948 | | nsLayoutUtils::GetNearestScrollableFrameForDirection(nsIFrame* aFrame, |
1949 | | Direction aDirection) |
1950 | 0 | { |
1951 | 0 | NS_ASSERTION(aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame"); |
1952 | 0 | for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) { |
1953 | 0 | nsIScrollableFrame* scrollableFrame = do_QueryFrame(f); |
1954 | 0 | if (scrollableFrame) { |
1955 | 0 | ScrollStyles ss = scrollableFrame->GetScrollStyles(); |
1956 | 0 | uint32_t directions = scrollableFrame->GetPerceivedScrollingDirections(); |
1957 | 0 | if (aDirection == eVertical ? |
1958 | 0 | (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN && |
1959 | 0 | (directions & nsIScrollableFrame::VERTICAL)) : |
1960 | 0 | (ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN && |
1961 | 0 | (directions & nsIScrollableFrame::HORIZONTAL))) |
1962 | 0 | return scrollableFrame; |
1963 | 0 | } |
1964 | 0 | } |
1965 | 0 | return nullptr; |
1966 | 0 | } |
1967 | | |
1968 | | // static |
1969 | | nsIScrollableFrame* |
1970 | | nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame, uint32_t aFlags) |
1971 | 0 | { |
1972 | 0 | NS_ASSERTION(aFrame, "GetNearestScrollableFrame expects a non-null frame"); |
1973 | 0 | for (nsIFrame* f = aFrame; f; f = (aFlags & SCROLLABLE_SAME_DOC) ? |
1974 | 0 | f->GetParent() : nsLayoutUtils::GetCrossDocParentFrame(f)) { |
1975 | 0 | nsIScrollableFrame* scrollableFrame = do_QueryFrame(f); |
1976 | 0 | if (scrollableFrame) { |
1977 | 0 | if (aFlags & SCROLLABLE_ONLY_ASYNC_SCROLLABLE) { |
1978 | 0 | if (scrollableFrame->WantAsyncScroll()) { |
1979 | 0 | return scrollableFrame; |
1980 | 0 | } |
1981 | 0 | } else { |
1982 | 0 | ScrollStyles ss = scrollableFrame->GetScrollStyles(); |
1983 | 0 | if ((aFlags & SCROLLABLE_INCLUDE_HIDDEN) || |
1984 | 0 | ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN || |
1985 | 0 | ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) { |
1986 | 0 | return scrollableFrame; |
1987 | 0 | } |
1988 | 0 | } |
1989 | 0 | if (aFlags & SCROLLABLE_ALWAYS_MATCH_ROOT) { |
1990 | 0 | nsIPresShell* ps = f->PresShell(); |
1991 | 0 | if (ps->GetRootScrollFrame() == f && |
1992 | 0 | ps->GetDocument() && ps->GetDocument()->IsRootDisplayDocument()) { |
1993 | 0 | return scrollableFrame; |
1994 | 0 | } |
1995 | 0 | } |
1996 | 0 | } |
1997 | 0 | if ((aFlags & SCROLLABLE_FIXEDPOS_FINDS_ROOT) && |
1998 | 0 | f->StyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED && |
1999 | 0 | nsLayoutUtils::IsReallyFixedPos(f)) { |
2000 | 0 | return f->PresShell()->GetRootScrollFrameAsScrollable(); |
2001 | 0 | } |
2002 | 0 | } |
2003 | 0 | return nullptr; |
2004 | 0 | } |
2005 | | |
2006 | | // static |
2007 | | nsRect |
2008 | | nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame, |
2009 | | const nsRect& aScrolledFrameOverflowArea, |
2010 | | const nsSize& aScrollPortSize, |
2011 | | uint8_t aDirection) |
2012 | 0 | { |
2013 | 0 | WritingMode wm = aScrolledFrame->GetWritingMode(); |
2014 | 0 | // Potentially override the frame's direction to use the direction found |
2015 | 0 | // by ScrollFrameHelper::GetScrolledFrameDir() |
2016 | 0 | wm.SetDirectionFromBidiLevel(aDirection == NS_STYLE_DIRECTION_RTL ? 1 : 0); |
2017 | 0 |
|
2018 | 0 | nscoord x1 = aScrolledFrameOverflowArea.x, |
2019 | 0 | x2 = aScrolledFrameOverflowArea.XMost(), |
2020 | 0 | y1 = aScrolledFrameOverflowArea.y, |
2021 | 0 | y2 = aScrolledFrameOverflowArea.YMost(); |
2022 | 0 |
|
2023 | 0 | bool horizontal = !wm.IsVertical(); |
2024 | 0 |
|
2025 | 0 | // Clamp the horizontal start-edge (x1 or x2, depending whether the logical |
2026 | 0 | // axis that corresponds to horizontal progresses from L-R or R-L). |
2027 | 0 | // In horizontal writing mode, we need to check IsInlineReversed() to see |
2028 | 0 | // which side to clamp; in vertical mode, it depends on the block direction. |
2029 | 0 | if ((horizontal && !wm.IsInlineReversed()) || wm.IsVerticalLR()) { |
2030 | 0 | if (x1 < 0) { |
2031 | 0 | x1 = 0; |
2032 | 0 | } |
2033 | 0 | } else { |
2034 | 0 | if (x2 > aScrollPortSize.width) { |
2035 | 0 | x2 = aScrollPortSize.width; |
2036 | 0 | } |
2037 | 0 | // When the scrolled frame chooses a size larger than its available width |
2038 | 0 | // (because its padding alone is larger than the available width), we need |
2039 | 0 | // to keep the start-edge of the scroll frame anchored to the start-edge of |
2040 | 0 | // the scrollport. |
2041 | 0 | // When the scrolled frame is RTL, this means moving it in our left-based |
2042 | 0 | // coordinate system, so we need to compensate for its extra width here by |
2043 | 0 | // effectively repositioning the frame. |
2044 | 0 | nscoord extraWidth = |
2045 | 0 | std::max(0, aScrolledFrame->GetSize().width - aScrollPortSize.width); |
2046 | 0 | x2 += extraWidth; |
2047 | 0 | } |
2048 | 0 |
|
2049 | 0 | // Similarly, clamp the vertical start-edge. |
2050 | 0 | // In horizontal writing mode, the block direction is always top-to-bottom; |
2051 | 0 | // in vertical writing mode, we need to check IsInlineReversed(). |
2052 | 0 | if (horizontal || !wm.IsInlineReversed()) { |
2053 | 0 | if (y1 < 0) { |
2054 | 0 | y1 = 0; |
2055 | 0 | } |
2056 | 0 | } else { |
2057 | 0 | if (y2 > aScrollPortSize.height) { |
2058 | 0 | y2 = aScrollPortSize.height; |
2059 | 0 | } |
2060 | 0 | nscoord extraHeight = |
2061 | 0 | std::max(0, aScrolledFrame->GetSize().height - aScrollPortSize.height); |
2062 | 0 | y2 += extraHeight; |
2063 | 0 | } |
2064 | 0 |
|
2065 | 0 | return nsRect(x1, y1, x2 - x1, y2 - y1); |
2066 | 0 | } |
2067 | | |
2068 | | //static |
2069 | | bool |
2070 | | nsLayoutUtils::HasPseudoStyle(nsIContent* aContent, |
2071 | | ComputedStyle* aComputedStyle, |
2072 | | CSSPseudoElementType aPseudoElement, |
2073 | | nsPresContext* aPresContext) |
2074 | 0 | { |
2075 | 0 | MOZ_ASSERT(aPresContext, "Must have a prescontext"); |
2076 | 0 |
|
2077 | 0 | RefPtr<ComputedStyle> pseudoContext; |
2078 | 0 | if (aContent) { |
2079 | 0 | pseudoContext = aPresContext->StyleSet()-> |
2080 | 0 | ProbePseudoElementStyle(*aContent->AsElement(), aPseudoElement, |
2081 | 0 | aComputedStyle); |
2082 | 0 | } |
2083 | 0 | return pseudoContext != nullptr; |
2084 | 0 | } |
2085 | | |
2086 | | nsPoint |
2087 | | nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(Event* aDOMEvent, nsIFrame* aFrame) |
2088 | 0 | { |
2089 | 0 | if (!aDOMEvent) |
2090 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2091 | 0 | WidgetEvent* event = aDOMEvent->WidgetEventPtr(); |
2092 | 0 | if (!event) |
2093 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2094 | 0 | return GetEventCoordinatesRelativeTo(event, aFrame); |
2095 | 0 | } |
2096 | | |
2097 | | nsPoint |
2098 | | nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent, |
2099 | | nsIFrame* aFrame) |
2100 | 0 | { |
2101 | 0 | if (!aEvent || (aEvent->mClass != eMouseEventClass && |
2102 | 0 | aEvent->mClass != eMouseScrollEventClass && |
2103 | 0 | aEvent->mClass != eWheelEventClass && |
2104 | 0 | aEvent->mClass != eDragEventClass && |
2105 | 0 | aEvent->mClass != eSimpleGestureEventClass && |
2106 | 0 | aEvent->mClass != ePointerEventClass && |
2107 | 0 | aEvent->mClass != eGestureNotifyEventClass && |
2108 | 0 | aEvent->mClass != eTouchEventClass && |
2109 | 0 | aEvent->mClass != eQueryContentEventClass)) |
2110 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2111 | 0 |
|
2112 | 0 | return GetEventCoordinatesRelativeTo(aEvent, |
2113 | 0 | aEvent->AsGUIEvent()->mRefPoint, |
2114 | 0 | aFrame); |
2115 | 0 | } |
2116 | | |
2117 | | nsPoint |
2118 | | nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent, |
2119 | | const LayoutDeviceIntPoint& aPoint, |
2120 | | nsIFrame* aFrame) |
2121 | 0 | { |
2122 | 0 | if (!aFrame) { |
2123 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2124 | 0 | } |
2125 | 0 |
|
2126 | 0 | nsIWidget* widget = aEvent->AsGUIEvent()->mWidget; |
2127 | 0 | if (!widget) { |
2128 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2129 | 0 | } |
2130 | 0 |
|
2131 | 0 | return GetEventCoordinatesRelativeTo(widget, aPoint, aFrame); |
2132 | 0 | } |
2133 | | |
2134 | | nsPoint |
2135 | | nsLayoutUtils::GetEventCoordinatesRelativeTo(nsIWidget* aWidget, |
2136 | | const LayoutDeviceIntPoint& aPoint, |
2137 | | nsIFrame* aFrame) |
2138 | 0 | { |
2139 | 0 | if (!aFrame || !aWidget) { |
2140 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2141 | 0 | } |
2142 | 0 |
|
2143 | 0 | nsView* view = aFrame->GetView(); |
2144 | 0 | if (view) { |
2145 | 0 | nsIWidget* frameWidget = view->GetWidget(); |
2146 | 0 | if (frameWidget && frameWidget == aWidget) { |
2147 | 0 | // Special case this cause it happens a lot. |
2148 | 0 | // This also fixes bug 664707, events in the extra-special case of select |
2149 | 0 | // dropdown popups that are transformed. |
2150 | 0 | nsPresContext* presContext = aFrame->PresContext(); |
2151 | 0 | nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x), |
2152 | 0 | presContext->DevPixelsToAppUnits(aPoint.y)); |
2153 | 0 | pt = pt - view->ViewToWidgetOffset(); |
2154 | 0 | pt = pt.RemoveResolution(GetCurrentAPZResolutionScale(presContext->PresShell())); |
2155 | 0 | return pt; |
2156 | 0 | } |
2157 | 0 | } |
2158 | 0 | |
2159 | 0 | /* If we walk up the frame tree and discover that any of the frames are |
2160 | 0 | * transformed, we need to do extra work to convert from the global |
2161 | 0 | * space to the local space. |
2162 | 0 | */ |
2163 | 0 | nsIFrame* rootFrame = aFrame; |
2164 | 0 | bool transformFound = false; |
2165 | 0 | for (nsIFrame* f = aFrame; f; f = GetCrossDocParentFrame(f)) { |
2166 | 0 | if (f->IsTransformed()) { |
2167 | 0 | transformFound = true; |
2168 | 0 | } |
2169 | 0 |
|
2170 | 0 | rootFrame = f; |
2171 | 0 | } |
2172 | 0 |
|
2173 | 0 | nsView* rootView = rootFrame->GetView(); |
2174 | 0 | if (!rootView) { |
2175 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2176 | 0 | } |
2177 | 0 |
|
2178 | 0 | nsPoint widgetToView = TranslateWidgetToView(rootFrame->PresContext(), |
2179 | 0 | aWidget, aPoint, rootView); |
2180 | 0 |
|
2181 | 0 | if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { |
2182 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2183 | 0 | } |
2184 | 0 |
|
2185 | 0 | // Convert from root document app units to app units of the document aFrame |
2186 | 0 | // is in. |
2187 | 0 | int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel(); |
2188 | 0 | int32_t localAPD = aFrame->PresContext()->AppUnitsPerDevPixel(); |
2189 | 0 | widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD); |
2190 | 0 | nsIPresShell* shell = aFrame->PresShell(); |
2191 | 0 |
|
2192 | 0 | // XXX Bug 1224748 - Update nsLayoutUtils functions to correctly handle nsPresShell resolution |
2193 | 0 | widgetToView = widgetToView.RemoveResolution(GetCurrentAPZResolutionScale(shell)); |
2194 | 0 |
|
2195 | 0 | /* If we encountered a transform, we can't do simple arithmetic to figure |
2196 | 0 | * out how to convert back to aFrame's coordinates and must use the CTM. |
2197 | 0 | */ |
2198 | 0 | if (transformFound || nsSVGUtils::IsInSVGTextSubtree(aFrame)) { |
2199 | 0 | return TransformRootPointToFrame(aFrame, widgetToView); |
2200 | 0 | } |
2201 | 0 | |
2202 | 0 | /* Otherwise, all coordinate systems are translations of one another, |
2203 | 0 | * so we can just subtract out the difference. |
2204 | 0 | */ |
2205 | 0 | return widgetToView - aFrame->GetOffsetToCrossDoc(rootFrame); |
2206 | 0 | } |
2207 | | |
2208 | | nsIFrame* |
2209 | | nsLayoutUtils::GetPopupFrameForEventCoordinates(nsPresContext* aPresContext, |
2210 | | const WidgetEvent* aEvent) |
2211 | 0 | { |
2212 | 0 | #ifdef MOZ_XUL |
2213 | 0 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
2214 | 0 | if (!pm) { |
2215 | 0 | return nullptr; |
2216 | 0 | } |
2217 | 0 | nsTArray<nsIFrame*> popups; |
2218 | 0 | pm->GetVisiblePopups(popups); |
2219 | 0 | uint32_t i; |
2220 | 0 | // Search from top to bottom |
2221 | 0 | for (i = 0; i < popups.Length(); i++) { |
2222 | 0 | nsIFrame* popup = popups[i]; |
2223 | 0 | if (popup->PresContext()->GetRootPresContext() == aPresContext && |
2224 | 0 | popup->GetScrollableOverflowRect().Contains( |
2225 | 0 | GetEventCoordinatesRelativeTo(aEvent, popup))) { |
2226 | 0 | return popup; |
2227 | 0 | } |
2228 | 0 | } |
2229 | 0 | #endif |
2230 | 0 | return nullptr; |
2231 | 0 | } |
2232 | | |
2233 | | static void ConstrainToCoordValues(float& aStart, float& aSize) |
2234 | 0 | { |
2235 | 0 | MOZ_ASSERT(aSize >= 0); |
2236 | 0 |
|
2237 | 0 | // Here we try to make sure that the resulting nsRect will continue to cover |
2238 | 0 | // as much of the area that was covered by the original gfx Rect as possible. |
2239 | 0 |
|
2240 | 0 | // We clamp the bounds of the rect to {nscoord_MIN,nscoord_MAX} since |
2241 | 0 | // nsRect::X/Y() and nsRect::XMost/YMost() can't return values outwith this |
2242 | 0 | // range: |
2243 | 0 | float end = aStart + aSize; |
2244 | 0 | aStart = clamped(aStart, float(nscoord_MIN), float(nscoord_MAX)); |
2245 | 0 | end = clamped(end, float(nscoord_MIN), float(nscoord_MAX)); |
2246 | 0 |
|
2247 | 0 | aSize = end - aStart; |
2248 | 0 |
|
2249 | 0 | // We must also clamp aSize to {0,nscoord_MAX} since nsRect::Width/Height() |
2250 | 0 | // can't return a value greater than nscoord_MAX. If aSize is greater than |
2251 | 0 | // nscoord_MAX then we reduce it to nscoord_MAX while keeping the rect |
2252 | 0 | // centered: |
2253 | 0 | if (aSize > nscoord_MAX) { |
2254 | 0 | float excess = aSize - nscoord_MAX; |
2255 | 0 | excess /= 2; |
2256 | 0 | aStart += excess; |
2257 | 0 | aSize = (float)nscoord_MAX; |
2258 | 0 | } |
2259 | 0 | } |
2260 | | |
2261 | | /** |
2262 | | * Given a gfxFloat, constrains its value to be between nscoord_MIN and nscoord_MAX. |
2263 | | * |
2264 | | * @param aVal The value to constrain (in/out) |
2265 | | */ |
2266 | | static void ConstrainToCoordValues(gfxFloat& aVal) |
2267 | 0 | { |
2268 | 0 | if (aVal <= nscoord_MIN) |
2269 | 0 | aVal = nscoord_MIN; |
2270 | 0 | else if (aVal >= nscoord_MAX) |
2271 | 0 | aVal = nscoord_MAX; |
2272 | 0 | } |
2273 | | |
2274 | | static void ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize) |
2275 | 0 | { |
2276 | 0 | gfxFloat max = aStart + aSize; |
2277 | 0 |
|
2278 | 0 | // Clamp the end points to within nscoord range |
2279 | 0 | ConstrainToCoordValues(aStart); |
2280 | 0 | ConstrainToCoordValues(max); |
2281 | 0 |
|
2282 | 0 | aSize = max - aStart; |
2283 | 0 | // If the width if still greater than the max nscoord, then bring both |
2284 | 0 | // endpoints in by the same amount until it fits. |
2285 | 0 | if (aSize > nscoord_MAX) { |
2286 | 0 | gfxFloat excess = aSize - nscoord_MAX; |
2287 | 0 | excess /= 2; |
2288 | 0 |
|
2289 | 0 | aStart += excess; |
2290 | 0 | aSize = nscoord_MAX; |
2291 | 0 | } else if (aSize < nscoord_MIN) { |
2292 | 0 | gfxFloat excess = aSize - nscoord_MIN; |
2293 | 0 | excess /= 2; |
2294 | 0 |
|
2295 | 0 | aStart -= excess; |
2296 | 0 | aSize = nscoord_MIN; |
2297 | 0 | } |
2298 | 0 | } |
2299 | | |
2300 | | nsRect |
2301 | | nsLayoutUtils::RoundGfxRectToAppRect(const Rect &aRect, float aFactor) |
2302 | 0 | { |
2303 | 0 | /* Get a new Rect whose units are app units by scaling by the specified factor. */ |
2304 | 0 | Rect scaledRect = aRect; |
2305 | 0 | scaledRect.ScaleRoundOut(aFactor); |
2306 | 0 |
|
2307 | 0 | /* We now need to constrain our results to the max and min values for coords. */ |
2308 | 0 | ConstrainToCoordValues(scaledRect.x, scaledRect.width); |
2309 | 0 | ConstrainToCoordValues(scaledRect.y, scaledRect.height); |
2310 | 0 |
|
2311 | 0 | /* Now typecast everything back. This is guaranteed to be safe. */ |
2312 | 0 | if (aRect.IsEmpty()) { |
2313 | 0 | return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()), 0, 0); |
2314 | 0 | } else { |
2315 | 0 | return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()), |
2316 | 0 | nscoord(scaledRect.Width()), nscoord(scaledRect.Height())); |
2317 | 0 | } |
2318 | 0 | } |
2319 | | |
2320 | | nsRect |
2321 | | nsLayoutUtils::RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor) |
2322 | 0 | { |
2323 | 0 | /* Get a new gfxRect whose units are app units by scaling by the specified factor. */ |
2324 | 0 | gfxRect scaledRect = aRect; |
2325 | 0 | scaledRect.ScaleRoundOut(aFactor); |
2326 | 0 |
|
2327 | 0 | /* We now need to constrain our results to the max and min values for coords. */ |
2328 | 0 | ConstrainToCoordValues(scaledRect.x, scaledRect.width); |
2329 | 0 | ConstrainToCoordValues(scaledRect.y, scaledRect.height); |
2330 | 0 |
|
2331 | 0 | /* Now typecast everything back. This is guaranteed to be safe. */ |
2332 | 0 | if (aRect.IsEmpty()) { |
2333 | 0 | return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()), 0, 0); |
2334 | 0 | } else { |
2335 | 0 | return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()), |
2336 | 0 | nscoord(scaledRect.Width()), nscoord(scaledRect.Height())); |
2337 | 0 | } |
2338 | 0 | } |
2339 | | |
2340 | | |
2341 | | nsRegion |
2342 | | nsLayoutUtils::RoundedRectIntersectRect(const nsRect& aRoundedRect, |
2343 | | const nscoord aRadii[8], |
2344 | | const nsRect& aContainedRect) |
2345 | 0 | { |
2346 | 0 | // rectFullHeight and rectFullWidth together will approximately contain |
2347 | 0 | // the total area of the frame minus the rounded corners. |
2348 | 0 | nsRect rectFullHeight = aRoundedRect; |
2349 | 0 | nscoord xDiff = std::max(aRadii[eCornerTopLeftX], aRadii[eCornerBottomLeftX]); |
2350 | 0 | rectFullHeight.x += xDiff; |
2351 | 0 | rectFullHeight.width -= std::max(aRadii[eCornerTopRightX], |
2352 | 0 | aRadii[eCornerBottomRightX]) + xDiff; |
2353 | 0 | nsRect r1; |
2354 | 0 | r1.IntersectRect(rectFullHeight, aContainedRect); |
2355 | 0 |
|
2356 | 0 | nsRect rectFullWidth = aRoundedRect; |
2357 | 0 | nscoord yDiff = std::max(aRadii[eCornerTopLeftY], aRadii[eCornerTopRightY]); |
2358 | 0 | rectFullWidth.y += yDiff; |
2359 | 0 | rectFullWidth.height -= std::max(aRadii[eCornerBottomLeftY], |
2360 | 0 | aRadii[eCornerBottomRightY]) + yDiff; |
2361 | 0 | nsRect r2; |
2362 | 0 | r2.IntersectRect(rectFullWidth, aContainedRect); |
2363 | 0 |
|
2364 | 0 | nsRegion result; |
2365 | 0 | result.Or(r1, r2); |
2366 | 0 | return result; |
2367 | 0 | } |
2368 | | |
2369 | | nsIntRegion |
2370 | | nsLayoutUtils::RoundedRectIntersectIntRect(const nsIntRect& aRoundedRect, |
2371 | | const RectCornerRadii& aCornerRadii, |
2372 | | const nsIntRect& aContainedRect) |
2373 | 0 | { |
2374 | 0 | // rectFullHeight and rectFullWidth together will approximately contain |
2375 | 0 | // the total area of the frame minus the rounded corners. |
2376 | 0 | nsIntRect rectFullHeight = aRoundedRect; |
2377 | 0 | uint32_t xDiff = std::max(aCornerRadii.TopLeft().width, |
2378 | 0 | aCornerRadii.BottomLeft().width); |
2379 | 0 | rectFullHeight.x += xDiff; |
2380 | 0 | rectFullHeight.width -= std::max(aCornerRadii.TopRight().width, |
2381 | 0 | aCornerRadii.BottomRight().width) + xDiff; |
2382 | 0 | nsIntRect r1; |
2383 | 0 | r1.IntersectRect(rectFullHeight, aContainedRect); |
2384 | 0 |
|
2385 | 0 | nsIntRect rectFullWidth = aRoundedRect; |
2386 | 0 | uint32_t yDiff = std::max(aCornerRadii.TopLeft().height, |
2387 | 0 | aCornerRadii.TopRight().height); |
2388 | 0 | rectFullWidth.y += yDiff; |
2389 | 0 | rectFullWidth.height -= std::max(aCornerRadii.BottomLeft().height, |
2390 | 0 | aCornerRadii.BottomRight().height) + yDiff; |
2391 | 0 | nsIntRect r2; |
2392 | 0 | r2.IntersectRect(rectFullWidth, aContainedRect); |
2393 | 0 |
|
2394 | 0 | nsIntRegion result; |
2395 | 0 | result.Or(r1, r2); |
2396 | 0 | return result; |
2397 | 0 | } |
2398 | | |
2399 | | // Helper for RoundedRectIntersectsRect. |
2400 | | static bool |
2401 | | CheckCorner(nscoord aXOffset, nscoord aYOffset, |
2402 | | nscoord aXRadius, nscoord aYRadius) |
2403 | 0 | { |
2404 | 0 | MOZ_ASSERT(aXOffset > 0 && aYOffset > 0, |
2405 | 0 | "must not pass nonpositives to CheckCorner"); |
2406 | 0 | MOZ_ASSERT(aXRadius >= 0 && aYRadius >= 0, |
2407 | 0 | "must not pass negatives to CheckCorner"); |
2408 | 0 |
|
2409 | 0 | // Avoid floating point math unless we're either (1) within the |
2410 | 0 | // quarter-ellipse area at the rounded corner or (2) outside the |
2411 | 0 | // rounding. |
2412 | 0 | if (aXOffset >= aXRadius || aYOffset >= aYRadius) |
2413 | 0 | return true; |
2414 | 0 | |
2415 | 0 | // Convert coordinates to a unit circle with (0,0) as the center of |
2416 | 0 | // curvature, and see if we're inside the circle or outside. |
2417 | 0 | float scaledX = float(aXRadius - aXOffset) / float(aXRadius); |
2418 | 0 | float scaledY = float(aYRadius - aYOffset) / float(aYRadius); |
2419 | 0 | return scaledX * scaledX + scaledY * scaledY < 1.0f; |
2420 | 0 | } |
2421 | | |
2422 | | bool |
2423 | | nsLayoutUtils::RoundedRectIntersectsRect(const nsRect& aRoundedRect, |
2424 | | const nscoord aRadii[8], |
2425 | | const nsRect& aTestRect) |
2426 | 0 | { |
2427 | 0 | if (!aTestRect.Intersects(aRoundedRect)) |
2428 | 0 | return false; |
2429 | 0 | |
2430 | 0 | // distances from this edge of aRoundedRect to opposite edge of aTestRect, |
2431 | 0 | // which we know are positive due to the Intersects check above. |
2432 | 0 | nsMargin insets; |
2433 | 0 | insets.top = aTestRect.YMost() - aRoundedRect.y; |
2434 | 0 | insets.right = aRoundedRect.XMost() - aTestRect.x; |
2435 | 0 | insets.bottom = aRoundedRect.YMost() - aTestRect.y; |
2436 | 0 | insets.left = aTestRect.XMost() - aRoundedRect.x; |
2437 | 0 |
|
2438 | 0 | // Check whether the bottom-right corner of aTestRect is inside the |
2439 | 0 | // top left corner of aBounds when rounded by aRadii, etc. If any |
2440 | 0 | // corner is not, then fail; otherwise succeed. |
2441 | 0 | return CheckCorner(insets.left, insets.top, |
2442 | 0 | aRadii[eCornerTopLeftX], |
2443 | 0 | aRadii[eCornerTopLeftY]) && |
2444 | 0 | CheckCorner(insets.right, insets.top, |
2445 | 0 | aRadii[eCornerTopRightX], |
2446 | 0 | aRadii[eCornerTopRightY]) && |
2447 | 0 | CheckCorner(insets.right, insets.bottom, |
2448 | 0 | aRadii[eCornerBottomRightX], |
2449 | 0 | aRadii[eCornerBottomRightY]) && |
2450 | 0 | CheckCorner(insets.left, insets.bottom, |
2451 | 0 | aRadii[eCornerBottomLeftX], |
2452 | 0 | aRadii[eCornerBottomLeftY]); |
2453 | 0 | } |
2454 | | |
2455 | | nsRect |
2456 | | nsLayoutUtils::MatrixTransformRect(const nsRect &aBounds, |
2457 | | const Matrix4x4 &aMatrix, float aFactor) |
2458 | 0 | { |
2459 | 0 | RectDouble image = RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor), |
2460 | 0 | NSAppUnitsToDoublePixels(aBounds.y, aFactor), |
2461 | 0 | NSAppUnitsToDoublePixels(aBounds.width, aFactor), |
2462 | 0 | NSAppUnitsToDoublePixels(aBounds.height, aFactor)); |
2463 | 0 |
|
2464 | 0 | RectDouble maxBounds = RectDouble(double(nscoord_MIN) / aFactor * 0.5, |
2465 | 0 | double(nscoord_MIN) / aFactor * 0.5, |
2466 | 0 | double(nscoord_MAX) / aFactor, |
2467 | 0 | double(nscoord_MAX) / aFactor); |
2468 | 0 |
|
2469 | 0 | image = aMatrix.TransformAndClipBounds(image, maxBounds); |
2470 | 0 |
|
2471 | 0 | return RoundGfxRectToAppRect(ThebesRect(image), aFactor); |
2472 | 0 | } |
2473 | | |
2474 | | nsRect |
2475 | | nsLayoutUtils::MatrixTransformRect(const nsRect &aBounds, |
2476 | | const Matrix4x4Flagged &aMatrix, float aFactor) |
2477 | 0 | { |
2478 | 0 | RectDouble image = RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor), |
2479 | 0 | NSAppUnitsToDoublePixels(aBounds.y, aFactor), |
2480 | 0 | NSAppUnitsToDoublePixels(aBounds.width, aFactor), |
2481 | 0 | NSAppUnitsToDoublePixels(aBounds.height, aFactor)); |
2482 | 0 |
|
2483 | 0 | RectDouble maxBounds = RectDouble(double(nscoord_MIN) / aFactor * 0.5, |
2484 | 0 | double(nscoord_MIN) / aFactor * 0.5, |
2485 | 0 | double(nscoord_MAX) / aFactor, |
2486 | 0 | double(nscoord_MAX) / aFactor); |
2487 | 0 |
|
2488 | 0 | image = aMatrix.TransformAndClipBounds(image, maxBounds); |
2489 | 0 |
|
2490 | 0 | return RoundGfxRectToAppRect(ThebesRect(image), aFactor); |
2491 | 0 | } |
2492 | | |
2493 | | nsPoint |
2494 | | nsLayoutUtils::MatrixTransformPoint(const nsPoint &aPoint, |
2495 | | const Matrix4x4 &aMatrix, float aFactor) |
2496 | 0 | { |
2497 | 0 | gfxPoint image = gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor), |
2498 | 0 | NSAppUnitsToFloatPixels(aPoint.y, aFactor)); |
2499 | 0 | image = aMatrix.TransformPoint(image); |
2500 | 0 | return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor), |
2501 | 0 | NSFloatPixelsToAppUnits(float(image.y), aFactor)); |
2502 | 0 | } |
2503 | | |
2504 | | void |
2505 | | nsLayoutUtils::PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin, float aAppUnitsPerPixel, bool aRounded) |
2506 | 0 | { |
2507 | 0 | Point3D gfxOrigin = |
2508 | 0 | Point3D(NSAppUnitsToFloatPixels(aOrigin.x, aAppUnitsPerPixel), |
2509 | 0 | NSAppUnitsToFloatPixels(aOrigin.y, aAppUnitsPerPixel), |
2510 | 0 | 0.0f); |
2511 | 0 | if (aRounded) { |
2512 | 0 | gfxOrigin.x = NS_round(gfxOrigin.x); |
2513 | 0 | gfxOrigin.y = NS_round(gfxOrigin.y); |
2514 | 0 | } |
2515 | 0 | aTransform.PostTranslate(gfxOrigin); |
2516 | 0 | } |
2517 | | |
2518 | | // We want to this return true for the scroll frame, but not the |
2519 | | // scrolled frame (which has the same content). |
2520 | | bool |
2521 | | nsLayoutUtils::FrameHasDisplayPort(nsIFrame* aFrame, const nsIFrame* aScrolledFrame) |
2522 | 0 | { |
2523 | 0 | if (!aFrame->GetContent() || !HasDisplayPort(aFrame->GetContent())) { |
2524 | 0 | return false; |
2525 | 0 | } |
2526 | 0 | nsIScrollableFrame* sf = do_QueryFrame(aFrame); |
2527 | 0 | if (sf) { |
2528 | 0 | if (aScrolledFrame && aScrolledFrame != sf->GetScrolledFrame()) { |
2529 | 0 | return false; |
2530 | 0 | } |
2531 | 0 | return true; |
2532 | 0 | } |
2533 | 0 | return false; |
2534 | 0 | } |
2535 | | |
2536 | | Matrix4x4Flagged |
2537 | | nsLayoutUtils::GetTransformToAncestor(const nsIFrame *aFrame, |
2538 | | const nsIFrame *aAncestor, |
2539 | | uint32_t aFlags, |
2540 | | nsIFrame** aOutAncestor) |
2541 | 0 | { |
2542 | 0 | nsIFrame* parent; |
2543 | 0 | Matrix4x4Flagged ctm; |
2544 | 0 | if (aFrame == aAncestor) { |
2545 | 0 | return ctm; |
2546 | 0 | } |
2547 | 0 | ctm = aFrame->GetTransformMatrix(aAncestor, &parent, aFlags); |
2548 | 0 | while (parent && parent != aAncestor && |
2549 | 0 | (!(aFlags & nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) || |
2550 | 0 | (!parent->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && |
2551 | 0 | !parent->IsStackingContext() && |
2552 | 0 | !FrameHasDisplayPort(parent)))) { |
2553 | 0 | if (!parent->Extend3DContext()) { |
2554 | 0 | ctm.ProjectTo2D(); |
2555 | 0 | } |
2556 | 0 | ctm = ctm * parent->GetTransformMatrix(aAncestor, &parent, aFlags); |
2557 | 0 | } |
2558 | 0 | if (aOutAncestor) { |
2559 | 0 | *aOutAncestor = parent; |
2560 | 0 | } |
2561 | 0 | return ctm; |
2562 | 0 | } |
2563 | | |
2564 | | gfxSize |
2565 | | nsLayoutUtils::GetTransformToAncestorScale(nsIFrame* aFrame) |
2566 | 0 | { |
2567 | 0 | Matrix4x4Flagged transform = GetTransformToAncestor(aFrame, |
2568 | 0 | nsLayoutUtils::GetDisplayRootFrame(aFrame)); |
2569 | 0 | Matrix transform2D; |
2570 | 0 | if (transform.Is2D(&transform2D)) { |
2571 | 0 | return ThebesMatrix(transform2D).ScaleFactors(true); |
2572 | 0 | } |
2573 | 0 | return gfxSize(1, 1); |
2574 | 0 | } |
2575 | | |
2576 | | static Matrix4x4Flagged |
2577 | | GetTransformToAncestorExcludingAnimated(nsIFrame* aFrame, |
2578 | | const nsIFrame* aAncestor) |
2579 | 0 | { |
2580 | 0 | nsIFrame* parent; |
2581 | 0 | Matrix4x4Flagged ctm; |
2582 | 0 | if (aFrame == aAncestor) { |
2583 | 0 | return ctm; |
2584 | 0 | } |
2585 | 0 | if (ActiveLayerTracker::IsScaleSubjectToAnimation(aFrame)) { |
2586 | 0 | return ctm; |
2587 | 0 | } |
2588 | 0 | ctm = aFrame->GetTransformMatrix(aAncestor, &parent); |
2589 | 0 | while (parent && parent != aAncestor) { |
2590 | 0 | if (ActiveLayerTracker::IsScaleSubjectToAnimation(parent)) { |
2591 | 0 | return Matrix4x4Flagged(); |
2592 | 0 | } |
2593 | 0 | if (!parent->Extend3DContext()) { |
2594 | 0 | ctm.ProjectTo2D(); |
2595 | 0 | } |
2596 | 0 | ctm = ctm * parent->GetTransformMatrix(aAncestor, &parent); |
2597 | 0 | } |
2598 | 0 | return ctm; |
2599 | 0 | } |
2600 | | |
2601 | | gfxSize |
2602 | | nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(nsIFrame* aFrame) |
2603 | 0 | { |
2604 | 0 | Matrix4x4Flagged transform = GetTransformToAncestorExcludingAnimated(aFrame, |
2605 | 0 | nsLayoutUtils::GetDisplayRootFrame(aFrame)); |
2606 | 0 | Matrix transform2D; |
2607 | 0 | if (transform.Is2D(&transform2D)) { |
2608 | 0 | return ThebesMatrix(transform2D).ScaleFactors(true); |
2609 | 0 | } |
2610 | 0 | return gfxSize(1, 1); |
2611 | 0 | } |
2612 | | |
2613 | | nsIFrame* |
2614 | | nsLayoutUtils::FindNearestCommonAncestorFrame(nsIFrame* aFrame1, nsIFrame* aFrame2) |
2615 | 0 | { |
2616 | 0 | AutoTArray<nsIFrame*,100> ancestors1; |
2617 | 0 | AutoTArray<nsIFrame*,100> ancestors2; |
2618 | 0 | nsIFrame* commonAncestor = nullptr; |
2619 | 0 | if (aFrame1->PresContext() == aFrame2->PresContext()) { |
2620 | 0 | commonAncestor = aFrame1->PresShell()->GetRootFrame(); |
2621 | 0 | } |
2622 | 0 | for (nsIFrame* f = aFrame1; f != commonAncestor; |
2623 | 0 | f = nsLayoutUtils::GetCrossDocParentFrame(f)) { |
2624 | 0 | ancestors1.AppendElement(f); |
2625 | 0 | } |
2626 | 0 | for (nsIFrame* f = aFrame2; f != commonAncestor; |
2627 | 0 | f = nsLayoutUtils::GetCrossDocParentFrame(f)) { |
2628 | 0 | ancestors2.AppendElement(f); |
2629 | 0 | } |
2630 | 0 | uint32_t minLengths = std::min(ancestors1.Length(), ancestors2.Length()); |
2631 | 0 | for (uint32_t i = 1; i <= minLengths; ++i) { |
2632 | 0 | if (ancestors1[ancestors1.Length() - i] == ancestors2[ancestors2.Length() - i]) { |
2633 | 0 | commonAncestor = ancestors1[ancestors1.Length() - i]; |
2634 | 0 | } else { |
2635 | 0 | break; |
2636 | 0 | } |
2637 | 0 | } |
2638 | 0 | return commonAncestor; |
2639 | 0 | } |
2640 | | |
2641 | | nsLayoutUtils::TransformResult |
2642 | | nsLayoutUtils::TransformPoints(nsIFrame* aFromFrame, nsIFrame* aToFrame, |
2643 | | uint32_t aPointCount, CSSPoint* aPoints) |
2644 | 0 | { |
2645 | 0 | nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame); |
2646 | 0 | if (!nearestCommonAncestor) { |
2647 | 0 | return NO_COMMON_ANCESTOR; |
2648 | 0 | } |
2649 | 0 | Matrix4x4Flagged downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor); |
2650 | 0 | if (downToDest.IsSingular()) { |
2651 | 0 | return NONINVERTIBLE_TRANSFORM; |
2652 | 0 | } |
2653 | 0 | downToDest.Invert(); |
2654 | 0 | Matrix4x4Flagged upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor); |
2655 | 0 | CSSToLayoutDeviceScale devPixelsPerCSSPixelFromFrame = |
2656 | 0 | aFromFrame->PresContext()->CSSToDevPixelScale(); |
2657 | 0 | CSSToLayoutDeviceScale devPixelsPerCSSPixelToFrame = |
2658 | 0 | aToFrame->PresContext()->CSSToDevPixelScale(); |
2659 | 0 | for (uint32_t i = 0; i < aPointCount; ++i) { |
2660 | 0 | LayoutDevicePoint devPixels = aPoints[i] * devPixelsPerCSSPixelFromFrame; |
2661 | 0 | // What should the behaviour be if some of the points aren't invertible |
2662 | 0 | // and others are? Just assume all points are for now. |
2663 | 0 | Point toDevPixels = downToDest.ProjectPoint( |
2664 | 0 | (upToAncestor.TransformPoint(Point(devPixels.x, devPixels.y)))).As2DPoint(); |
2665 | 0 | // Divide here so that when the devPixelsPerCSSPixels are the same, we get the correct |
2666 | 0 | // answer instead of some inaccuracy multiplying a number by its reciprocal. |
2667 | 0 | aPoints[i] = LayoutDevicePoint(toDevPixels.x, toDevPixels.y) / |
2668 | 0 | devPixelsPerCSSPixelToFrame; |
2669 | 0 | } |
2670 | 0 | return TRANSFORM_SUCCEEDED; |
2671 | 0 | } |
2672 | | |
2673 | | nsLayoutUtils::TransformResult |
2674 | | nsLayoutUtils::TransformPoint(nsIFrame* aFromFrame, nsIFrame* aToFrame, |
2675 | | nsPoint& aPoint) |
2676 | 0 | { |
2677 | 0 | nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame); |
2678 | 0 | if (!nearestCommonAncestor) { |
2679 | 0 | return NO_COMMON_ANCESTOR; |
2680 | 0 | } |
2681 | 0 | Matrix4x4Flagged downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor); |
2682 | 0 | if (downToDest.IsSingular()) { |
2683 | 0 | return NONINVERTIBLE_TRANSFORM; |
2684 | 0 | } |
2685 | 0 | downToDest.Invert(); |
2686 | 0 | Matrix4x4Flagged upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor); |
2687 | 0 |
|
2688 | 0 | float devPixelsPerAppUnitFromFrame = |
2689 | 0 | 1.0f / aFromFrame->PresContext()->AppUnitsPerDevPixel(); |
2690 | 0 | float devPixelsPerAppUnitToFrame = |
2691 | 0 | 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel(); |
2692 | 0 | Point4D toDevPixels = downToDest.ProjectPoint( |
2693 | 0 | upToAncestor.TransformPoint(Point(aPoint.x * devPixelsPerAppUnitFromFrame, |
2694 | 0 | aPoint.y * devPixelsPerAppUnitFromFrame))); |
2695 | 0 | if (!toDevPixels.HasPositiveWCoord()) { |
2696 | 0 | // Not strictly true, but we failed to get a valid point in this |
2697 | 0 | // coordinate space. |
2698 | 0 | return NONINVERTIBLE_TRANSFORM; |
2699 | 0 | } |
2700 | 0 | aPoint.x = NSToCoordRound(toDevPixels.x / devPixelsPerAppUnitToFrame); |
2701 | 0 | aPoint.y = NSToCoordRound(toDevPixels.y / devPixelsPerAppUnitToFrame); |
2702 | 0 | return TRANSFORM_SUCCEEDED; |
2703 | 0 | } |
2704 | | |
2705 | | nsLayoutUtils::TransformResult |
2706 | | nsLayoutUtils::TransformRect(nsIFrame* aFromFrame, nsIFrame* aToFrame, |
2707 | | nsRect& aRect) |
2708 | 0 | { |
2709 | 0 | nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame); |
2710 | 0 | if (!nearestCommonAncestor) { |
2711 | 0 | return NO_COMMON_ANCESTOR; |
2712 | 0 | } |
2713 | 0 | Matrix4x4Flagged downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor); |
2714 | 0 | if (downToDest.IsSingular()) { |
2715 | 0 | return NONINVERTIBLE_TRANSFORM; |
2716 | 0 | } |
2717 | 0 | downToDest.Invert(); |
2718 | 0 | Matrix4x4Flagged upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor); |
2719 | 0 |
|
2720 | 0 | float devPixelsPerAppUnitFromFrame = |
2721 | 0 | 1.0f / aFromFrame->PresContext()->AppUnitsPerDevPixel(); |
2722 | 0 | float devPixelsPerAppUnitToFrame = |
2723 | 0 | 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel(); |
2724 | 0 | gfx::Rect toDevPixels = downToDest.ProjectRectBounds( |
2725 | 0 | upToAncestor.ProjectRectBounds( |
2726 | 0 | gfx::Rect(aRect.x * devPixelsPerAppUnitFromFrame, |
2727 | 0 | aRect.y * devPixelsPerAppUnitFromFrame, |
2728 | 0 | aRect.width * devPixelsPerAppUnitFromFrame, |
2729 | 0 | aRect.height * devPixelsPerAppUnitFromFrame), |
2730 | 0 | Rect(-std::numeric_limits<Float>::max() * 0.5f, |
2731 | 0 | -std::numeric_limits<Float>::max() * 0.5f, |
2732 | 0 | std::numeric_limits<Float>::max(), |
2733 | 0 | std::numeric_limits<Float>::max())), |
2734 | 0 | Rect(-std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame * 0.5f, |
2735 | 0 | -std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame * 0.5f, |
2736 | 0 | std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame, |
2737 | 0 | std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame)); |
2738 | 0 | aRect.x = NSToCoordRound(toDevPixels.x / devPixelsPerAppUnitToFrame); |
2739 | 0 | aRect.y = NSToCoordRound(toDevPixels.y / devPixelsPerAppUnitToFrame); |
2740 | 0 | aRect.width = NSToCoordRound(toDevPixels.width / devPixelsPerAppUnitToFrame); |
2741 | 0 | aRect.height = NSToCoordRound(toDevPixels.height / devPixelsPerAppUnitToFrame); |
2742 | 0 | return TRANSFORM_SUCCEEDED; |
2743 | 0 | } |
2744 | | |
2745 | | nsRect |
2746 | | nsLayoutUtils::GetRectRelativeToFrame(Element* aElement, nsIFrame* aFrame) |
2747 | 0 | { |
2748 | 0 | if (!aElement || !aFrame) { |
2749 | 0 | return nsRect(); |
2750 | 0 | } |
2751 | 0 | |
2752 | 0 | nsIFrame* frame = aElement->GetPrimaryFrame(); |
2753 | 0 | if (!frame) { |
2754 | 0 | return nsRect(); |
2755 | 0 | } |
2756 | 0 | |
2757 | 0 | nsRect rect = frame->GetRectRelativeToSelf(); |
2758 | 0 | nsLayoutUtils::TransformResult rv = |
2759 | 0 | nsLayoutUtils::TransformRect(frame, aFrame, rect); |
2760 | 0 | if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) { |
2761 | 0 | return nsRect(); |
2762 | 0 | } |
2763 | 0 | |
2764 | 0 | return rect; |
2765 | 0 | } |
2766 | | |
2767 | | bool |
2768 | | nsLayoutUtils::ContainsPoint(const nsRect& aRect, const nsPoint& aPoint, |
2769 | | nscoord aInflateSize) |
2770 | 0 | { |
2771 | 0 | nsRect rect = aRect; |
2772 | 0 | rect.Inflate(aInflateSize); |
2773 | 0 | return rect.Contains(aPoint); |
2774 | 0 | } |
2775 | | |
2776 | | nsRect |
2777 | | nsLayoutUtils::ClampRectToScrollFrames(nsIFrame* aFrame, const nsRect& aRect) |
2778 | 0 | { |
2779 | 0 | nsIFrame* closestScrollFrame = |
2780 | 0 | nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll); |
2781 | 0 |
|
2782 | 0 | nsRect resultRect = aRect; |
2783 | 0 |
|
2784 | 0 | while (closestScrollFrame) { |
2785 | 0 | nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame); |
2786 | 0 |
|
2787 | 0 | nsRect scrollPortRect = sf->GetScrollPortRect(); |
2788 | 0 | nsLayoutUtils::TransformRect(closestScrollFrame, aFrame, scrollPortRect); |
2789 | 0 |
|
2790 | 0 | resultRect = resultRect.Intersect(scrollPortRect); |
2791 | 0 |
|
2792 | 0 | // Check whether aRect is visible in the scroll frame or not. |
2793 | 0 | if (resultRect.IsEmpty()) { |
2794 | 0 | break; |
2795 | 0 | } |
2796 | 0 | |
2797 | 0 | // Get next ancestor scroll frame. |
2798 | 0 | closestScrollFrame = nsLayoutUtils::GetClosestFrameOfType( |
2799 | 0 | closestScrollFrame->GetParent(), LayoutFrameType::Scroll); |
2800 | 0 | } |
2801 | 0 |
|
2802 | 0 | return resultRect; |
2803 | 0 | } |
2804 | | |
2805 | | bool |
2806 | | nsLayoutUtils::GetLayerTransformForFrame(nsIFrame* aFrame, |
2807 | | Matrix4x4Flagged* aTransform) |
2808 | 0 | { |
2809 | 0 | // FIXME/bug 796690: we can sometimes compute a transform in these |
2810 | 0 | // cases, it just increases complexity considerably. Punt for now. |
2811 | 0 | if (aFrame->Extend3DContext() || aFrame->HasTransformGetter()) { |
2812 | 0 | return false; |
2813 | 0 | } |
2814 | 0 | |
2815 | 0 | nsIFrame* root = nsLayoutUtils::GetDisplayRootFrame(aFrame); |
2816 | 0 | if (root->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) { |
2817 | 0 | // Content may have been invalidated, so we can't reliably compute |
2818 | 0 | // the "layer transform" in general. |
2819 | 0 | return false; |
2820 | 0 | } |
2821 | 0 | // If the caller doesn't care about the value, early-return to skip |
2822 | 0 | // overhead below. |
2823 | 0 | if (!aTransform) { |
2824 | 0 | return true; |
2825 | 0 | } |
2826 | 0 | |
2827 | 0 | nsDisplayListBuilder builder(root, |
2828 | 0 | nsDisplayListBuilderMode::TRANSFORM_COMPUTATION, |
2829 | 0 | false/*don't build caret*/); |
2830 | 0 | builder.BeginFrame(); |
2831 | 0 | nsDisplayList list; |
2832 | 0 | nsDisplayTransform* item = |
2833 | 0 | MakeDisplayItem<nsDisplayTransform>(&builder, aFrame, &list, nsRect()); |
2834 | 0 |
|
2835 | 0 | *aTransform = item->GetTransform(); |
2836 | 0 | item->Destroy(&builder); |
2837 | 0 |
|
2838 | 0 | builder.EndFrame(); |
2839 | 0 |
|
2840 | 0 | return true; |
2841 | 0 | } |
2842 | | |
2843 | | static bool |
2844 | | TransformGfxPointFromAncestor(nsIFrame *aFrame, |
2845 | | const Point &aPoint, |
2846 | | nsIFrame *aAncestor, |
2847 | | Point* aOut) |
2848 | 0 | { |
2849 | 0 | Matrix4x4Flagged ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor); |
2850 | 0 | ctm.Invert(); |
2851 | 0 | Point4D point = ctm.ProjectPoint(aPoint); |
2852 | 0 | if (!point.HasPositiveWCoord()) { |
2853 | 0 | return false; |
2854 | 0 | } |
2855 | 0 | *aOut = point.As2DPoint(); |
2856 | 0 | return true; |
2857 | 0 | } |
2858 | | |
2859 | | static Rect |
2860 | | TransformGfxRectToAncestor(const nsIFrame *aFrame, |
2861 | | const Rect &aRect, |
2862 | | const nsIFrame *aAncestor, |
2863 | | bool* aPreservesAxisAlignedRectangles = nullptr, |
2864 | | Maybe<Matrix4x4Flagged>* aMatrixCache = nullptr, |
2865 | | bool aStopAtStackingContextAndDisplayPortAndOOFFrame = false, |
2866 | | nsIFrame** aOutAncestor = nullptr) |
2867 | 0 | { |
2868 | 0 | Matrix4x4Flagged ctm; |
2869 | 0 | if (aMatrixCache && *aMatrixCache) { |
2870 | 0 | // We are given a matrix to use, so use it |
2871 | 0 | ctm = aMatrixCache->value(); |
2872 | 0 | } else { |
2873 | 0 | // Else, compute it |
2874 | 0 | uint32_t flags = 0; |
2875 | 0 | if (aStopAtStackingContextAndDisplayPortAndOOFFrame) { |
2876 | 0 | flags |= nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT; |
2877 | 0 | } |
2878 | 0 | ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor, flags, aOutAncestor); |
2879 | 0 | if (aMatrixCache) { |
2880 | 0 | // and put it in the cache, if provided |
2881 | 0 | *aMatrixCache = Some(ctm); |
2882 | 0 | } |
2883 | 0 | } |
2884 | 0 | // Fill out the axis-alignment flag |
2885 | 0 | if (aPreservesAxisAlignedRectangles) { |
2886 | 0 | Matrix matrix2d; |
2887 | 0 | *aPreservesAxisAlignedRectangles = |
2888 | 0 | ctm.Is2D(&matrix2d) && matrix2d.PreservesAxisAlignedRectangles(); |
2889 | 0 | } |
2890 | 0 | const nsIFrame* ancestor = aOutAncestor ? *aOutAncestor : aAncestor; |
2891 | 0 | float factor = ancestor->PresContext()->AppUnitsPerDevPixel(); |
2892 | 0 | Rect maxBounds = Rect(float(nscoord_MIN) / factor * 0.5, |
2893 | 0 | float(nscoord_MIN) / factor * 0.5, |
2894 | 0 | float(nscoord_MAX) / factor, |
2895 | 0 | float(nscoord_MAX) / factor); |
2896 | 0 | return ctm.TransformAndClipBounds(aRect, maxBounds); |
2897 | 0 | } |
2898 | | |
2899 | | static SVGTextFrame* |
2900 | | GetContainingSVGTextFrame(const nsIFrame* aFrame) |
2901 | 0 | { |
2902 | 0 | if (!nsSVGUtils::IsInSVGTextSubtree(aFrame)) { |
2903 | 0 | return nullptr; |
2904 | 0 | } |
2905 | 0 | |
2906 | 0 | return static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType( |
2907 | 0 | aFrame->GetParent(), LayoutFrameType::SVGText)); |
2908 | 0 | } |
2909 | | |
2910 | | nsPoint |
2911 | | nsLayoutUtils::TransformAncestorPointToFrame(nsIFrame* aFrame, |
2912 | | const nsPoint& aPoint, |
2913 | | nsIFrame* aAncestor) |
2914 | 0 | { |
2915 | 0 | SVGTextFrame* text = GetContainingSVGTextFrame(aFrame); |
2916 | 0 |
|
2917 | 0 | float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); |
2918 | 0 | Point result(NSAppUnitsToFloatPixels(aPoint.x, factor), |
2919 | 0 | NSAppUnitsToFloatPixels(aPoint.y, factor)); |
2920 | 0 |
|
2921 | 0 | if (text) { |
2922 | 0 | if (!TransformGfxPointFromAncestor(text, result, aAncestor, &result)) { |
2923 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2924 | 0 | } |
2925 | 0 | result = text->TransformFramePointToTextChild(result, aFrame); |
2926 | 0 | } else { |
2927 | 0 | if (!TransformGfxPointFromAncestor(aFrame, result, nullptr, &result)) { |
2928 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
2929 | 0 | } |
2930 | 0 | } |
2931 | 0 |
|
2932 | 0 | return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor), |
2933 | 0 | NSFloatPixelsToAppUnits(float(result.y), factor)); |
2934 | 0 | } |
2935 | | |
2936 | | nsRect |
2937 | | nsLayoutUtils::TransformFrameRectToAncestor(const nsIFrame* aFrame, |
2938 | | const nsRect& aRect, |
2939 | | const nsIFrame* aAncestor, |
2940 | | bool* aPreservesAxisAlignedRectangles /* = nullptr */, |
2941 | | Maybe<Matrix4x4Flagged>* aMatrixCache /* = nullptr */, |
2942 | | bool aStopAtStackingContextAndDisplayPortAndOOFFrame /* = false */, |
2943 | | nsIFrame** aOutAncestor /* = nullptr */) |
2944 | 0 | { |
2945 | 0 | SVGTextFrame* text = GetContainingSVGTextFrame(aFrame); |
2946 | 0 |
|
2947 | 0 | float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); |
2948 | 0 | Rect result; |
2949 | 0 |
|
2950 | 0 | if (text) { |
2951 | 0 | result = ToRect(text->TransformFrameRectFromTextChild(aRect, aFrame)); |
2952 | 0 | result = TransformGfxRectToAncestor(text, result, aAncestor, |
2953 | 0 | nullptr, aMatrixCache, |
2954 | 0 | aStopAtStackingContextAndDisplayPortAndOOFFrame, aOutAncestor); |
2955 | 0 | // TransformFrameRectFromTextChild could involve any kind of transform, we |
2956 | 0 | // could drill down into it to get an answer out of it but we don't yet. |
2957 | 0 | if (aPreservesAxisAlignedRectangles) |
2958 | 0 | *aPreservesAxisAlignedRectangles = false; |
2959 | 0 | } else { |
2960 | 0 | result = Rect(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel), |
2961 | 0 | NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel), |
2962 | 0 | NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel), |
2963 | 0 | NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel)); |
2964 | 0 | result = TransformGfxRectToAncestor(aFrame, result, aAncestor, |
2965 | 0 | aPreservesAxisAlignedRectangles, aMatrixCache, |
2966 | 0 | aStopAtStackingContextAndDisplayPortAndOOFFrame, aOutAncestor); |
2967 | 0 | } |
2968 | 0 |
|
2969 | 0 | float destAppUnitsPerDevPixel = aAncestor->PresContext()->AppUnitsPerDevPixel(); |
2970 | 0 | return nsRect(NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel), |
2971 | 0 | NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel), |
2972 | 0 | NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel), |
2973 | 0 | NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel)); |
2974 | 0 | } |
2975 | | |
2976 | 0 | static LayoutDeviceIntPoint GetWidgetOffset(nsIWidget* aWidget, nsIWidget*& aRootWidget) { |
2977 | 0 | LayoutDeviceIntPoint offset(0, 0); |
2978 | 0 | while ((aWidget->WindowType() == eWindowType_child || |
2979 | 0 | aWidget->IsPlugin())) { |
2980 | 0 | nsIWidget* parent = aWidget->GetParent(); |
2981 | 0 | if (!parent) { |
2982 | 0 | break; |
2983 | 0 | } |
2984 | 0 | LayoutDeviceIntRect bounds = aWidget->GetBounds(); |
2985 | 0 | offset += bounds.TopLeft(); |
2986 | 0 | aWidget = parent; |
2987 | 0 | } |
2988 | 0 | aRootWidget = aWidget; |
2989 | 0 | return offset; |
2990 | 0 | } |
2991 | | |
2992 | | LayoutDeviceIntPoint |
2993 | 0 | nsLayoutUtils::WidgetToWidgetOffset(nsIWidget* aFrom, nsIWidget* aTo) { |
2994 | 0 | nsIWidget* fromRoot; |
2995 | 0 | LayoutDeviceIntPoint fromOffset = GetWidgetOffset(aFrom, fromRoot); |
2996 | 0 | nsIWidget* toRoot; |
2997 | 0 | LayoutDeviceIntPoint toOffset = GetWidgetOffset(aTo, toRoot); |
2998 | 0 |
|
2999 | 0 | if (fromRoot == toRoot) { |
3000 | 0 | return fromOffset - toOffset; |
3001 | 0 | } |
3002 | 0 | return aFrom->WidgetToScreenOffset() - aTo->WidgetToScreenOffset(); |
3003 | 0 | } |
3004 | | |
3005 | | nsPoint |
3006 | | nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext, |
3007 | | nsIWidget* aWidget, const LayoutDeviceIntPoint& aPt, |
3008 | | nsView* aView) |
3009 | 0 | { |
3010 | 0 | nsPoint viewOffset; |
3011 | 0 | nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset); |
3012 | 0 | if (!viewWidget) { |
3013 | 0 | return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
3014 | 0 | } |
3015 | 0 |
|
3016 | 0 | LayoutDeviceIntPoint widgetPoint = aPt + WidgetToWidgetOffset(aWidget, viewWidget); |
3017 | 0 | nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x), |
3018 | 0 | aPresContext->DevPixelsToAppUnits(widgetPoint.y)); |
3019 | 0 | return widgetAppUnits - viewOffset; |
3020 | 0 | } |
3021 | | |
3022 | | LayoutDeviceIntPoint |
3023 | | nsLayoutUtils::TranslateViewToWidget(nsPresContext* aPresContext, |
3024 | | nsView* aView, nsPoint aPt, |
3025 | | nsIWidget* aWidget) |
3026 | 0 | { |
3027 | 0 | nsPoint viewOffset; |
3028 | 0 | nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset); |
3029 | 0 | if (!viewWidget) { |
3030 | 0 | return LayoutDeviceIntPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
3031 | 0 | } |
3032 | 0 |
|
3033 | 0 | nsPoint pt = (aPt + |
3034 | 0 | viewOffset).ApplyResolution(GetCurrentAPZResolutionScale(aPresContext->PresShell())); |
3035 | 0 | LayoutDeviceIntPoint relativeToViewWidget(aPresContext->AppUnitsToDevPixels(pt.x), |
3036 | 0 | aPresContext->AppUnitsToDevPixels(pt.y)); |
3037 | 0 | return relativeToViewWidget + WidgetToWidgetOffset(viewWidget, aWidget); |
3038 | 0 | } |
3039 | | |
3040 | | // Combine aNewBreakType with aOrigBreakType, but limit the break types |
3041 | | // to StyleClear::Left, Right, Both. |
3042 | | StyleClear |
3043 | | nsLayoutUtils::CombineBreakType(StyleClear aOrigBreakType, |
3044 | | StyleClear aNewBreakType) |
3045 | 0 | { |
3046 | 0 | StyleClear breakType = aOrigBreakType; |
3047 | 0 | switch(breakType) { |
3048 | 0 | case StyleClear::Left: |
3049 | 0 | if (StyleClear::Right == aNewBreakType || |
3050 | 0 | StyleClear::Both == aNewBreakType) { |
3051 | 0 | breakType = StyleClear::Both; |
3052 | 0 | } |
3053 | 0 | break; |
3054 | 0 | case StyleClear::Right: |
3055 | 0 | if (StyleClear::Left == aNewBreakType || |
3056 | 0 | StyleClear::Both == aNewBreakType) { |
3057 | 0 | breakType = StyleClear::Both; |
3058 | 0 | } |
3059 | 0 | break; |
3060 | 0 | case StyleClear::None: |
3061 | 0 | if (StyleClear::Left == aNewBreakType || |
3062 | 0 | StyleClear::Right == aNewBreakType || |
3063 | 0 | StyleClear::Both == aNewBreakType) { |
3064 | 0 | breakType = aNewBreakType; |
3065 | 0 | } |
3066 | 0 | break; |
3067 | 0 | default: |
3068 | 0 | break; |
3069 | 0 | } |
3070 | 0 | return breakType; |
3071 | 0 | } |
3072 | | |
3073 | | #ifdef MOZ_DUMP_PAINTING |
3074 | | #include <stdio.h> |
3075 | | |
3076 | | static bool gDumpEventList = false; |
3077 | | |
3078 | | // nsLayoutUtils::PaintFrame() can call itself recursively, so rather than |
3079 | | // maintaining a single paint count, we need a stack. |
3080 | | StaticAutoPtr<nsTArray<int>> gPaintCountStack; |
3081 | | |
3082 | | struct AutoNestedPaintCount { |
3083 | | AutoNestedPaintCount() { |
3084 | | gPaintCountStack->AppendElement(0); |
3085 | | } |
3086 | | ~AutoNestedPaintCount() { |
3087 | | gPaintCountStack->RemoveLastElement(); |
3088 | | } |
3089 | | }; |
3090 | | |
3091 | | #endif |
3092 | | |
3093 | | nsIFrame* |
3094 | | nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt, uint32_t aFlags) |
3095 | 0 | { |
3096 | 0 | AUTO_PROFILER_LABEL("nsLayoutUtils::GetFrameForPoint", LAYOUT); |
3097 | 0 |
|
3098 | 0 | nsresult rv; |
3099 | 0 | AutoTArray<nsIFrame*,8> outFrames; |
3100 | 0 | rv = GetFramesForArea(aFrame, nsRect(aPt, nsSize(1, 1)), outFrames, aFlags); |
3101 | 0 | NS_ENSURE_SUCCESS(rv, nullptr); |
3102 | 0 | return outFrames.Length() ? outFrames.ElementAt(0) : nullptr; |
3103 | 0 | } |
3104 | | |
3105 | | nsresult |
3106 | | nsLayoutUtils::GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect, |
3107 | | nsTArray<nsIFrame*> &aOutFrames, |
3108 | | uint32_t aFlags) |
3109 | 0 | { |
3110 | 0 | AUTO_PROFILER_LABEL("nsLayoutUtils::GetFramesForArea", LAYOUT); |
3111 | 0 |
|
3112 | 0 | nsDisplayListBuilder builder(aFrame, |
3113 | 0 | nsDisplayListBuilderMode::EVENT_DELIVERY, |
3114 | 0 | false); |
3115 | 0 | builder.BeginFrame(); |
3116 | 0 | nsDisplayList list; |
3117 | 0 |
|
3118 | 0 | if (aFlags & IGNORE_PAINT_SUPPRESSION) { |
3119 | 0 | builder.IgnorePaintSuppression(); |
3120 | 0 | } |
3121 | 0 |
|
3122 | 0 | if (aFlags & IGNORE_ROOT_SCROLL_FRAME) { |
3123 | 0 | nsIFrame* rootScrollFrame = aFrame->PresShell()->GetRootScrollFrame(); |
3124 | 0 | if (rootScrollFrame) { |
3125 | 0 | builder.SetIgnoreScrollFrame(rootScrollFrame); |
3126 | 0 | } |
3127 | 0 | } |
3128 | 0 | if (aFlags & IGNORE_CROSS_DOC) { |
3129 | 0 | builder.SetDescendIntoSubdocuments(false); |
3130 | 0 | } |
3131 | 0 |
|
3132 | 0 | builder.SetHitTestIsForVisibility(aFlags & ONLY_VISIBLE); |
3133 | 0 |
|
3134 | 0 | builder.EnterPresShell(aFrame); |
3135 | 0 |
|
3136 | 0 | builder.SetVisibleRect(aRect); |
3137 | 0 | builder.SetDirtyRect(aRect); |
3138 | 0 |
|
3139 | 0 | aFrame->BuildDisplayListForStackingContext(&builder, &list); |
3140 | 0 | builder.LeavePresShell(aFrame, nullptr); |
3141 | 0 |
|
3142 | | #ifdef MOZ_DUMP_PAINTING |
3143 | | if (gDumpEventList) { |
3144 | | fprintf_stderr(stderr, "Event handling --- (%d,%d):\n", aRect.x, aRect.y); |
3145 | | |
3146 | | std::stringstream ss; |
3147 | | nsFrame::PrintDisplayList(&builder, list, ss); |
3148 | | print_stderr(ss); |
3149 | | } |
3150 | | #endif |
3151 | |
|
3152 | 0 | nsDisplayItem::HitTestState hitTestState; |
3153 | 0 | list.HitTest(&builder, aRect, &hitTestState, &aOutFrames); |
3154 | 0 | list.DeleteAll(&builder); |
3155 | 0 | builder.EndFrame(); |
3156 | 0 | return NS_OK; |
3157 | 0 | } |
3158 | | |
3159 | | // aScrollFrameAsScrollable must be non-nullptr and queryable to an nsIFrame |
3160 | | FrameMetrics |
3161 | 0 | nsLayoutUtils::CalculateBasicFrameMetrics(nsIScrollableFrame* aScrollFrame) { |
3162 | 0 | nsIFrame* frame = do_QueryFrame(aScrollFrame); |
3163 | 0 | MOZ_ASSERT(frame); |
3164 | 0 |
|
3165 | 0 | // Calculate the metrics necessary for calculating the displayport. |
3166 | 0 | // This code has a lot in common with the code in ComputeFrameMetrics(); |
3167 | 0 | // we may want to refactor this at some point. |
3168 | 0 | FrameMetrics metrics; |
3169 | 0 | nsPresContext* presContext = frame->PresContext(); |
3170 | 0 | nsIPresShell* presShell = presContext->PresShell(); |
3171 | 0 | CSSToLayoutDeviceScale deviceScale = presContext->CSSToDevPixelScale(); |
3172 | 0 | float resolution = 1.0f; |
3173 | 0 | if (frame == presShell->GetRootScrollFrame()) { |
3174 | 0 | // Only the root scrollable frame for a given presShell should pick up |
3175 | 0 | // the presShell's resolution. All the other frames are 1.0. |
3176 | 0 | resolution = presShell->GetResolution(); |
3177 | 0 | } |
3178 | 0 | // Note: unlike in ComputeFrameMetrics(), we don't know the full cumulative |
3179 | 0 | // resolution including FrameMetrics::mExtraResolution, because layout hasn't |
3180 | 0 | // chosen a resolution to paint at yet. However, the display port calculation |
3181 | 0 | // divides out mExtraResolution anyways, so we get the correct result by |
3182 | 0 | // setting the mCumulativeResolution to everything except the extra resolution |
3183 | 0 | // and leaving mExtraResolution at 1. |
3184 | 0 | LayoutDeviceToLayerScale2D cumulativeResolution( |
3185 | 0 | presShell->GetCumulativeResolution() |
3186 | 0 | * nsLayoutUtils::GetTransformToAncestorScale(frame)); |
3187 | 0 |
|
3188 | 0 | LayerToParentLayerScale layerToParentLayerScale(1.0f); |
3189 | 0 | metrics.SetDevPixelsPerCSSPixel(deviceScale); |
3190 | 0 | metrics.SetPresShellResolution(resolution); |
3191 | 0 | metrics.SetCumulativeResolution(cumulativeResolution); |
3192 | 0 | metrics.SetZoom(deviceScale * cumulativeResolution * layerToParentLayerScale); |
3193 | 0 |
|
3194 | 0 | // Only the size of the composition bounds is relevant to the |
3195 | 0 | // displayport calculation, not its origin. |
3196 | 0 | nsSize compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(frame); |
3197 | 0 | LayoutDeviceToParentLayerScale2D compBoundsScale; |
3198 | 0 | if (frame == presShell->GetRootScrollFrame() && presContext->IsRootContentDocument()) { |
3199 | 0 | if (presContext->GetParentPresContext()) { |
3200 | 0 | float res = presContext->GetParentPresContext()->PresShell()->GetCumulativeResolution(); |
3201 | 0 | compBoundsScale = LayoutDeviceToParentLayerScale2D( |
3202 | 0 | LayoutDeviceToParentLayerScale(res)); |
3203 | 0 | } |
3204 | 0 | } else { |
3205 | 0 | compBoundsScale = cumulativeResolution * layerToParentLayerScale; |
3206 | 0 | } |
3207 | 0 | metrics.SetCompositionBounds( |
3208 | 0 | LayoutDeviceRect::FromAppUnits(nsRect(nsPoint(0, 0), compositionSize), |
3209 | 0 | presContext->AppUnitsPerDevPixel()) |
3210 | 0 | * compBoundsScale); |
3211 | 0 |
|
3212 | 0 | metrics.SetRootCompositionSize( |
3213 | 0 | nsLayoutUtils::CalculateRootCompositionSize(frame, false, metrics)); |
3214 | 0 |
|
3215 | 0 | metrics.SetScrollOffset(CSSPoint::FromAppUnits( |
3216 | 0 | aScrollFrame->GetScrollPosition())); |
3217 | 0 |
|
3218 | 0 | metrics.SetScrollableRect(CSSRect::FromAppUnits( |
3219 | 0 | nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrame, nullptr))); |
3220 | 0 |
|
3221 | 0 | return metrics; |
3222 | 0 | } |
3223 | | |
3224 | | bool |
3225 | | nsLayoutUtils::CalculateAndSetDisplayPortMargins(nsIScrollableFrame* aScrollFrame, |
3226 | 0 | RepaintMode aRepaintMode) { |
3227 | 0 | nsIFrame* frame = do_QueryFrame(aScrollFrame); |
3228 | 0 | MOZ_ASSERT(frame); |
3229 | 0 | nsIContent* content = frame->GetContent(); |
3230 | 0 | MOZ_ASSERT(content); |
3231 | 0 |
|
3232 | 0 | FrameMetrics metrics = CalculateBasicFrameMetrics(aScrollFrame); |
3233 | 0 | ScreenMargin displayportMargins = apz::CalculatePendingDisplayPort( |
3234 | 0 | metrics, ParentLayerPoint(0.0f, 0.0f)); |
3235 | 0 | nsIPresShell* presShell = frame->PresContext()->GetPresShell(); |
3236 | 0 | return nsLayoutUtils::SetDisplayPortMargins( |
3237 | 0 | content, presShell, displayportMargins, 0, aRepaintMode); |
3238 | 0 | } |
3239 | | |
3240 | | bool |
3241 | | nsLayoutUtils::MaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder, |
3242 | | nsIFrame* aScrollFrame, |
3243 | | RepaintMode aRepaintMode) |
3244 | 0 | { |
3245 | 0 | nsIContent* content = aScrollFrame->GetContent(); |
3246 | 0 | nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame); |
3247 | 0 | if (!content || !scrollableFrame) { |
3248 | 0 | return false; |
3249 | 0 | } |
3250 | 0 | |
3251 | 0 | bool haveDisplayPort = HasDisplayPort(content); |
3252 | 0 |
|
3253 | 0 | // We perform an optimization where we ensure that at least one |
3254 | 0 | // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a displayport. |
3255 | 0 | // If that's not the case yet, and we are async-scrollable, we will get a |
3256 | 0 | // displayport. |
3257 | 0 | if (aBuilder.IsPaintingToWindow() && |
3258 | 0 | nsLayoutUtils::AsyncPanZoomEnabled(aScrollFrame) && |
3259 | 0 | !aBuilder.HaveScrollableDisplayPort() && |
3260 | 0 | scrollableFrame->WantAsyncScroll()) { |
3261 | 0 |
|
3262 | 0 | // If we don't already have a displayport, calculate and set one. |
3263 | 0 | if (!haveDisplayPort) { |
3264 | 0 | CalculateAndSetDisplayPortMargins(scrollableFrame, aRepaintMode); |
3265 | | #ifdef DEBUG |
3266 | | haveDisplayPort = HasDisplayPort(content); |
3267 | | MOZ_ASSERT(haveDisplayPort, "should have a displayport after having just set it"); |
3268 | | #endif |
3269 | | } |
3270 | 0 |
|
3271 | 0 | // Record that the we now have a scrollable display port. |
3272 | 0 | aBuilder.SetHaveScrollableDisplayPort(); |
3273 | 0 | return true; |
3274 | 0 | } |
3275 | 0 | return false; |
3276 | 0 | } |
3277 | | |
3278 | | nsIScrollableFrame* |
3279 | | nsLayoutUtils::GetAsyncScrollableAncestorFrame(nsIFrame* aTarget) |
3280 | 0 | { |
3281 | 0 | uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT |
3282 | 0 | | nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE |
3283 | 0 | | nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT; |
3284 | 0 | return nsLayoutUtils::GetNearestScrollableFrame(aTarget, flags); |
3285 | 0 | } |
3286 | | |
3287 | | void |
3288 | | nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame* aFrame, |
3289 | | RepaintMode aRepaintMode) |
3290 | 0 | { |
3291 | 0 | nsIFrame* frame = aFrame; |
3292 | 0 | while (frame) { |
3293 | 0 | frame = nsLayoutUtils::GetCrossDocParentFrame(frame); |
3294 | 0 | if (!frame) { |
3295 | 0 | break; |
3296 | 0 | } |
3297 | 0 | nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame); |
3298 | 0 | if (!scrollAncestor) { |
3299 | 0 | break; |
3300 | 0 | } |
3301 | 0 | frame = do_QueryFrame(scrollAncestor); |
3302 | 0 | MOZ_ASSERT(frame); |
3303 | 0 | MOZ_ASSERT(scrollAncestor->WantAsyncScroll() || |
3304 | 0 | frame->PresShell()->GetRootScrollFrame() == frame); |
3305 | 0 | if (nsLayoutUtils::AsyncPanZoomEnabled(frame) && |
3306 | 0 | !nsLayoutUtils::HasDisplayPort(frame->GetContent())) { |
3307 | 0 | nsLayoutUtils::SetDisplayPortMargins( |
3308 | 0 | frame->GetContent(), frame->PresShell(), ScreenMargin(), 0, |
3309 | 0 | aRepaintMode); |
3310 | 0 | } |
3311 | 0 | } |
3312 | 0 | } |
3313 | | |
3314 | | bool |
3315 | | nsLayoutUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered( |
3316 | | nsIFrame* aFrame, nsDisplayListBuilder& aBuilder) |
3317 | 0 | { |
3318 | 0 | nsIScrollableFrame* sf = do_QueryFrame(aFrame); |
3319 | 0 | if (sf) { |
3320 | 0 | if (MaybeCreateDisplayPort(aBuilder, aFrame, RepaintMode::Repaint)) { |
3321 | 0 | return true; |
3322 | 0 | } |
3323 | 0 | } |
3324 | 0 | if (aFrame->IsPlaceholderFrame()) { |
3325 | 0 | nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(aFrame); |
3326 | 0 | if (MaybeCreateDisplayPortInFirstScrollFrameEncountered( |
3327 | 0 | placeholder->GetOutOfFlowFrame(), aBuilder)) { |
3328 | 0 | return true; |
3329 | 0 | } |
3330 | 0 | } |
3331 | 0 | if (aFrame->IsSubDocumentFrame()) { |
3332 | 0 | nsIPresShell* presShell = |
3333 | 0 | static_cast<nsSubDocumentFrame*>(aFrame)->GetSubdocumentPresShellForPainting(0); |
3334 | 0 | nsIFrame* root = presShell ? presShell->GetRootFrame() : nullptr; |
3335 | 0 | if (root) { |
3336 | 0 | if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(root, aBuilder)) { |
3337 | 0 | return true; |
3338 | 0 | } |
3339 | 0 | } |
3340 | 0 | } |
3341 | 0 | if (aFrame->IsDeckFrame()) { |
3342 | 0 | // only descend the visible card of a decks |
3343 | 0 | nsIFrame* child = static_cast<nsDeckFrame*>(aFrame)->GetSelectedBox(); |
3344 | 0 | if (child) { |
3345 | 0 | return MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder); |
3346 | 0 | } |
3347 | 0 | } |
3348 | 0 | |
3349 | 0 | for (nsIFrame* child : aFrame->PrincipalChildList()) { |
3350 | 0 | if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder)) { |
3351 | 0 | return true; |
3352 | 0 | } |
3353 | 0 | } |
3354 | 0 |
|
3355 | 0 | return false; |
3356 | 0 | } |
3357 | | |
3358 | | void |
3359 | | nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame) |
3360 | 0 | { |
3361 | 0 | nsIFrame* frame = aFrame; |
3362 | 0 | while (frame) { |
3363 | 0 | frame = nsLayoutUtils::GetCrossDocParentFrame(frame); |
3364 | 0 | if (!frame) { |
3365 | 0 | break; |
3366 | 0 | } |
3367 | 0 | nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame); |
3368 | 0 | if (!scrollAncestor) { |
3369 | 0 | break; |
3370 | 0 | } |
3371 | 0 | frame = do_QueryFrame(scrollAncestor); |
3372 | 0 | MOZ_ASSERT(frame); |
3373 | 0 | if (!frame) { |
3374 | 0 | break; |
3375 | 0 | } |
3376 | 0 | MOZ_ASSERT(scrollAncestor->WantAsyncScroll() || |
3377 | 0 | frame->PresShell()->GetRootScrollFrame() == frame); |
3378 | 0 | if (nsLayoutUtils::HasDisplayPort(frame->GetContent())) { |
3379 | 0 | scrollAncestor->TriggerDisplayPortExpiration(); |
3380 | 0 | // Stop after the first trigger. If it failed, there's no point in |
3381 | 0 | // continuing because all the rest of the frames we encounter are going |
3382 | 0 | // to be ancestors of |scrollAncestor| which will keep its displayport. |
3383 | 0 | // If the trigger succeeded, we stop because when the trigger executes |
3384 | 0 | // it will call this function again to trigger the next ancestor up the |
3385 | 0 | // chain. |
3386 | 0 | break; |
3387 | 0 | } |
3388 | 0 | } |
3389 | 0 | } |
3390 | | |
3391 | | void |
3392 | | nsLayoutUtils::AddExtraBackgroundItems(nsDisplayListBuilder& aBuilder, |
3393 | | nsDisplayList& aList, |
3394 | | nsIFrame* aFrame, |
3395 | | const nsRect& aCanvasArea, |
3396 | | const nsRegion& aVisibleRegion, |
3397 | | nscolor aBackstop) |
3398 | 0 | { |
3399 | 0 | LayoutFrameType frameType = aFrame->Type(); |
3400 | 0 | nsPresContext* presContext = aFrame->PresContext(); |
3401 | 0 | nsIPresShell* presShell = presContext->PresShell(); |
3402 | 0 |
|
3403 | 0 | // For the viewport frame in print preview/page layout we want to paint |
3404 | 0 | // the grey background behind the page, not the canvas color. |
3405 | 0 | if (frameType == LayoutFrameType::Viewport && |
3406 | 0 | nsLayoutUtils::NeedsPrintPreviewBackground(presContext)) { |
3407 | 0 | nsRect bounds = nsRect(aBuilder.ToReferenceFrame(aFrame), |
3408 | 0 | aFrame->GetSize()); |
3409 | 0 | nsDisplayListBuilder::AutoBuildingDisplayList |
3410 | 0 | buildingDisplayList(&aBuilder, aFrame, bounds, bounds, false); |
3411 | 0 | presShell->AddPrintPreviewBackgroundItem(aBuilder, aList, aFrame, bounds); |
3412 | 0 | } else if (frameType != LayoutFrameType::Page) { |
3413 | 0 | // For printing, this function is first called on an nsPageFrame, which |
3414 | 0 | // creates a display list with a PageContent item. The PageContent item's |
3415 | 0 | // paint function calls this function on the nsPageFrame's child which is |
3416 | 0 | // an nsPageContentFrame. We only want to add the canvas background color |
3417 | 0 | // item once, for the nsPageContentFrame. |
3418 | 0 |
|
3419 | 0 | // Add the canvas background color to the bottom of the list. This |
3420 | 0 | // happens after we've built the list so that AddCanvasBackgroundColorItem |
3421 | 0 | // can monkey with the contents if necessary. |
3422 | 0 | nsRect canvasArea = aVisibleRegion.GetBounds(); |
3423 | 0 | canvasArea.IntersectRect(aCanvasArea, canvasArea); |
3424 | 0 | nsDisplayListBuilder::AutoBuildingDisplayList |
3425 | 0 | buildingDisplayList(&aBuilder, aFrame, canvasArea, canvasArea, false); |
3426 | 0 | presShell->AddCanvasBackgroundColorItem( |
3427 | 0 | aBuilder, aList, aFrame, canvasArea, aBackstop); |
3428 | 0 | } |
3429 | 0 | } |
3430 | | |
3431 | | /** |
3432 | | * Returns a retained display list builder for frame |aFrame|. If there is no |
3433 | | * retained display list builder property set for the frame, and if the flag |
3434 | | * |aRetainingEnabled| is true, a new retained display list builder is created, |
3435 | | * stored as a property for the frame, and returned. |
3436 | | */ |
3437 | | static RetainedDisplayListBuilder* |
3438 | | GetOrCreateRetainedDisplayListBuilder(nsIFrame* aFrame, bool aRetainingEnabled, |
3439 | | bool aBuildCaret) |
3440 | 0 | { |
3441 | 0 | RetainedDisplayListBuilder* retainedBuilder = |
3442 | 0 | aFrame->GetProperty(RetainedDisplayListBuilder::Cached()); |
3443 | 0 |
|
3444 | 0 | if (retainedBuilder) { |
3445 | 0 | return retainedBuilder; |
3446 | 0 | } |
3447 | 0 | |
3448 | 0 | if (aRetainingEnabled) { |
3449 | 0 | retainedBuilder = |
3450 | 0 | new RetainedDisplayListBuilder(aFrame, nsDisplayListBuilderMode::PAINTING, |
3451 | 0 | aBuildCaret); |
3452 | 0 | aFrame->SetProperty(RetainedDisplayListBuilder::Cached(), retainedBuilder); |
3453 | 0 | } |
3454 | 0 |
|
3455 | 0 | return retainedBuilder; |
3456 | 0 | } |
3457 | | |
3458 | | nsresult |
3459 | | nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame, |
3460 | | const nsRegion& aDirtyRegion, nscolor aBackstop, |
3461 | | nsDisplayListBuilderMode aBuilderMode, |
3462 | | PaintFrameFlags aFlags) |
3463 | 0 | { |
3464 | 0 | AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame", GRAPHICS); |
3465 | 0 | typedef RetainedDisplayListBuilder::PartialUpdateResult PartialUpdateResult; |
3466 | 0 |
|
3467 | | #ifdef MOZ_DUMP_PAINTING |
3468 | | if (!gPaintCountStack) { |
3469 | | gPaintCountStack = new nsTArray<int>(); |
3470 | | ClearOnShutdown(&gPaintCountStack); |
3471 | | |
3472 | | gPaintCountStack->AppendElement(0); |
3473 | | } |
3474 | | ++gPaintCountStack->LastElement(); |
3475 | | AutoNestedPaintCount nestedPaintCount; |
3476 | | #endif |
3477 | |
|
3478 | 0 | if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) { |
3479 | 0 | nsView* view = aFrame->GetView(); |
3480 | 0 | if (!(view && view->GetWidget() && GetDisplayRootFrame(aFrame) == aFrame)) { |
3481 | 0 | aFlags &= ~PaintFrameFlags::PAINT_WIDGET_LAYERS; |
3482 | 0 | NS_ASSERTION(aRenderingContext, "need a rendering context"); |
3483 | 0 | } |
3484 | 0 | } |
3485 | 0 |
|
3486 | 0 | nsPresContext* presContext = aFrame->PresContext(); |
3487 | 0 | nsIPresShell* presShell = presContext->PresShell(); |
3488 | 0 | nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); |
3489 | 0 | if (!rootPresContext) { |
3490 | 0 | return NS_OK; |
3491 | 0 | } |
3492 | 0 | |
3493 | 0 | TimeStamp startBuildDisplayList = TimeStamp::Now(); |
3494 | 0 |
|
3495 | 0 | const bool buildCaret = !(aFlags & PaintFrameFlags::PAINT_HIDE_CARET); |
3496 | 0 | const bool isForPainting = (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) && |
3497 | 0 | aBuilderMode == nsDisplayListBuilderMode::PAINTING; |
3498 | 0 |
|
3499 | 0 | // Only allow retaining for painting when preffed on, and for root frames (since |
3500 | 0 | // the modified frame tracking is per-root-frame). |
3501 | 0 | const bool retainingEnabled = |
3502 | 0 | isForPainting && AreRetainedDisplayListsEnabled() && !aFrame->GetParent(); |
3503 | 0 |
|
3504 | 0 | RetainedDisplayListBuilder* retainedBuilder = |
3505 | 0 | GetOrCreateRetainedDisplayListBuilder(aFrame, retainingEnabled, buildCaret); |
3506 | 0 |
|
3507 | 0 | // Only use the retained display list builder if the retaining is currently |
3508 | 0 | // enabled. This check is needed because it is possible that the pref has been |
3509 | 0 | // disabled after creating the retained display list builder. |
3510 | 0 | const bool useRetainedBuilder = retainedBuilder && retainingEnabled; |
3511 | 0 |
|
3512 | 0 | Maybe<nsDisplayListBuilder> nonRetainedBuilder; |
3513 | 0 | Maybe<nsDisplayList> nonRetainedList; |
3514 | 0 | nsDisplayListBuilder* builderPtr = nullptr; |
3515 | 0 | nsDisplayList* listPtr = nullptr; |
3516 | 0 |
|
3517 | 0 | if (useRetainedBuilder) { |
3518 | 0 | builderPtr = retainedBuilder->Builder(); |
3519 | 0 | listPtr = retainedBuilder->List(); |
3520 | 0 | } else { |
3521 | 0 | nonRetainedBuilder.emplace(aFrame, aBuilderMode, buildCaret); |
3522 | 0 | builderPtr = nonRetainedBuilder.ptr(); |
3523 | 0 | nonRetainedList.emplace(); |
3524 | 0 | listPtr = nonRetainedList.ptr(); |
3525 | 0 | } |
3526 | 0 |
|
3527 | 0 | // Retained builder exists, but display list retaining is disabled. |
3528 | 0 | if (!useRetainedBuilder && retainedBuilder) { |
3529 | 0 | // Clear the modified frames lists and frame properties. |
3530 | 0 | retainedBuilder->ClearFramesWithProps(); |
3531 | 0 |
|
3532 | 0 | // Clear the retained display list. |
3533 | 0 | retainedBuilder->List()->DeleteAll(retainedBuilder->Builder()); |
3534 | 0 | } |
3535 | 0 |
|
3536 | 0 | nsDisplayListBuilder& builder = *builderPtr; |
3537 | 0 | nsDisplayList& list = *listPtr; |
3538 | 0 |
|
3539 | 0 | builder.BeginFrame(); |
3540 | 0 |
|
3541 | 0 | if (aFlags & PaintFrameFlags::PAINT_IN_TRANSFORM) { |
3542 | 0 | builder.SetInTransform(true); |
3543 | 0 | } |
3544 | 0 | if (aFlags & PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES) { |
3545 | 0 | builder.SetSyncDecodeImages(true); |
3546 | 0 | } |
3547 | 0 | if (aFlags & (PaintFrameFlags::PAINT_WIDGET_LAYERS | |
3548 | 0 | PaintFrameFlags::PAINT_TO_WINDOW)) { |
3549 | 0 | builder.SetPaintingToWindow(true); |
3550 | 0 | } |
3551 | 0 | if (aFlags & PaintFrameFlags::PAINT_IGNORE_SUPPRESSION) { |
3552 | 0 | builder.IgnorePaintSuppression(); |
3553 | 0 | } |
3554 | 0 |
|
3555 | 0 | nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); |
3556 | 0 | if (rootScrollFrame && !aFrame->GetParent()) { |
3557 | 0 | nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable(); |
3558 | 0 | MOZ_ASSERT(rootScrollableFrame); |
3559 | 0 | nsRect displayPortBase = aFrame->GetVisualOverflowRectRelativeToSelf(); |
3560 | 0 | nsRect temp = displayPortBase; |
3561 | 0 | Unused << rootScrollableFrame->DecideScrollableLayer(&builder, &displayPortBase, &temp, |
3562 | 0 | /* aSetBase = */ true); |
3563 | 0 | } |
3564 | 0 |
|
3565 | 0 | nsRegion visibleRegion; |
3566 | 0 | if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) { |
3567 | 0 | // This layer tree will be reused, so we'll need to calculate it |
3568 | 0 | // for the whole "visible" area of the window |
3569 | 0 | // |
3570 | 0 | // |ignoreViewportScrolling| and |usingDisplayPort| are persistent |
3571 | 0 | // document-rendering state. We rely on PresShell to flush |
3572 | 0 | // retained layers as needed when that persistent state changes. |
3573 | 0 | visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf(); |
3574 | 0 | } else { |
3575 | 0 | visibleRegion = aDirtyRegion; |
3576 | 0 | } |
3577 | 0 |
|
3578 | 0 | // If the root has embedded plugins, flag the builder so we know we'll need |
3579 | 0 | // to update plugin geometry after painting. |
3580 | 0 | if ((aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) && |
3581 | 0 | !(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE) && |
3582 | 0 | rootPresContext->NeedToComputePluginGeometryUpdates()) { |
3583 | 0 | builder.SetWillComputePluginGeometry(true); |
3584 | 0 |
|
3585 | 0 | // Disable partial updates for this paint as the list we're about to |
3586 | 0 | // build has plugin-specific differences that won't trigger invalidations. |
3587 | 0 | builder.SetDisablePartialUpdates(true); |
3588 | 0 | } |
3589 | 0 |
|
3590 | 0 | nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize()); |
3591 | 0 | bool ignoreViewportScrolling = |
3592 | 0 | aFrame->GetParent() ? false : presShell->IgnoringViewportScrolling(); |
3593 | 0 | if (ignoreViewportScrolling && rootScrollFrame) { |
3594 | 0 | nsIScrollableFrame* rootScrollableFrame = |
3595 | 0 | presShell->GetRootScrollFrameAsScrollable(); |
3596 | 0 | if (aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE) { |
3597 | 0 | // Make visibleRegion and aRenderingContext relative to the |
3598 | 0 | // scrolled frame instead of the root frame. |
3599 | 0 | nsPoint pos = rootScrollableFrame->GetScrollPosition(); |
3600 | 0 | visibleRegion.MoveBy(-pos); |
3601 | 0 | if (aRenderingContext) { |
3602 | 0 | gfxPoint devPixelOffset = |
3603 | 0 | nsLayoutUtils::PointToGfxPoint(pos, |
3604 | 0 | presContext->AppUnitsPerDevPixel()); |
3605 | 0 | aRenderingContext->SetMatrixDouble( |
3606 | 0 | aRenderingContext->CurrentMatrixDouble().PreTranslate(devPixelOffset)); |
3607 | 0 | } |
3608 | 0 | } |
3609 | 0 | builder.SetIgnoreScrollFrame(rootScrollFrame); |
3610 | 0 |
|
3611 | 0 | nsCanvasFrame* canvasFrame = |
3612 | 0 | do_QueryFrame(rootScrollableFrame->GetScrolledFrame()); |
3613 | 0 | if (canvasFrame) { |
3614 | 0 | // Use UnionRect here to ensure that areas where the scrollbars |
3615 | 0 | // were are still filled with the background color. |
3616 | 0 | canvasArea.UnionRect(canvasArea, |
3617 | 0 | canvasFrame->CanvasArea() + builder.ToReferenceFrame(canvasFrame)); |
3618 | 0 | } |
3619 | 0 | } |
3620 | 0 |
|
3621 | 0 | builder.ClearHaveScrollableDisplayPort(); |
3622 | 0 | if (builder.IsPaintingToWindow()) { |
3623 | 0 | MaybeCreateDisplayPortInFirstScrollFrameEncountered(aFrame, builder); |
3624 | 0 | } |
3625 | 0 |
|
3626 | 0 | nsRect visibleRect = visibleRegion.GetBounds(); |
3627 | 0 | PartialUpdateResult updateState = PartialUpdateResult::Failed; |
3628 | 0 |
|
3629 | 0 | { |
3630 | 0 | AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame:BuildDisplayList", |
3631 | 0 | GRAPHICS); |
3632 | 0 | AUTO_PROFILER_TRACING("Paint", "DisplayList"); |
3633 | 0 |
|
3634 | 0 | PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList); |
3635 | 0 | TimeStamp dlStart = TimeStamp::Now(); |
3636 | 0 |
|
3637 | 0 | { |
3638 | 0 | // If a scrollable container layer is created in nsDisplayList::PaintForFrame, |
3639 | 0 | // it will be the scroll parent for display items that are built in the |
3640 | 0 | // BuildDisplayListForStackingContext call below. We need to set the scroll |
3641 | 0 | // parent on the display list builder while we build those items, so that they |
3642 | 0 | // can pick up their scroll parent's id. |
3643 | 0 | ViewID id = FrameMetrics::NULL_SCROLL_ID; |
3644 | 0 | if (ignoreViewportScrolling && presContext->IsRootContentDocument()) { |
3645 | 0 | if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { |
3646 | 0 | if (nsIContent* content = rootScrollFrame->GetContent()) { |
3647 | 0 | id = nsLayoutUtils::FindOrCreateIDFor(content); |
3648 | 0 | } |
3649 | 0 | } |
3650 | 0 | } |
3651 | 0 | else if (presShell->GetDocument() && presShell->GetDocument()->IsRootDisplayDocument() |
3652 | 0 | && !presShell->GetRootScrollFrame()) { |
3653 | 0 | // In cases where the root document is a XUL document, we want to take |
3654 | 0 | // the ViewID from the root element, as that will be the ViewID of the |
3655 | 0 | // root APZC in the tree. Skip doing this in cases where we know |
3656 | 0 | // nsGfxScrollFrame::BuilDisplayList will do it instead. |
3657 | 0 | if (dom::Element* element = presShell->GetDocument()->GetDocumentElement()) { |
3658 | 0 | id = nsLayoutUtils::FindOrCreateIDFor(element); |
3659 | 0 | } |
3660 | 0 | } |
3661 | 0 |
|
3662 | 0 | nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id); |
3663 | 0 |
|
3664 | 0 | builder.SetVisibleRect(visibleRect); |
3665 | 0 | builder.SetIsBuilding(true); |
3666 | 0 | builder.SetAncestorHasApzAwareEventHandler( |
3667 | 0 | gfxPlatform::AsyncPanZoomEnabled() && |
3668 | 0 | nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell)); |
3669 | 0 |
|
3670 | 0 | DisplayListChecker beforeMergeChecker; |
3671 | 0 | DisplayListChecker toBeMergedChecker; |
3672 | 0 | DisplayListChecker afterMergeChecker; |
3673 | 0 |
|
3674 | 0 | // Attempt to do a partial build and merge into the existing list. |
3675 | 0 | // This calls BuildDisplayListForStacking context on a subset of the |
3676 | 0 | // viewport. |
3677 | 0 | if (useRetainedBuilder) { |
3678 | 0 | if (gfxPrefs::LayoutVerifyRetainDisplayList()) { |
3679 | 0 | beforeMergeChecker.Set(&list, "BM"); |
3680 | 0 | } |
3681 | 0 | updateState = retainedBuilder->AttemptPartialUpdate( |
3682 | 0 | aBackstop, beforeMergeChecker ? &toBeMergedChecker : nullptr); |
3683 | 0 | if ((updateState != PartialUpdateResult::Failed) && beforeMergeChecker) { |
3684 | 0 | afterMergeChecker.Set(&list, "AM"); |
3685 | 0 | } |
3686 | 0 | } |
3687 | 0 |
|
3688 | 0 | if ((updateState != PartialUpdateResult::Failed) && |
3689 | 0 | (gfxPrefs::LayoutDisplayListBuildTwice() || afterMergeChecker)) { |
3690 | 0 | updateState = PartialUpdateResult::Failed; |
3691 | 0 | if (gfxPrefs::LayersDrawFPS()) { |
3692 | 0 | if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) { |
3693 | 0 | if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) { |
3694 | 0 | pt->dl2Ms() = (TimeStamp::Now() - dlStart).ToMilliseconds(); |
3695 | 0 | } |
3696 | 0 | } |
3697 | 0 | } |
3698 | 0 | dlStart = TimeStamp::Now(); |
3699 | 0 | } |
3700 | 0 |
|
3701 | 0 | if (updateState == PartialUpdateResult::Failed) { |
3702 | 0 | list.DeleteAll(&builder); |
3703 | 0 |
|
3704 | 0 | builder.ClearRetainedWindowRegions(); |
3705 | 0 | builder.ClearWillChangeBudget(); |
3706 | 0 |
|
3707 | 0 | builder.EnterPresShell(aFrame); |
3708 | 0 | builder.SetDirtyRect(visibleRect); |
3709 | 0 | aFrame->BuildDisplayListForStackingContext(&builder, &list); |
3710 | 0 | AddExtraBackgroundItems( |
3711 | 0 | builder, list, aFrame, canvasArea, visibleRegion, aBackstop); |
3712 | 0 |
|
3713 | 0 | builder.LeavePresShell(aFrame, &list); |
3714 | 0 | updateState = PartialUpdateResult::Updated; |
3715 | 0 |
|
3716 | 0 | if (afterMergeChecker) { |
3717 | 0 | DisplayListChecker nonRetainedChecker(&list, "NR"); |
3718 | 0 | std::stringstream ss; |
3719 | 0 | ss << "**** Differences between retained-after-merged (AM) and " |
3720 | 0 | << "non-retained (NR) display lists:"; |
3721 | 0 | if (!nonRetainedChecker.CompareList(afterMergeChecker, ss)) { |
3722 | 0 | ss << "\n\n*** non-retained display items:"; |
3723 | 0 | nonRetainedChecker.Dump(ss); |
3724 | 0 | ss << "\n\n*** before-merge retained display items:"; |
3725 | 0 | beforeMergeChecker.Dump(ss); |
3726 | 0 | ss << "\n\n*** to-be-merged retained display items:"; |
3727 | 0 | toBeMergedChecker.Dump(ss); |
3728 | 0 | ss << "\n\n*** after-merge retained display items:"; |
3729 | 0 | afterMergeChecker.Dump(ss); |
3730 | 0 | fprintf(stderr, "%s\n\n", ss.str().c_str()); |
3731 | | #ifdef DEBUG_FRAME_DUMP |
3732 | | fprintf(stderr, "*** Frame tree:\n"); |
3733 | | aFrame->DumpFrameTree(); |
3734 | | #endif |
3735 | | } |
3736 | 0 | } |
3737 | 0 | } |
3738 | 0 | } |
3739 | 0 |
|
3740 | 0 | builder.SetIsBuilding(false); |
3741 | 0 | builder.IncrementPresShellPaintCount(presShell); |
3742 | 0 |
|
3743 | 0 | if (gfxPrefs::LayersDrawFPS()) { |
3744 | 0 | if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) { |
3745 | 0 | if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) { |
3746 | 0 | pt->dlMs() = (TimeStamp::Now() - dlStart).ToMilliseconds(); |
3747 | 0 | } |
3748 | 0 | } |
3749 | 0 | } |
3750 | 0 | } |
3751 | 0 |
|
3752 | 0 | MOZ_ASSERT(updateState != PartialUpdateResult::Failed); |
3753 | 0 | builder.Check(); |
3754 | 0 |
|
3755 | 0 | Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME, |
3756 | 0 | startBuildDisplayList); |
3757 | 0 |
|
3758 | 0 | bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint(); |
3759 | | #ifdef MOZ_DUMP_PAINTING |
3760 | | FILE* savedDumpFile = gfxUtils::sDumpPaintFile; |
3761 | | #endif |
3762 | |
|
3763 | 0 | UniquePtr<std::stringstream> ss; |
3764 | 0 | if (consoleNeedsDisplayList) { |
3765 | 0 | ss = MakeUnique<std::stringstream>(); |
3766 | | #ifdef MOZ_DUMP_PAINTING |
3767 | | if (gfxEnv::DumpPaintToFile()) { |
3768 | | nsCString string("dump-"); |
3769 | | // Include the process ID in the dump file name, to make sure that in an |
3770 | | // e10s setup different processes don't clobber each other's dump files. |
3771 | | string.AppendInt(getpid()); |
3772 | | for (int paintCount : *gPaintCountStack) { |
3773 | | string.AppendLiteral("-"); |
3774 | | string.AppendInt(paintCount); |
3775 | | } |
3776 | | string.AppendLiteral(".html"); |
3777 | | gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w"); |
3778 | | } else { |
3779 | | gfxUtils::sDumpPaintFile = stderr; |
3780 | | } |
3781 | | if (gfxEnv::DumpPaintToFile()) { |
3782 | | *ss << "<html><head><script>\n" |
3783 | | "var array = {};\n" |
3784 | | "function ViewImage(index) { \n" |
3785 | | " var image = document.getElementById(index);\n" |
3786 | | " if (image.src) {\n" |
3787 | | " image.removeAttribute('src');\n" |
3788 | | " } else {\n" |
3789 | | " image.src = array[index];\n" |
3790 | | " }\n" |
3791 | | "}</script></head><body>"; |
3792 | | } |
3793 | | #endif |
3794 | | *ss << nsPrintfCString("Painting --- before optimization (dirty %d,%d,%d,%d):\n", |
3795 | 0 | visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height).get(); |
3796 | 0 | nsFrame::PrintDisplayList(&builder, list, *ss, gfxEnv::DumpPaintToFile()); |
3797 | 0 |
|
3798 | 0 | if (gfxEnv::DumpPaint() || gfxEnv::DumpPaintItems()) { |
3799 | 0 | // Flush stream now to avoid reordering dump output relative to |
3800 | 0 | // messages dumped by PaintRoot below. |
3801 | 0 | fprint_stderr(gfxUtils::sDumpPaintFile, *ss); |
3802 | 0 | ss = MakeUnique<std::stringstream>(); |
3803 | 0 | } |
3804 | 0 | } |
3805 | 0 |
|
3806 | 0 | uint32_t flags = nsDisplayList::PAINT_DEFAULT; |
3807 | 0 | if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) { |
3808 | 0 | flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS; |
3809 | 0 | if (!(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE)) { |
3810 | 0 | nsIWidget *widget = aFrame->GetNearestWidget(); |
3811 | 0 | if (widget) { |
3812 | 0 | // If we're finished building display list items for painting of the outermost |
3813 | 0 | // pres shell, notify the widget about any toolbars we've encountered. |
3814 | 0 | widget->UpdateThemeGeometries(builder.GetThemeGeometries()); |
3815 | 0 | } |
3816 | 0 | } |
3817 | 0 | } |
3818 | 0 | if (aFlags & PaintFrameFlags::PAINT_EXISTING_TRANSACTION) { |
3819 | 0 | flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION; |
3820 | 0 | } |
3821 | 0 | if (aFlags & PaintFrameFlags::PAINT_NO_COMPOSITE) { |
3822 | 0 | flags |= nsDisplayList::PAINT_NO_COMPOSITE; |
3823 | 0 | } |
3824 | 0 | if (aFlags & PaintFrameFlags::PAINT_COMPRESSED) { |
3825 | 0 | flags |= nsDisplayList::PAINT_COMPRESSED; |
3826 | 0 | } |
3827 | 0 | if (updateState == PartialUpdateResult::NoChange && |
3828 | 0 | !aRenderingContext) { |
3829 | 0 | flags |= nsDisplayList::PAINT_IDENTICAL_DISPLAY_LIST; |
3830 | 0 | } |
3831 | 0 |
|
3832 | 0 | TimeStamp paintStart = TimeStamp::Now(); |
3833 | 0 | RefPtr<LayerManager> layerManager |
3834 | 0 | = list.PaintRoot(&builder, aRenderingContext, flags); |
3835 | 0 | Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME, |
3836 | 0 | paintStart); |
3837 | 0 |
|
3838 | 0 | builder.Check(); |
3839 | 0 |
|
3840 | 0 | if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) { |
3841 | 0 | TimeStamp now = TimeStamp::Now(); |
3842 | 0 | float rasterizeTime = (now - paintStart).ToMilliseconds(); |
3843 | 0 | uint32_t pixelCount = layerManager->GetAndClearPaintedPixelCount(); |
3844 | 0 | static std::vector<std::pair<TimeStamp, uint32_t>> history; |
3845 | 0 | if (pixelCount) { |
3846 | 0 | history.push_back(std::make_pair(now, pixelCount)); |
3847 | 0 | } |
3848 | 0 | uint32_t paintedInLastSecond = 0; |
3849 | 0 | for (auto i = history.begin(); i != history.end(); i++) { |
3850 | 0 | if ((now - i->first).ToMilliseconds() > 1000.0f) { |
3851 | 0 | // more than 1000ms ago, don't count it |
3852 | 0 | continue; |
3853 | 0 | } |
3854 | 0 | if (paintedInLastSecond == 0) { |
3855 | 0 | // This is the first one in the last 1000ms, so drop everything earlier |
3856 | 0 | history.erase(history.begin(), i); |
3857 | 0 | i = history.begin(); |
3858 | 0 | } |
3859 | 0 | paintedInLastSecond += i->second; |
3860 | 0 | MOZ_ASSERT(paintedInLastSecond); // all historical pixel counts are > 0 |
3861 | 0 | } |
3862 | 0 | printf_stderr("Painted %u pixels in %fms (%u in the last 1000ms)\n", |
3863 | 0 | pixelCount, rasterizeTime, paintedInLastSecond); |
3864 | 0 | } |
3865 | 0 |
|
3866 | 0 | if (consoleNeedsDisplayList) { |
3867 | 0 | *ss << "Painting --- after optimization:\n"; |
3868 | 0 | nsFrame::PrintDisplayList(&builder, list, *ss, gfxEnv::DumpPaintToFile()); |
3869 | 0 |
|
3870 | 0 | *ss << "Painting --- layer tree:\n"; |
3871 | 0 | if (layerManager) { |
3872 | 0 | FrameLayerBuilder::DumpRetainedLayerTree(layerManager, *ss, |
3873 | 0 | gfxEnv::DumpPaintToFile()); |
3874 | 0 | } |
3875 | 0 |
|
3876 | 0 | fprint_stderr(gfxUtils::sDumpPaintFile, *ss); |
3877 | 0 |
|
3878 | | #ifdef MOZ_DUMP_PAINTING |
3879 | | if (gfxEnv::DumpPaintToFile()) { |
3880 | | *ss << "</body></html>"; |
3881 | | } |
3882 | | if (gfxEnv::DumpPaintToFile()) { |
3883 | | fclose(gfxUtils::sDumpPaintFile); |
3884 | | } |
3885 | | gfxUtils::sDumpPaintFile = savedDumpFile; |
3886 | | #endif |
3887 | |
|
3888 | 0 | std::stringstream lsStream; |
3889 | 0 | nsFrame::PrintDisplayList(&builder, list, lsStream); |
3890 | 0 | if (layerManager->GetRoot()) { |
3891 | 0 | layerManager->GetRoot()->SetDisplayListLog(lsStream.str().c_str()); |
3892 | 0 | } |
3893 | 0 | } |
3894 | 0 |
|
3895 | | #ifdef MOZ_DUMP_PAINTING |
3896 | | if (gfxPrefs::DumpClientLayers()) { |
3897 | | std::stringstream ss; |
3898 | | FrameLayerBuilder::DumpRetainedLayerTree(layerManager, ss, false); |
3899 | | print_stderr(ss); |
3900 | | } |
3901 | | #endif |
3902 | |
|
3903 | 0 | // Update the widget's opaque region information. This sets |
3904 | 0 | // glass boundaries on Windows. Also set up the window dragging region |
3905 | 0 | // and plugin clip regions and bounds. |
3906 | 0 | if ((aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) && |
3907 | 0 | !(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE)) { |
3908 | 0 | nsIWidget *widget = aFrame->GetNearestWidget(); |
3909 | 0 | if (widget) { |
3910 | 0 | nsRegion opaqueRegion; |
3911 | 0 | opaqueRegion.And(builder.GetWindowExcludeGlassRegion(), builder.GetWindowOpaqueRegion()); |
3912 | 0 | widget->UpdateOpaqueRegion( |
3913 | 0 | LayoutDeviceIntRegion::FromUnknownRegion( |
3914 | 0 | opaqueRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel()))); |
3915 | 0 |
|
3916 | 0 | widget->UpdateWindowDraggingRegion(builder.GetWindowDraggingRegion()); |
3917 | 0 | } |
3918 | 0 | } |
3919 | 0 |
|
3920 | 0 | if (builder.WillComputePluginGeometry()) { |
3921 | 0 | // For single process compute and apply plugin geometry updates to plugin |
3922 | 0 | // windows, then request composition. For content processes skip eveything |
3923 | 0 | // except requesting composition. Geometry updates were calculated and |
3924 | 0 | // shipped to the chrome process in nsDisplayList when the layer |
3925 | 0 | // transaction completed. |
3926 | 0 | if (XRE_IsParentProcess()) { |
3927 | 0 | rootPresContext->ComputePluginGeometryUpdates(aFrame, &builder, &list); |
3928 | 0 | // We're not going to get a WillPaintWindow event here if we didn't do |
3929 | 0 | // widget invalidation, so just apply the plugin geometry update here |
3930 | 0 | // instead. We could instead have the compositor send back an equivalent |
3931 | 0 | // to WillPaintWindow, but it should be close enough to now not to matter. |
3932 | 0 | if (layerManager && !layerManager->NeedsWidgetInvalidation()) { |
3933 | 0 | rootPresContext->ApplyPluginGeometryUpdates(); |
3934 | 0 | } |
3935 | 0 | } |
3936 | 0 |
|
3937 | 0 | // We told the compositor thread not to composite when it received the |
3938 | 0 | // transaction because we wanted to update plugins first. Schedule the |
3939 | 0 | // composite now. |
3940 | 0 | if (layerManager) { |
3941 | 0 | layerManager->ScheduleComposite(); |
3942 | 0 | } |
3943 | 0 |
|
3944 | 0 | // Disable partial updates for the following paint as well, as we now have |
3945 | 0 | // a plugin-specific display list. |
3946 | 0 | builder.SetDisablePartialUpdates(true); |
3947 | 0 | } |
3948 | 0 |
|
3949 | 0 | builder.Check(); |
3950 | 0 |
|
3951 | 0 | { |
3952 | 0 | AUTO_PROFILER_TRACING("Paint", "DisplayListResources"); |
3953 | 0 |
|
3954 | 0 | // Flush the list so we don't trigger the IsEmpty-on-destruction assertion |
3955 | 0 | if (!useRetainedBuilder) { |
3956 | 0 | list.DeleteAll(&builder); |
3957 | 0 | } |
3958 | 0 |
|
3959 | 0 | builder.EndFrame(); |
3960 | 0 | } |
3961 | 0 | return NS_OK; |
3962 | 0 | } |
3963 | | |
3964 | | /** |
3965 | | * Uses a binary search for find where the cursor falls in the line of text |
3966 | | * It also keeps track of the part of the string that has already been measured |
3967 | | * so it doesn't have to keep measuring the same text over and over |
3968 | | * |
3969 | | * @param "aBaseWidth" contains the width in twips of the portion |
3970 | | * of the text that has already been measured, and aBaseInx contains |
3971 | | * the index of the text that has already been measured. |
3972 | | * |
3973 | | * @param aTextWidth returns the (in twips) the length of the text that falls |
3974 | | * before the cursor aIndex contains the index of the text where the cursor falls |
3975 | | */ |
3976 | | bool |
3977 | | nsLayoutUtils::BinarySearchForPosition(DrawTarget* aDrawTarget, |
3978 | | nsFontMetrics& aFontMetrics, |
3979 | | const char16_t* aText, |
3980 | | int32_t aBaseWidth, |
3981 | | int32_t aBaseInx, |
3982 | | int32_t aStartInx, |
3983 | | int32_t aEndInx, |
3984 | | int32_t aCursorPos, |
3985 | | int32_t& aIndex, |
3986 | | int32_t& aTextWidth) |
3987 | 0 | { |
3988 | 0 | int32_t range = aEndInx - aStartInx; |
3989 | 0 | if ((range == 1) || (range == 2 && NS_IS_HIGH_SURROGATE(aText[aStartInx]))) { |
3990 | 0 | aIndex = aStartInx + aBaseInx; |
3991 | 0 | aTextWidth = nsLayoutUtils::AppUnitWidthOfString(aText, aIndex, |
3992 | 0 | aFontMetrics, aDrawTarget); |
3993 | 0 | return true; |
3994 | 0 | } |
3995 | 0 | |
3996 | 0 | int32_t inx = aStartInx + (range / 2); |
3997 | 0 |
|
3998 | 0 | // Make sure we don't leave a dangling low surrogate |
3999 | 0 | if (NS_IS_HIGH_SURROGATE(aText[inx-1])) |
4000 | 0 | inx++; |
4001 | 0 |
|
4002 | 0 | int32_t textWidth = nsLayoutUtils::AppUnitWidthOfString(aText, inx, |
4003 | 0 | aFontMetrics, |
4004 | 0 | aDrawTarget); |
4005 | 0 |
|
4006 | 0 | int32_t fullWidth = aBaseWidth + textWidth; |
4007 | 0 | if (fullWidth == aCursorPos) { |
4008 | 0 | aTextWidth = textWidth; |
4009 | 0 | aIndex = inx; |
4010 | 0 | return true; |
4011 | 0 | } else if (aCursorPos < fullWidth) { |
4012 | 0 | aTextWidth = aBaseWidth; |
4013 | 0 | if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth, |
4014 | 0 | aBaseInx, aStartInx, inx, aCursorPos, aIndex, |
4015 | 0 | aTextWidth)) { |
4016 | 0 | return true; |
4017 | 0 | } |
4018 | 0 | } else { |
4019 | 0 | aTextWidth = fullWidth; |
4020 | 0 | if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth, |
4021 | 0 | aBaseInx, inx, aEndInx, aCursorPos, aIndex, |
4022 | 0 | aTextWidth)) { |
4023 | 0 | return true; |
4024 | 0 | } |
4025 | 0 | } |
4026 | 0 | return false; |
4027 | 0 | } |
4028 | | |
4029 | | void |
4030 | | nsLayoutUtils::AddBoxesForFrame(nsIFrame* aFrame, |
4031 | | nsLayoutUtils::BoxCallback* aCallback) |
4032 | 0 | { |
4033 | 0 | nsAtom* pseudoType = aFrame->Style()->GetPseudo(); |
4034 | 0 |
|
4035 | 0 | if (pseudoType == nsCSSAnonBoxes::tableWrapper()) { |
4036 | 0 | AddBoxesForFrame(aFrame->PrincipalChildList().FirstChild(), aCallback); |
4037 | 0 | if (aCallback->mIncludeCaptionBoxForTable) { |
4038 | 0 | nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild(); |
4039 | 0 | if (kid) { |
4040 | 0 | AddBoxesForFrame(kid, aCallback); |
4041 | 0 | } |
4042 | 0 | } |
4043 | 0 | } else if (pseudoType == nsCSSAnonBoxes::mozBlockInsideInlineWrapper() || |
4044 | 0 | pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock() || |
4045 | 0 | pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock()) { |
4046 | 0 | for (nsIFrame* kid : aFrame->PrincipalChildList()) { |
4047 | 0 | AddBoxesForFrame(kid, aCallback); |
4048 | 0 | } |
4049 | 0 | } else { |
4050 | 0 | aCallback->AddBox(aFrame); |
4051 | 0 | } |
4052 | 0 | } |
4053 | | |
4054 | | void |
4055 | | nsLayoutUtils::GetAllInFlowBoxes(nsIFrame* aFrame, BoxCallback* aCallback) |
4056 | 0 | { |
4057 | 0 | while (aFrame) { |
4058 | 0 | AddBoxesForFrame(aFrame, aCallback); |
4059 | 0 | aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); |
4060 | 0 | } |
4061 | 0 | } |
4062 | | |
4063 | | nsIFrame* |
4064 | | nsLayoutUtils::GetFirstNonAnonymousFrame(nsIFrame* aFrame) |
4065 | 0 | { |
4066 | 0 | while (aFrame) { |
4067 | 0 | nsAtom* pseudoType = aFrame->Style()->GetPseudo(); |
4068 | 0 |
|
4069 | 0 | if (pseudoType == nsCSSAnonBoxes::tableWrapper()) { |
4070 | 0 | nsIFrame* f = GetFirstNonAnonymousFrame(aFrame->PrincipalChildList().FirstChild()); |
4071 | 0 | if (f) { |
4072 | 0 | return f; |
4073 | 0 | } |
4074 | 0 | nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild(); |
4075 | 0 | if (kid) { |
4076 | 0 | f = GetFirstNonAnonymousFrame(kid); |
4077 | 0 | if (f) { |
4078 | 0 | return f; |
4079 | 0 | } |
4080 | 0 | } |
4081 | 0 | } else if (pseudoType == nsCSSAnonBoxes::mozBlockInsideInlineWrapper() || |
4082 | 0 | pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock() || |
4083 | 0 | pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock()) { |
4084 | 0 | for (nsIFrame* kid : aFrame->PrincipalChildList()) { |
4085 | 0 | nsIFrame* f = GetFirstNonAnonymousFrame(kid); |
4086 | 0 | if (f) { |
4087 | 0 | return f; |
4088 | 0 | } |
4089 | 0 | } |
4090 | 0 | } else { |
4091 | 0 | return aFrame; |
4092 | 0 | } |
4093 | 0 | |
4094 | 0 | aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); |
4095 | 0 | } |
4096 | 0 | return nullptr; |
4097 | 0 | } |
4098 | | |
4099 | | struct BoxToRect : public nsLayoutUtils::BoxCallback { |
4100 | | const nsIFrame* mRelativeTo; |
4101 | | nsLayoutUtils::RectCallback* mCallback; |
4102 | | uint32_t mFlags; |
4103 | | |
4104 | | BoxToRect(const nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback, |
4105 | | uint32_t aFlags) |
4106 | 0 | : mRelativeTo(aRelativeTo), mCallback(aCallback), mFlags(aFlags) {} |
4107 | | |
4108 | 0 | virtual void AddBox(nsIFrame* aFrame) override { |
4109 | 0 | nsRect r; |
4110 | 0 | nsIFrame* outer = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r); |
4111 | 0 | if (!outer) { |
4112 | 0 | outer = aFrame; |
4113 | 0 | switch (mFlags & nsLayoutUtils::RECTS_WHICH_BOX_MASK) { |
4114 | 0 | case nsLayoutUtils::RECTS_USE_CONTENT_BOX: |
4115 | 0 | r = aFrame->GetContentRectRelativeToSelf(); |
4116 | 0 | break; |
4117 | 0 | case nsLayoutUtils::RECTS_USE_PADDING_BOX: |
4118 | 0 | r = aFrame->GetPaddingRectRelativeToSelf(); |
4119 | 0 | break; |
4120 | 0 | case nsLayoutUtils::RECTS_USE_MARGIN_BOX: |
4121 | 0 | r = aFrame->GetMarginRectRelativeToSelf(); |
4122 | 0 | break; |
4123 | 0 | default: // Use the border box |
4124 | 0 | r = aFrame->GetRectRelativeToSelf(); |
4125 | 0 | } |
4126 | 0 | } |
4127 | 0 | if (mFlags & nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS) { |
4128 | 0 | r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo); |
4129 | 0 | } else { |
4130 | 0 | r += outer->GetOffsetTo(mRelativeTo); |
4131 | 0 | } |
4132 | 0 | mCallback->AddRect(r); |
4133 | 0 | } |
4134 | | }; |
4135 | | |
4136 | | struct MOZ_RAII BoxToRectAndText : public BoxToRect { |
4137 | | Sequence<nsString>* mTextList; |
4138 | | |
4139 | | BoxToRectAndText(const nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback, |
4140 | | Sequence<nsString>* aTextList, uint32_t aFlags) |
4141 | 0 | : BoxToRect(aRelativeTo, aCallback, aFlags), mTextList(aTextList) {} |
4142 | | |
4143 | 0 | static void AccumulateText(nsIFrame* aFrame, nsAString& aResult) { |
4144 | 0 | MOZ_ASSERT(aFrame); |
4145 | 0 |
|
4146 | 0 | // Get all the text in aFrame and child frames, while respecting |
4147 | 0 | // the content offsets in each of the nsTextFrames. |
4148 | 0 | if (aFrame->IsTextFrame()) { |
4149 | 0 | nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame); |
4150 | 0 |
|
4151 | 0 | nsIFrame::RenderedText renderedText = textFrame->GetRenderedText( |
4152 | 0 | textFrame->GetContentOffset(), |
4153 | 0 | textFrame->GetContentOffset() + textFrame->GetContentLength(), |
4154 | 0 | nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, |
4155 | 0 | nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); |
4156 | 0 |
|
4157 | 0 | aResult.Append(renderedText.mString); |
4158 | 0 | } |
4159 | 0 |
|
4160 | 0 | for (nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); |
4161 | 0 | child; |
4162 | 0 | child = child->GetNextSibling()) { |
4163 | 0 | AccumulateText(child, aResult); |
4164 | 0 | } |
4165 | 0 | } |
4166 | | |
4167 | 0 | virtual void AddBox(nsIFrame* aFrame) override { |
4168 | 0 | BoxToRect::AddBox(aFrame); |
4169 | 0 | if (mTextList) { |
4170 | 0 | nsString* textForFrame = mTextList->AppendElement(fallible); |
4171 | 0 | if (textForFrame) { |
4172 | 0 | AccumulateText(aFrame, *textForFrame); |
4173 | 0 | } |
4174 | 0 | } |
4175 | 0 | } |
4176 | | }; |
4177 | | |
4178 | | void |
4179 | | nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, const nsIFrame* aRelativeTo, |
4180 | | RectCallback* aCallback, uint32_t aFlags) |
4181 | 0 | { |
4182 | 0 | BoxToRect converter(aRelativeTo, aCallback, aFlags); |
4183 | 0 | GetAllInFlowBoxes(aFrame, &converter); |
4184 | 0 | } |
4185 | | |
4186 | | void |
4187 | | nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame, |
4188 | | const nsIFrame* aRelativeTo, |
4189 | | RectCallback* aCallback, |
4190 | | Sequence<nsString>* aTextList, |
4191 | | uint32_t aFlags) |
4192 | 0 | { |
4193 | 0 | BoxToRectAndText converter(aRelativeTo, aCallback, aTextList, aFlags); |
4194 | 0 | GetAllInFlowBoxes(aFrame, &converter); |
4195 | 0 | } |
4196 | | |
4197 | 0 | nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {} |
4198 | | |
4199 | 0 | void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) { |
4200 | 0 | mResultRect.UnionRect(mResultRect, aRect); |
4201 | 0 | if (!mSeenFirstRect) { |
4202 | 0 | mSeenFirstRect = true; |
4203 | 0 | mFirstRect = aRect; |
4204 | 0 | } |
4205 | 0 | } |
4206 | | |
4207 | | nsLayoutUtils::RectListBuilder::RectListBuilder(DOMRectList* aList) |
4208 | | : mRectList(aList) |
4209 | 0 | { |
4210 | 0 | } |
4211 | | |
4212 | 0 | void nsLayoutUtils::RectListBuilder::AddRect(const nsRect& aRect) { |
4213 | 0 | RefPtr<DOMRect> rect = new DOMRect(mRectList); |
4214 | 0 |
|
4215 | 0 | rect->SetLayoutRect(aRect); |
4216 | 0 | mRectList->Append(rect); |
4217 | 0 | } |
4218 | | |
4219 | | nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame) |
4220 | 0 | { |
4221 | 0 | return aFrame->PresShell()->GetRootFrame(); |
4222 | 0 | } |
4223 | | |
4224 | | nsRect |
4225 | | nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame, const nsIFrame* aRelativeTo, |
4226 | 0 | uint32_t aFlags) { |
4227 | 0 | RectAccumulator accumulator; |
4228 | 0 | GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags); |
4229 | 0 | return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect |
4230 | 0 | : accumulator.mResultRect; |
4231 | 0 | } |
4232 | | |
4233 | | nsRect |
4234 | | nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect, |
4235 | | nsIFrame* aFrame, |
4236 | | uint32_t aFlags) |
4237 | 0 | { |
4238 | 0 | const nsStyleText* textStyle = aFrame->StyleText(); |
4239 | 0 | if (!textStyle->HasTextShadow()) |
4240 | 0 | return aTextAndDecorationsRect; |
4241 | 0 | |
4242 | 0 | nsRect resultRect = aTextAndDecorationsRect; |
4243 | 0 | int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel(); |
4244 | 0 | for (uint32_t i = 0; i < textStyle->mTextShadow->Length(); ++i) { |
4245 | 0 | nsCSSShadowItem* shadow = textStyle->mTextShadow->ShadowAt(i); |
4246 | 0 | nsMargin blur = nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D); |
4247 | 0 | if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0)) |
4248 | 0 | continue; |
4249 | 0 | |
4250 | 0 | nsRect tmpRect(aTextAndDecorationsRect); |
4251 | 0 |
|
4252 | 0 | tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset)); |
4253 | 0 | tmpRect.Inflate(blur); |
4254 | 0 |
|
4255 | 0 | resultRect.UnionRect(resultRect, tmpRect); |
4256 | 0 | } |
4257 | 0 | return resultRect; |
4258 | 0 | } |
4259 | | |
4260 | | enum ObjectDimensionType { eWidth, eHeight }; |
4261 | | static nscoord |
4262 | | ComputeMissingDimension(const nsSize& aDefaultObjectSize, |
4263 | | const nsSize& aIntrinsicRatio, |
4264 | | const Maybe<nscoord>& aSpecifiedWidth, |
4265 | | const Maybe<nscoord>& aSpecifiedHeight, |
4266 | | ObjectDimensionType aDimensionToCompute) |
4267 | 0 | { |
4268 | 0 | // The "default sizing algorithm" computes the missing dimension as follows: |
4269 | 0 | // (source: http://dev.w3.org/csswg/css-images-3/#default-sizing ) |
4270 | 0 |
|
4271 | 0 | // 1. "If the object has an intrinsic aspect ratio, the missing dimension of |
4272 | 0 | // the concrete object size is calculated using the intrinsic aspect |
4273 | 0 | // ratio and the present dimension." |
4274 | 0 | if (aIntrinsicRatio.width > 0 && aIntrinsicRatio.height > 0) { |
4275 | 0 | // Fill in the missing dimension using the intrinsic aspect ratio. |
4276 | 0 | nscoord knownDimensionSize; |
4277 | 0 | float ratio; |
4278 | 0 | if (aDimensionToCompute == eWidth) { |
4279 | 0 | knownDimensionSize = *aSpecifiedHeight; |
4280 | 0 | ratio = aIntrinsicRatio.width / aIntrinsicRatio.height; |
4281 | 0 | } else { |
4282 | 0 | knownDimensionSize = *aSpecifiedWidth; |
4283 | 0 | ratio = aIntrinsicRatio.height / aIntrinsicRatio.width; |
4284 | 0 | } |
4285 | 0 | return NSCoordSaturatingNonnegativeMultiply(knownDimensionSize, ratio); |
4286 | 0 | } |
4287 | 0 |
|
4288 | 0 | // 2. "Otherwise, if the missing dimension is present in the object’s |
4289 | 0 | // intrinsic dimensions, [...]" |
4290 | 0 | // NOTE: *Skipping* this case, because we already know it's not true -- we're |
4291 | 0 | // in this function because the missing dimension is *not* present in |
4292 | 0 | // the object's intrinsic dimensions. |
4293 | 0 |
|
4294 | 0 | // 3. "Otherwise, the missing dimension of the concrete object size is taken |
4295 | 0 | // from the default object size. " |
4296 | 0 | return (aDimensionToCompute == eWidth) ? |
4297 | 0 | aDefaultObjectSize.width : aDefaultObjectSize.height; |
4298 | 0 | } |
4299 | | |
4300 | | /* |
4301 | | * This computes & returns the concrete object size of replaced content, if |
4302 | | * that content were to be rendered with "object-fit: none". (Or, if the |
4303 | | * element has neither an intrinsic height nor width, this method returns an |
4304 | | * empty Maybe<> object.) |
4305 | | * |
4306 | | * As specced... |
4307 | | * http://dev.w3.org/csswg/css-images-3/#valdef-object-fit-none |
4308 | | * ..we use "the default sizing algorithm with no specified size, |
4309 | | * and a default object size equal to the replaced element's used width and |
4310 | | * height." |
4311 | | * |
4312 | | * The default sizing algorithm is described here: |
4313 | | * http://dev.w3.org/csswg/css-images-3/#default-sizing |
4314 | | * Quotes in the function-impl are taken from that ^ spec-text. |
4315 | | * |
4316 | | * Per its final bulleted section: since there's no specified size, |
4317 | | * we run the default sizing algorithm using the object's intrinsic size in |
4318 | | * place of the specified size. But if the object has neither an intrinsic |
4319 | | * height nor an intrinsic width, then we instead return without populating our |
4320 | | * outparam, and we let the caller figure out the size (using a contain |
4321 | | * constraint). |
4322 | | */ |
4323 | | static Maybe<nsSize> |
4324 | | MaybeComputeObjectFitNoneSize(const nsSize& aDefaultObjectSize, |
4325 | | const IntrinsicSize& aIntrinsicSize, |
4326 | | const nsSize& aIntrinsicRatio) |
4327 | 0 | { |
4328 | 0 | // "If the object has an intrinsic height or width, its size is resolved as |
4329 | 0 | // if its intrinsic dimensions were given as the specified size." |
4330 | 0 | // |
4331 | 0 | // So, first we check if we have an intrinsic height and/or width: |
4332 | 0 | Maybe<nscoord> specifiedWidth; |
4333 | 0 | if (aIntrinsicSize.width.GetUnit() == eStyleUnit_Coord) { |
4334 | 0 | specifiedWidth.emplace(aIntrinsicSize.width.GetCoordValue()); |
4335 | 0 | } |
4336 | 0 |
|
4337 | 0 | Maybe<nscoord> specifiedHeight; |
4338 | 0 | if (aIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) { |
4339 | 0 | specifiedHeight.emplace(aIntrinsicSize.height.GetCoordValue()); |
4340 | 0 | } |
4341 | 0 |
|
4342 | 0 | Maybe<nsSize> noneSize; // (the value we'll return) |
4343 | 0 | if (specifiedWidth || specifiedHeight) { |
4344 | 0 | // We have at least one specified dimension; use whichever dimension is |
4345 | 0 | // specified, and compute the other one using our intrinsic ratio, or (if |
4346 | 0 | // no valid ratio) using the default object size. |
4347 | 0 | noneSize.emplace(); |
4348 | 0 |
|
4349 | 0 | noneSize->width = specifiedWidth ? |
4350 | 0 | *specifiedWidth : |
4351 | 0 | ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio, |
4352 | 0 | specifiedWidth, specifiedHeight, |
4353 | 0 | eWidth); |
4354 | 0 |
|
4355 | 0 | noneSize->height = specifiedHeight ? |
4356 | 0 | *specifiedHeight : |
4357 | 0 | ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio, |
4358 | 0 | specifiedWidth, specifiedHeight, |
4359 | 0 | eHeight); |
4360 | 0 | } |
4361 | 0 | // [else:] "Otherwise [if there's neither an intrinsic height nor width], its |
4362 | 0 | // size is resolved as a contain constraint against the default object size." |
4363 | 0 | // We'll let our caller do that, to share code & avoid redundant |
4364 | 0 | // computations; so, we return w/out populating noneSize. |
4365 | 0 | return noneSize; |
4366 | 0 | } |
4367 | | |
4368 | | // Computes the concrete object size to render into, as described at |
4369 | | // http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution |
4370 | | static nsSize |
4371 | | ComputeConcreteObjectSize(const nsSize& aConstraintSize, |
4372 | | const IntrinsicSize& aIntrinsicSize, |
4373 | | const nsSize& aIntrinsicRatio, |
4374 | | uint8_t aObjectFit) |
4375 | 0 | { |
4376 | 0 | // Handle default behavior (filling the container) w/ fast early return. |
4377 | 0 | // (Also: if there's no valid intrinsic ratio, then we have the "fill" |
4378 | 0 | // behavior & just use the constraint size.) |
4379 | 0 | if (MOZ_LIKELY(aObjectFit == NS_STYLE_OBJECT_FIT_FILL) || |
4380 | 0 | aIntrinsicRatio.width == 0 || |
4381 | 0 | aIntrinsicRatio.height == 0) { |
4382 | 0 | return aConstraintSize; |
4383 | 0 | } |
4384 | 0 | |
4385 | 0 | // The type of constraint to compute (cover/contain), if needed: |
4386 | 0 | Maybe<nsImageRenderer::FitType> fitType; |
4387 | 0 |
|
4388 | 0 | Maybe<nsSize> noneSize; |
4389 | 0 | if (aObjectFit == NS_STYLE_OBJECT_FIT_NONE || |
4390 | 0 | aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) { |
4391 | 0 | noneSize = MaybeComputeObjectFitNoneSize(aConstraintSize, aIntrinsicSize, |
4392 | 0 | aIntrinsicRatio); |
4393 | 0 | if (!noneSize || aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) { |
4394 | 0 | // Need to compute a 'CONTAIN' constraint (either for the 'none' size |
4395 | 0 | // itself, or for comparison w/ the 'none' size to resolve 'scale-down'.) |
4396 | 0 | fitType.emplace(nsImageRenderer::CONTAIN); |
4397 | 0 | } |
4398 | 0 | } else if (aObjectFit == NS_STYLE_OBJECT_FIT_COVER) { |
4399 | 0 | fitType.emplace(nsImageRenderer::COVER); |
4400 | 0 | } else if (aObjectFit == NS_STYLE_OBJECT_FIT_CONTAIN) { |
4401 | 0 | fitType.emplace(nsImageRenderer::CONTAIN); |
4402 | 0 | } |
4403 | 0 |
|
4404 | 0 | Maybe<nsSize> constrainedSize; |
4405 | 0 | if (fitType) { |
4406 | 0 | constrainedSize.emplace( |
4407 | 0 | nsImageRenderer::ComputeConstrainedSize(aConstraintSize, |
4408 | 0 | aIntrinsicRatio, |
4409 | 0 | *fitType)); |
4410 | 0 | } |
4411 | 0 |
|
4412 | 0 | // Now, we should have all the sizing information that we need. |
4413 | 0 | switch (aObjectFit) { |
4414 | 0 | // skipping NS_STYLE_OBJECT_FIT_FILL; we handled it w/ early-return. |
4415 | 0 | case NS_STYLE_OBJECT_FIT_CONTAIN: |
4416 | 0 | case NS_STYLE_OBJECT_FIT_COVER: |
4417 | 0 | MOZ_ASSERT(constrainedSize); |
4418 | 0 | return *constrainedSize; |
4419 | 0 |
|
4420 | 0 | case NS_STYLE_OBJECT_FIT_NONE: |
4421 | 0 | if (noneSize) { |
4422 | 0 | return *noneSize; |
4423 | 0 | } |
4424 | 0 | MOZ_ASSERT(constrainedSize); |
4425 | 0 | return *constrainedSize; |
4426 | 0 |
|
4427 | 0 | case NS_STYLE_OBJECT_FIT_SCALE_DOWN: |
4428 | 0 | MOZ_ASSERT(constrainedSize); |
4429 | 0 | if (noneSize) { |
4430 | 0 | constrainedSize->width = |
4431 | 0 | std::min(constrainedSize->width, noneSize->width); |
4432 | 0 | constrainedSize->height = |
4433 | 0 | std::min(constrainedSize->height, noneSize->height); |
4434 | 0 | } |
4435 | 0 | return *constrainedSize; |
4436 | 0 |
|
4437 | 0 | default: |
4438 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected enum value for 'object-fit'"); |
4439 | 0 | return aConstraintSize; // fall back to (default) 'fill' behavior |
4440 | 0 | } |
4441 | 0 | } |
4442 | | |
4443 | | // (Helper for HasInitialObjectFitAndPosition, to check |
4444 | | // each "object-position" coord.) |
4445 | | static bool |
4446 | | IsCoord50Pct(const mozilla::Position::Coord& aCoord) |
4447 | 0 | { |
4448 | 0 | return (aCoord.mLength == 0 && |
4449 | 0 | aCoord.mHasPercent && |
4450 | 0 | aCoord.mPercent == 0.5f); |
4451 | 0 | } |
4452 | | |
4453 | | // Indicates whether the given nsStylePosition has the initial values |
4454 | | // for the "object-fit" and "object-position" properties. |
4455 | | static bool |
4456 | | HasInitialObjectFitAndPosition(const nsStylePosition* aStylePos) |
4457 | 0 | { |
4458 | 0 | const mozilla::Position& objectPos = aStylePos->mObjectPosition; |
4459 | 0 |
|
4460 | 0 | return aStylePos->mObjectFit == NS_STYLE_OBJECT_FIT_FILL && |
4461 | 0 | IsCoord50Pct(objectPos.mXPosition) && |
4462 | 0 | IsCoord50Pct(objectPos.mYPosition); |
4463 | 0 | } |
4464 | | |
4465 | | /* static */ nsRect |
4466 | | nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect, |
4467 | | const IntrinsicSize& aIntrinsicSize, |
4468 | | const nsSize& aIntrinsicRatio, |
4469 | | const nsStylePosition* aStylePos, |
4470 | | nsPoint* aAnchorPoint) |
4471 | 0 | { |
4472 | 0 | // Step 1: Figure out our "concrete object size" |
4473 | 0 | // (the size of the region we'll actually draw our image's pixels into). |
4474 | 0 | nsSize concreteObjectSize = |
4475 | 0 | ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize, |
4476 | 0 | aIntrinsicRatio, aStylePos->mObjectFit); |
4477 | 0 |
|
4478 | 0 | // Step 2: Figure out how to align that region in the element's content-box. |
4479 | 0 | nsPoint imageTopLeftPt, imageAnchorPt; |
4480 | 0 | nsImageRenderer::ComputeObjectAnchorPoint(aStylePos->mObjectPosition, |
4481 | 0 | aConstraintRect.Size(), |
4482 | 0 | concreteObjectSize, |
4483 | 0 | &imageTopLeftPt, &imageAnchorPt); |
4484 | 0 | // Right now, we're with respect to aConstraintRect's top-left point. We add |
4485 | 0 | // that point here, to convert to the same broader coordinate space that |
4486 | 0 | // aConstraintRect is in. |
4487 | 0 | imageTopLeftPt += aConstraintRect.TopLeft(); |
4488 | 0 | imageAnchorPt += aConstraintRect.TopLeft(); |
4489 | 0 |
|
4490 | 0 | if (aAnchorPoint) { |
4491 | 0 | // Special-case: if our "object-fit" and "object-position" properties have |
4492 | 0 | // their default values ("object-fit: fill; object-position:50% 50%"), then |
4493 | 0 | // we'll override the calculated imageAnchorPt, and instead use the |
4494 | 0 | // object's top-left corner. |
4495 | 0 | // |
4496 | 0 | // This special case is partly for backwards compatibility (since |
4497 | 0 | // traditionally we've pixel-aligned the top-left corner of e.g. <img> |
4498 | 0 | // elements), and partly because ComputeSnappedDrawingParameters produces |
4499 | 0 | // less error if the anchor point is at the top-left corner. So, all other |
4500 | 0 | // things being equal, we prefer that code path with less error. |
4501 | 0 | if (HasInitialObjectFitAndPosition(aStylePos)) { |
4502 | 0 | *aAnchorPoint = imageTopLeftPt; |
4503 | 0 | } else { |
4504 | 0 | *aAnchorPoint = imageAnchorPt; |
4505 | 0 | } |
4506 | 0 | } |
4507 | 0 | return nsRect(imageTopLeftPt, concreteObjectSize); |
4508 | 0 | } |
4509 | | |
4510 | | already_AddRefed<nsFontMetrics> |
4511 | | nsLayoutUtils::GetFontMetricsForFrame(const nsIFrame* aFrame, float aInflation) |
4512 | 0 | { |
4513 | 0 | ComputedStyle* computedStyle = aFrame->Style(); |
4514 | 0 | uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL; |
4515 | 0 | if (computedStyle->IsTextCombined()) { |
4516 | 0 | MOZ_ASSERT(aFrame->IsTextFrame()); |
4517 | 0 | auto textFrame = static_cast<const nsTextFrame*>(aFrame); |
4518 | 0 | auto clusters = textFrame->CountGraphemeClusters(); |
4519 | 0 | if (clusters == 2) { |
4520 | 0 | variantWidth = NS_FONT_VARIANT_WIDTH_HALF; |
4521 | 0 | } else if (clusters == 3) { |
4522 | 0 | variantWidth = NS_FONT_VARIANT_WIDTH_THIRD; |
4523 | 0 | } else if (clusters == 4) { |
4524 | 0 | variantWidth = NS_FONT_VARIANT_WIDTH_QUARTER; |
4525 | 0 | } |
4526 | 0 | } |
4527 | 0 | return GetFontMetricsForComputedStyle(computedStyle, aFrame->PresContext(), |
4528 | 0 | aInflation, variantWidth); |
4529 | 0 | } |
4530 | | |
4531 | | already_AddRefed<nsFontMetrics> |
4532 | | nsLayoutUtils::GetFontMetricsForComputedStyle(ComputedStyle* aComputedStyle, |
4533 | | nsPresContext* aPresContext, |
4534 | | float aInflation, |
4535 | | uint8_t aVariantWidth) |
4536 | 0 | { |
4537 | 0 | WritingMode wm(aComputedStyle); |
4538 | 0 | const nsStyleFont* styleFont = aComputedStyle->StyleFont(); |
4539 | 0 | nsFontMetrics::Params params; |
4540 | 0 | params.language = styleFont->mLanguage; |
4541 | 0 | params.explicitLanguage = styleFont->mExplicitLanguage; |
4542 | 0 | params.orientation = |
4543 | 0 | wm.IsVertical() && !wm.IsSideways() ? gfxFont::eVertical |
4544 | 0 | : gfxFont::eHorizontal; |
4545 | 0 | // pass the user font set object into the device context to |
4546 | 0 | // pass along to CreateFontGroup |
4547 | 0 | params.userFontSet = aPresContext->GetUserFontSet(); |
4548 | 0 | params.textPerf = aPresContext->GetTextPerfMetrics(); |
4549 | 0 |
|
4550 | 0 | // When aInflation is 1.0 and we don't require width variant, avoid |
4551 | 0 | // making a local copy of the nsFont. |
4552 | 0 | // This also avoids running font.size through floats when it is large, |
4553 | 0 | // which would be lossy. Fortunately, in such cases, aInflation is |
4554 | 0 | // guaranteed to be 1.0f. |
4555 | 0 | if (aInflation == 1.0f && aVariantWidth == NS_FONT_VARIANT_WIDTH_NORMAL) { |
4556 | 0 | return aPresContext->DeviceContext()->GetMetricsFor(styleFont->mFont, params); |
4557 | 0 | } |
4558 | 0 | |
4559 | 0 | nsFont font = styleFont->mFont; |
4560 | 0 | font.size = NSToCoordRound(font.size * aInflation); |
4561 | 0 | font.variantWidth = aVariantWidth; |
4562 | 0 | return aPresContext->DeviceContext()->GetMetricsFor(font, params); |
4563 | 0 | } |
4564 | | |
4565 | | nsIFrame* |
4566 | | nsLayoutUtils::FindChildContainingDescendant(nsIFrame* aParent, nsIFrame* aDescendantFrame) |
4567 | 0 | { |
4568 | 0 | nsIFrame* result = aDescendantFrame; |
4569 | 0 |
|
4570 | 0 | while (result) { |
4571 | 0 | nsIFrame* parent = result->GetParent(); |
4572 | 0 | if (parent == aParent) { |
4573 | 0 | break; |
4574 | 0 | } |
4575 | 0 | |
4576 | 0 | // The frame is not an immediate child of aParent so walk up another level |
4577 | 0 | result = parent; |
4578 | 0 | } |
4579 | 0 |
|
4580 | 0 | return result; |
4581 | 0 | } |
4582 | | |
4583 | | nsBlockFrame* |
4584 | | nsLayoutUtils::GetAsBlock(nsIFrame* aFrame) |
4585 | 0 | { |
4586 | 0 | nsBlockFrame* block = do_QueryFrame(aFrame); |
4587 | 0 | return block; |
4588 | 0 | } |
4589 | | |
4590 | | nsBlockFrame* |
4591 | | nsLayoutUtils::FindNearestBlockAncestor(nsIFrame* aFrame) |
4592 | 0 | { |
4593 | 0 | nsIFrame* nextAncestor; |
4594 | 0 | for (nextAncestor = aFrame->GetParent(); nextAncestor; |
4595 | 0 | nextAncestor = nextAncestor->GetParent()) { |
4596 | 0 | nsBlockFrame* block = GetAsBlock(nextAncestor); |
4597 | 0 | if (block) |
4598 | 0 | return block; |
4599 | 0 | } |
4600 | 0 | return nullptr; |
4601 | 0 | } |
4602 | | |
4603 | | nsIFrame* |
4604 | | nsLayoutUtils::GetNonGeneratedAncestor(nsIFrame* aFrame) |
4605 | 0 | { |
4606 | 0 | if (!(aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) |
4607 | 0 | return aFrame; |
4608 | 0 | |
4609 | 0 | nsIFrame* f = aFrame; |
4610 | 0 | do { |
4611 | 0 | f = GetParentOrPlaceholderFor(f); |
4612 | 0 | } while (f->GetStateBits() & NS_FRAME_GENERATED_CONTENT); |
4613 | 0 | return f; |
4614 | 0 | } |
4615 | | |
4616 | | nsIFrame* |
4617 | | nsLayoutUtils::GetParentOrPlaceholderFor(nsIFrame* aFrame) |
4618 | 0 | { |
4619 | 0 | if ((aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) |
4620 | 0 | && !aFrame->GetPrevInFlow()) { |
4621 | 0 | return aFrame->GetProperty(nsIFrame::PlaceholderFrameProperty()); |
4622 | 0 | } |
4623 | 0 | return aFrame->GetParent(); |
4624 | 0 | } |
4625 | | |
4626 | | nsIFrame* |
4627 | | nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(nsIFrame* aFrame) |
4628 | 0 | { |
4629 | 0 | nsIFrame* f = GetParentOrPlaceholderFor(aFrame); |
4630 | 0 | if (f) |
4631 | 0 | return f; |
4632 | 0 | return GetCrossDocParentFrame(aFrame); |
4633 | 0 | } |
4634 | | |
4635 | | nsIFrame* |
4636 | | nsLayoutUtils::GetNextContinuationOrIBSplitSibling(nsIFrame *aFrame) |
4637 | 0 | { |
4638 | 0 | nsIFrame *result = aFrame->GetNextContinuation(); |
4639 | 0 | if (result) |
4640 | 0 | return result; |
4641 | 0 | |
4642 | 0 | if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) != 0) { |
4643 | 0 | // We only store the ib-split sibling annotation with the first |
4644 | 0 | // frame in the continuation chain. Walk back to find that frame now. |
4645 | 0 | aFrame = aFrame->FirstContinuation(); |
4646 | 0 |
|
4647 | 0 | return aFrame->GetProperty(nsIFrame::IBSplitSibling()); |
4648 | 0 | } |
4649 | 0 | |
4650 | 0 | return nullptr; |
4651 | 0 | } |
4652 | | |
4653 | | nsIFrame* |
4654 | | nsLayoutUtils::FirstContinuationOrIBSplitSibling(const nsIFrame* aFrame) |
4655 | 0 | { |
4656 | 0 | nsIFrame* result = aFrame->FirstContinuation(); |
4657 | 0 |
|
4658 | 0 | if (result->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) { |
4659 | 0 | while (auto* f = result->GetProperty(nsIFrame::IBSplitPrevSibling())) { |
4660 | 0 | result = f; |
4661 | 0 | } |
4662 | 0 | } |
4663 | 0 |
|
4664 | 0 | return result; |
4665 | 0 | } |
4666 | | |
4667 | | nsIFrame* |
4668 | | nsLayoutUtils::LastContinuationOrIBSplitSibling(const nsIFrame* aFrame) |
4669 | 0 | { |
4670 | 0 | nsIFrame* result = aFrame->FirstContinuation(); |
4671 | 0 |
|
4672 | 0 | if (result->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) { |
4673 | 0 | while (auto* f = result->GetProperty(nsIFrame::IBSplitSibling())) { |
4674 | 0 | result = f; |
4675 | 0 | } |
4676 | 0 | } |
4677 | 0 |
|
4678 | 0 | return result->LastContinuation(); |
4679 | 0 | } |
4680 | | |
4681 | | bool |
4682 | | nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(nsIFrame *aFrame) |
4683 | 0 | { |
4684 | 0 | if (aFrame->GetPrevContinuation()) { |
4685 | 0 | return false; |
4686 | 0 | } |
4687 | 0 | if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) && |
4688 | 0 | aFrame->GetProperty(nsIFrame::IBSplitPrevSibling())) { |
4689 | 0 | return false; |
4690 | 0 | } |
4691 | 0 | |
4692 | 0 | return true; |
4693 | 0 | } |
4694 | | |
4695 | | bool |
4696 | | nsLayoutUtils::IsViewportScrollbarFrame(nsIFrame* aFrame) |
4697 | 0 | { |
4698 | 0 | if (!aFrame) |
4699 | 0 | return false; |
4700 | 0 | |
4701 | 0 | nsIFrame* rootScrollFrame = aFrame->PresShell()->GetRootScrollFrame(); |
4702 | 0 | if (!rootScrollFrame) |
4703 | 0 | return false; |
4704 | 0 | |
4705 | 0 | nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame); |
4706 | 0 | NS_ASSERTION(rootScrollableFrame, "The root scorollable frame is null"); |
4707 | 0 |
|
4708 | 0 | if (!IsProperAncestorFrame(rootScrollFrame, aFrame)) |
4709 | 0 | return false; |
4710 | 0 | |
4711 | 0 | nsIFrame* rootScrolledFrame = rootScrollableFrame->GetScrolledFrame(); |
4712 | 0 | return !(rootScrolledFrame == aFrame || |
4713 | 0 | IsProperAncestorFrame(rootScrolledFrame, aFrame)); |
4714 | 0 | } |
4715 | | |
4716 | | // Use only for widths/heights (or their min/max), since it clamps |
4717 | | // negative calc() results to 0. |
4718 | | static bool GetAbsoluteCoord(const nsStyleCoord& aStyle, nscoord& aResult) |
4719 | 0 | { |
4720 | 0 | if (aStyle.IsCalcUnit()) { |
4721 | 0 | if (aStyle.CalcHasPercent()) { |
4722 | 0 | return false; |
4723 | 0 | } |
4724 | 0 | // If it has no percents, we can pass 0 for the percentage basis. |
4725 | 0 | aResult = aStyle.ComputeComputedCalc(0); |
4726 | 0 | if (aResult < 0) |
4727 | 0 | aResult = 0; |
4728 | 0 | return true; |
4729 | 0 | } |
4730 | 0 |
|
4731 | 0 | if (eStyleUnit_Coord != aStyle.GetUnit()) |
4732 | 0 | return false; |
4733 | 0 | |
4734 | 0 | aResult = aStyle.GetCoordValue(); |
4735 | 0 | NS_ASSERTION(aResult >= 0, "negative widths not allowed"); |
4736 | 0 | return true; |
4737 | 0 | } |
4738 | | |
4739 | | static nscoord |
4740 | | GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing, |
4741 | | nsIFrame* aFrame, |
4742 | | bool aHorizontalAxis, |
4743 | | bool aIgnorePadding); |
4744 | | |
4745 | | // Only call on style coords for which GetAbsoluteCoord returned false. |
4746 | | static bool |
4747 | | GetPercentBSize(const nsStyleCoord& aStyle, |
4748 | | nsIFrame* aFrame, |
4749 | | bool aHorizontalAxis, |
4750 | | nscoord& aResult) |
4751 | 0 | { |
4752 | 0 | if (eStyleUnit_Percent != aStyle.GetUnit() && |
4753 | 0 | !aStyle.IsCalcUnit()) |
4754 | 0 | return false; |
4755 | 0 | |
4756 | 0 | MOZ_ASSERT(!aStyle.IsCalcUnit() || aStyle.CalcHasPercent(), |
4757 | 0 | "GetAbsoluteCoord should have handled this"); |
4758 | 0 |
|
4759 | 0 | // During reflow, nsHTMLScrollFrame::ReflowScrolledFrame uses |
4760 | 0 | // SetComputedHeight on the reflow state for its child to propagate its |
4761 | 0 | // computed height to the scrolled content. So here we skip to the scroll |
4762 | 0 | // frame that contains this scrolled content in order to get the same |
4763 | 0 | // behavior as layout when computing percentage heights. |
4764 | 0 | nsIFrame *f = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME); |
4765 | 0 | if (!f) { |
4766 | 0 | MOZ_ASSERT_UNREACHABLE("top of frame tree not a containing block"); |
4767 | 0 | return false; |
4768 | 0 | } |
4769 | 0 |
|
4770 | 0 | WritingMode wm = f->GetWritingMode(); |
4771 | 0 |
|
4772 | 0 | const nsStylePosition *pos = f->StylePosition(); |
4773 | 0 | const nsStyleCoord& bSizeCoord = pos->BSize(wm); |
4774 | 0 | nscoord h; |
4775 | 0 | if (!GetAbsoluteCoord(bSizeCoord, h) && |
4776 | 0 | !GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) { |
4777 | 0 | NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto || |
4778 | 0 | bSizeCoord.HasPercent(), |
4779 | 0 | "unknown block-size unit"); |
4780 | 0 | LayoutFrameType fType = f->Type(); |
4781 | 0 | if (fType != LayoutFrameType::Viewport && |
4782 | 0 | fType != LayoutFrameType::Canvas && |
4783 | 0 | fType != LayoutFrameType::PageContent) { |
4784 | 0 | // There's no basis for the percentage height, so it acts like auto. |
4785 | 0 | // Should we consider a max-height < min-height pair a basis for |
4786 | 0 | // percentage heights? The spec is somewhat unclear, and not doing |
4787 | 0 | // so is simpler and avoids troubling discontinuities in behavior, |
4788 | 0 | // so I'll choose not to. -LDB |
4789 | 0 | return false; |
4790 | 0 | } |
4791 | 0 | |
4792 | 0 | NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto, |
4793 | 0 | "Unexpected block-size unit for viewport or canvas or page-content"); |
4794 | 0 | // For the viewport, canvas, and page-content kids, the percentage |
4795 | 0 | // basis is just the parent block-size. |
4796 | 0 | h = f->BSize(wm); |
4797 | 0 | if (h == NS_UNCONSTRAINEDSIZE) { |
4798 | 0 | // We don't have a percentage basis after all |
4799 | 0 | return false; |
4800 | 0 | } |
4801 | 0 | } |
4802 | 0 | |
4803 | 0 | const nsStyleCoord& maxBSizeCoord = pos->MaxBSize(wm); |
4804 | 0 |
|
4805 | 0 | nscoord maxh; |
4806 | 0 | if (GetAbsoluteCoord(maxBSizeCoord, maxh) || |
4807 | 0 | GetPercentBSize(maxBSizeCoord, f, aHorizontalAxis, maxh)) { |
4808 | 0 | if (maxh < h) |
4809 | 0 | h = maxh; |
4810 | 0 | } else { |
4811 | 0 | NS_ASSERTION(maxBSizeCoord.GetUnit() == eStyleUnit_None || |
4812 | 0 | maxBSizeCoord.HasPercent(), |
4813 | 0 | "unknown max block-size unit"); |
4814 | 0 | } |
4815 | 0 |
|
4816 | 0 | const nsStyleCoord& minBSizeCoord = pos->MinBSize(wm); |
4817 | 0 |
|
4818 | 0 | nscoord minh; |
4819 | 0 | if (GetAbsoluteCoord(minBSizeCoord, minh) || |
4820 | 0 | GetPercentBSize(minBSizeCoord, f, aHorizontalAxis, minh)) { |
4821 | 0 | if (minh > h) |
4822 | 0 | h = minh; |
4823 | 0 | } else { |
4824 | 0 | NS_ASSERTION(minBSizeCoord.HasPercent() || |
4825 | 0 | minBSizeCoord.GetUnit() == eStyleUnit_Auto, |
4826 | 0 | "unknown min block-size unit"); |
4827 | 0 | } |
4828 | 0 |
|
4829 | 0 | // Now adjust h for box-sizing styles on the parent. We never ignore padding |
4830 | 0 | // here. That could conceivably cause some problems with fieldsets (which are |
4831 | 0 | // the one place that wants to ignore padding), but solving that here without |
4832 | 0 | // hardcoding a check for f being a fieldset-content frame is a bit of a pain. |
4833 | 0 | nscoord bSizeTakenByBoxSizing = |
4834 | 0 | GetBSizeTakenByBoxSizing(pos->mBoxSizing, f, aHorizontalAxis, false); |
4835 | 0 | h = std::max(0, h - bSizeTakenByBoxSizing); |
4836 | 0 |
|
4837 | 0 | if (aStyle.IsCalcUnit()) { |
4838 | 0 | aResult = std::max(aStyle.ComputeComputedCalc(h), 0); |
4839 | 0 | return true; |
4840 | 0 | } |
4841 | 0 | |
4842 | 0 | aResult = NSToCoordRound(aStyle.GetPercentValue() * h); |
4843 | 0 | return true; |
4844 | 0 | } |
4845 | | |
4846 | | // Return true if aStyle can be resolved to a definite value and if so |
4847 | | // return that value in aResult. |
4848 | | static bool |
4849 | | GetDefiniteSize(const nsStyleCoord& aStyle, |
4850 | | nsIFrame* aFrame, |
4851 | | bool aIsInlineAxis, |
4852 | | const Maybe<LogicalSize>& aPercentageBasis, |
4853 | | nscoord* aResult) |
4854 | 0 | { |
4855 | 0 | switch (aStyle.GetUnit()) { |
4856 | 0 | case eStyleUnit_Coord: |
4857 | 0 | *aResult = aStyle.GetCoordValue(); |
4858 | 0 | return true; |
4859 | 0 | case eStyleUnit_Percent: { |
4860 | 0 | if (aPercentageBasis.isNothing()) { |
4861 | 0 | return false; |
4862 | 0 | } |
4863 | 0 | auto wm = aFrame->GetWritingMode(); |
4864 | 0 | nscoord pb = aIsInlineAxis ? aPercentageBasis.value().ISize(wm) |
4865 | 0 | : aPercentageBasis.value().BSize(wm); |
4866 | 0 | if (pb != NS_UNCONSTRAINEDSIZE) { |
4867 | 0 | nscoord p = NSToCoordFloorClamped(pb * aStyle.GetPercentValue()); |
4868 | 0 | *aResult = std::max(nscoord(0), p); |
4869 | 0 | return true; |
4870 | 0 | } |
4871 | 0 | return false; |
4872 | 0 | } |
4873 | 0 | case eStyleUnit_Calc: { |
4874 | 0 | nsStyleCoord::Calc* calc = aStyle.GetCalcValue(); |
4875 | 0 | if (calc->mPercent != 0.0f) { |
4876 | 0 | if (aPercentageBasis.isNothing()) { |
4877 | 0 | return false; |
4878 | 0 | } |
4879 | 0 | auto wm = aFrame->GetWritingMode(); |
4880 | 0 | nscoord pb = aIsInlineAxis ? aPercentageBasis.value().ISize(wm) |
4881 | 0 | : aPercentageBasis.value().BSize(wm); |
4882 | 0 | if (pb == NS_UNCONSTRAINEDSIZE) { |
4883 | 0 | return false; |
4884 | 0 | } |
4885 | 0 | *aResult = std::max(0, calc->mLength + |
4886 | 0 | NSToCoordFloorClamped(pb * calc->mPercent)); |
4887 | 0 | } else { |
4888 | 0 | *aResult = std::max(0, calc->mLength); |
4889 | 0 | } |
4890 | 0 | return true; |
4891 | 0 | } |
4892 | 0 | default: |
4893 | 0 | return false; |
4894 | 0 | } |
4895 | 0 | } |
4896 | | |
4897 | | // |
4898 | | // NOTE: this function will be replaced by GetDefiniteSizeTakenByBoxSizing (bug 1363918). |
4899 | | // Please do not add new uses of this function. |
4900 | | // |
4901 | | // Get the amount of vertical space taken out of aFrame's content area due to |
4902 | | // its borders and paddings given the box-sizing value in aBoxSizing. We don't |
4903 | | // get aBoxSizing from the frame because some callers want to compute this for |
4904 | | // specific box-sizing values. aHorizontalAxis is true if our inline direction |
4905 | | // is horisontal and our block direction is vertical. aIgnorePadding is true if |
4906 | | // padding should be ignored. |
4907 | | static nscoord |
4908 | | GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing, |
4909 | | nsIFrame* aFrame, |
4910 | | bool aHorizontalAxis, |
4911 | | bool aIgnorePadding) |
4912 | 0 | { |
4913 | 0 | nscoord bSizeTakenByBoxSizing = 0; |
4914 | 0 | if (aBoxSizing == StyleBoxSizing::Border) { |
4915 | 0 | const nsStyleBorder* styleBorder = aFrame->StyleBorder(); |
4916 | 0 | bSizeTakenByBoxSizing += |
4917 | 0 | aHorizontalAxis ? styleBorder->GetComputedBorder().TopBottom() |
4918 | 0 | : styleBorder->GetComputedBorder().LeftRight(); |
4919 | 0 | if (!aIgnorePadding) { |
4920 | 0 | const nsStyleSides& stylePadding = |
4921 | 0 | aFrame->StylePadding()->mPadding; |
4922 | 0 | const nsStyleCoord& paddingStart = |
4923 | 0 | stylePadding.Get(aHorizontalAxis ? eSideTop : eSideLeft); |
4924 | 0 | const nsStyleCoord& paddingEnd = |
4925 | 0 | stylePadding.Get(aHorizontalAxis ? eSideBottom : eSideRight); |
4926 | 0 | nscoord pad; |
4927 | 0 | // XXXbz Calling GetPercentBSize on padding values looks bogus, since |
4928 | 0 | // percent padding is always a percentage of the inline-size of the |
4929 | 0 | // containing block. We should perhaps just treat non-absolute paddings |
4930 | 0 | // here as 0 instead, except that in some cases the width may in fact be |
4931 | 0 | // known. See bug 1231059. |
4932 | 0 | if (GetAbsoluteCoord(paddingStart, pad) || |
4933 | 0 | GetPercentBSize(paddingStart, aFrame, aHorizontalAxis, pad)) { |
4934 | 0 | bSizeTakenByBoxSizing += pad; |
4935 | 0 | } |
4936 | 0 | if (GetAbsoluteCoord(paddingEnd, pad) || |
4937 | 0 | GetPercentBSize(paddingEnd, aFrame, aHorizontalAxis, pad)) { |
4938 | 0 | bSizeTakenByBoxSizing += pad; |
4939 | 0 | } |
4940 | 0 | } |
4941 | 0 | } |
4942 | 0 | return bSizeTakenByBoxSizing; |
4943 | 0 | } |
4944 | | |
4945 | | // Get the amount of space taken out of aFrame's content area due to its |
4946 | | // borders and paddings given the box-sizing value in aBoxSizing. We don't |
4947 | | // get aBoxSizing from the frame because some callers want to compute this for |
4948 | | // specific box-sizing values. |
4949 | | // aIsInlineAxis is true if we're computing for aFrame's inline axis. |
4950 | | // aIgnorePadding is true if padding should be ignored. |
4951 | | static nscoord |
4952 | | GetDefiniteSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing, |
4953 | | nsIFrame* aFrame, |
4954 | | bool aIsInlineAxis, |
4955 | | bool aIgnorePadding, |
4956 | | const Maybe<LogicalSize>& aPercentageBasis) |
4957 | 0 | { |
4958 | 0 | nscoord sizeTakenByBoxSizing = 0; |
4959 | 0 | if (MOZ_UNLIKELY(aBoxSizing == StyleBoxSizing::Border)) { |
4960 | 0 | const bool isHorizontalAxis = |
4961 | 0 | aIsInlineAxis == !aFrame->GetWritingMode().IsVertical(); |
4962 | 0 | const nsStyleBorder* styleBorder = aFrame->StyleBorder(); |
4963 | 0 | sizeTakenByBoxSizing = |
4964 | 0 | isHorizontalAxis ? styleBorder->GetComputedBorder().LeftRight() |
4965 | 0 | : styleBorder->GetComputedBorder().TopBottom(); |
4966 | 0 | if (!aIgnorePadding) { |
4967 | 0 | const nsStyleSides& stylePadding = aFrame->StylePadding()->mPadding; |
4968 | 0 | const nsStyleCoord& pStart = |
4969 | 0 | stylePadding.Get(isHorizontalAxis ? eSideLeft : eSideTop); |
4970 | 0 | const nsStyleCoord& pEnd = |
4971 | 0 | stylePadding.Get(isHorizontalAxis ? eSideRight : eSideBottom); |
4972 | 0 | nscoord pad; |
4973 | 0 | // XXXbz Calling GetPercentBSize on padding values looks bogus, since |
4974 | 0 | // percent padding is always a percentage of the inline-size of the |
4975 | 0 | // containing block. We should perhaps just treat non-absolute paddings |
4976 | 0 | // here as 0 instead, except that in some cases the width may in fact be |
4977 | 0 | // known. See bug 1231059. |
4978 | 0 | if (GetDefiniteSize(pStart, aFrame, aIsInlineAxis, aPercentageBasis, &pad) || |
4979 | 0 | (aPercentageBasis.isNothing() && |
4980 | 0 | GetPercentBSize(pStart, aFrame, isHorizontalAxis, pad))) { |
4981 | 0 | sizeTakenByBoxSizing += pad; |
4982 | 0 | } |
4983 | 0 | if (GetDefiniteSize(pEnd, aFrame, aIsInlineAxis, aPercentageBasis, &pad) || |
4984 | 0 | (aPercentageBasis.isNothing() && |
4985 | 0 | GetPercentBSize(pEnd, aFrame, isHorizontalAxis, pad))) { |
4986 | 0 | sizeTakenByBoxSizing += pad; |
4987 | 0 | } |
4988 | 0 | } |
4989 | 0 | } |
4990 | 0 | return sizeTakenByBoxSizing; |
4991 | 0 | } |
4992 | | |
4993 | | // Handles only -moz-max-content and -moz-min-content, and |
4994 | | // -moz-fit-content for min-width and max-width, since the others |
4995 | | // (-moz-fit-content for width, and -moz-available) have no effect on |
4996 | | // intrinsic widths. |
4997 | | enum eWidthProperty { PROP_WIDTH, PROP_MAX_WIDTH, PROP_MIN_WIDTH }; |
4998 | | static bool |
4999 | | GetIntrinsicCoord(const nsStyleCoord& aStyle, |
5000 | | gfxContext* aRenderingContext, |
5001 | | nsIFrame* aFrame, |
5002 | | eWidthProperty aProperty, |
5003 | | nscoord& aResult) |
5004 | 0 | { |
5005 | 0 | MOZ_ASSERT(aProperty == PROP_WIDTH || aProperty == PROP_MAX_WIDTH || |
5006 | 0 | aProperty == PROP_MIN_WIDTH, "unexpected property"); |
5007 | 0 |
|
5008 | 0 | if (aStyle.GetUnit() != eStyleUnit_Enumerated) |
5009 | 0 | return false; |
5010 | 0 | int32_t val = aStyle.GetIntValue(); |
5011 | 0 | NS_ASSERTION(val == NS_STYLE_WIDTH_MAX_CONTENT || |
5012 | 0 | val == NS_STYLE_WIDTH_MIN_CONTENT || |
5013 | 0 | val == NS_STYLE_WIDTH_FIT_CONTENT || |
5014 | 0 | val == NS_STYLE_WIDTH_AVAILABLE, |
5015 | 0 | "unexpected enumerated value for width property"); |
5016 | 0 | if (val == NS_STYLE_WIDTH_AVAILABLE) |
5017 | 0 | return false; |
5018 | 0 | if (val == NS_STYLE_WIDTH_FIT_CONTENT) { |
5019 | 0 | if (aProperty == PROP_WIDTH) |
5020 | 0 | return false; // handle like 'width: auto' |
5021 | 0 | if (aProperty == PROP_MAX_WIDTH) |
5022 | 0 | // constrain large 'width' values down to -moz-max-content |
5023 | 0 | val = NS_STYLE_WIDTH_MAX_CONTENT; |
5024 | 0 | else |
5025 | 0 | // constrain small 'width' or 'max-width' values up to -moz-min-content |
5026 | 0 | val = NS_STYLE_WIDTH_MIN_CONTENT; |
5027 | 0 | } |
5028 | 0 |
|
5029 | 0 | NS_ASSERTION(val == NS_STYLE_WIDTH_MAX_CONTENT || |
5030 | 0 | val == NS_STYLE_WIDTH_MIN_CONTENT, |
5031 | 0 | "should have reduced everything remaining to one of these"); |
5032 | 0 |
|
5033 | 0 | // If aFrame is a container for font size inflation, then shrink |
5034 | 0 | // wrapping inside of it should not apply font size inflation. |
5035 | 0 | AutoMaybeDisableFontInflation an(aFrame); |
5036 | 0 |
|
5037 | 0 | if (val == NS_STYLE_WIDTH_MAX_CONTENT) |
5038 | 0 | aResult = aFrame->GetPrefISize(aRenderingContext); |
5039 | 0 | else |
5040 | 0 | aResult = aFrame->GetMinISize(aRenderingContext); |
5041 | 0 | return true; |
5042 | 0 | } |
5043 | | |
5044 | | #undef DEBUG_INTRINSIC_WIDTH |
5045 | | |
5046 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5047 | | static int32_t gNoiseIndent = 0; |
5048 | | #endif |
5049 | | |
5050 | | // Return true for form controls whose minimum intrinsic inline-size |
5051 | | // shrinks to 0 when they have a percentage inline-size (but not |
5052 | | // percentage max-inline-size). (Proper replaced elements, whose |
5053 | | // intrinsic minimium inline-size shrinks to 0 for both percentage |
5054 | | // inline-size and percentage max-inline-size, are handled elsewhere.) |
5055 | | inline static bool |
5056 | | FormControlShrinksForPercentISize(nsIFrame* aFrame) |
5057 | 0 | { |
5058 | 0 | if (!aFrame->IsFrameOfType(nsIFrame::eReplaced)) { |
5059 | 0 | // Quick test to reject most frames. |
5060 | 0 | return false; |
5061 | 0 | } |
5062 | 0 | |
5063 | 0 | LayoutFrameType fType = aFrame->Type(); |
5064 | 0 | if (fType == LayoutFrameType::Meter || fType == LayoutFrameType::Progress) { |
5065 | 0 | // progress and meter do have this shrinking behavior |
5066 | 0 | // FIXME: Maybe these should be nsIFormControlFrame? |
5067 | 0 | return true; |
5068 | 0 | } |
5069 | 0 | |
5070 | 0 | if (!static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) { |
5071 | 0 | // Not a form control. This includes fieldsets, which do not |
5072 | 0 | // shrink. |
5073 | 0 | return false; |
5074 | 0 | } |
5075 | 0 | |
5076 | 0 | if (fType == LayoutFrameType::GfxButtonControl || |
5077 | 0 | fType == LayoutFrameType::HTMLButtonControl) { |
5078 | 0 | // Buttons don't have this shrinking behavior. (Note that color |
5079 | 0 | // inputs do, even though they inherit from button, so we can't use |
5080 | 0 | // do_QueryFrame here.) |
5081 | 0 | return false; |
5082 | 0 | } |
5083 | 0 | |
5084 | 0 | return true; |
5085 | 0 | } |
5086 | | |
5087 | | // https://drafts.csswg.org/css-sizing-3/#percentage-sizing |
5088 | | // Return true if the above spec's rule for replaced boxes applies. |
5089 | | // XXX bug 1463700 will make this match the spec... |
5090 | | static bool |
5091 | | IsReplacedBoxResolvedAgainstZero(nsIFrame* aFrame, |
5092 | | const nsStyleCoord& aStyleSize, |
5093 | | const nsStyleCoord& aStyleMaxSize) |
5094 | 0 | { |
5095 | 0 | const bool sizeHasPercent = aStyleSize.HasPercent(); |
5096 | 0 | return ((sizeHasPercent || aStyleMaxSize.HasPercent()) && |
5097 | 0 | aFrame->IsFrameOfType(nsIFrame::eReplacedSizing)) || |
5098 | 0 | (sizeHasPercent && |
5099 | 0 | FormControlShrinksForPercentISize(aFrame)); |
5100 | 0 | } |
5101 | | |
5102 | | /** |
5103 | | * Add aOffsets which describes what to add on outside of the content box |
5104 | | * aContentSize (controlled by 'box-sizing') and apply min/max properties. |
5105 | | * We have to account for these properties after getting all the offsets |
5106 | | * (margin, border, padding) because percentages do not operate linearly. |
5107 | | * Doing this is ok because although percentages aren't handled linearly, |
5108 | | * they are handled monotonically. |
5109 | | * |
5110 | | * @param aContentSize the content size calculated so far |
5111 | | (@see IntrinsicForContainer) |
5112 | | * @param aContentMinSize ditto min content size |
5113 | | * @param aStyleSize a 'width' or 'height' property value |
5114 | | * @param aFixedMinSize if aStyleMinSize is a definite size then this points to |
5115 | | * the value, otherwise nullptr |
5116 | | * @param aStyleMinSize a 'min-width' or 'min-height' property value |
5117 | | * @param aFixedMaxSize if aStyleMaxSize is a definite size then this points to |
5118 | | * the value, otherwise nullptr |
5119 | | * @param aStyleMaxSize a 'max-width' or 'max-height' property value |
5120 | | * @param aFlags same as for IntrinsicForContainer |
5121 | | * @param aContainerWM the container's WM |
5122 | | */ |
5123 | | static nscoord |
5124 | | AddIntrinsicSizeOffset(gfxContext* aRenderingContext, |
5125 | | nsIFrame* aFrame, |
5126 | | const nsIFrame::IntrinsicISizeOffsetData& aOffsets, |
5127 | | nsLayoutUtils::IntrinsicISizeType aType, |
5128 | | StyleBoxSizing aBoxSizing, |
5129 | | nscoord aContentSize, |
5130 | | nscoord aContentMinSize, |
5131 | | const nsStyleCoord& aStyleSize, |
5132 | | const nscoord* aFixedMinSize, |
5133 | | const nsStyleCoord& aStyleMinSize, |
5134 | | const nscoord* aFixedMaxSize, |
5135 | | const nsStyleCoord& aStyleMaxSize, |
5136 | | uint32_t aFlags, |
5137 | | PhysicalAxis aAxis) |
5138 | 0 | { |
5139 | 0 | nscoord result = aContentSize; |
5140 | 0 | nscoord min = aContentMinSize; |
5141 | 0 | nscoord coordOutsideSize = 0; |
5142 | 0 |
|
5143 | 0 | if (!(aFlags & nsLayoutUtils::IGNORE_PADDING)) { |
5144 | 0 | coordOutsideSize += aOffsets.hPadding; |
5145 | 0 | } |
5146 | 0 |
|
5147 | 0 | coordOutsideSize += aOffsets.hBorder; |
5148 | 0 |
|
5149 | 0 | if (aBoxSizing == StyleBoxSizing::Border) { |
5150 | 0 | min += coordOutsideSize; |
5151 | 0 | result = NSCoordSaturatingAdd(result, coordOutsideSize); |
5152 | 0 |
|
5153 | 0 | coordOutsideSize = 0; |
5154 | 0 | } |
5155 | 0 |
|
5156 | 0 | coordOutsideSize += aOffsets.hMargin; |
5157 | 0 |
|
5158 | 0 | min += coordOutsideSize; |
5159 | 0 | result = NSCoordSaturatingAdd(result, coordOutsideSize); |
5160 | 0 |
|
5161 | 0 | nscoord size; |
5162 | 0 | if (aType == nsLayoutUtils::MIN_ISIZE && |
5163 | 0 | ::IsReplacedBoxResolvedAgainstZero(aFrame, aStyleSize, aStyleMaxSize)) { |
5164 | 0 | // XXX bug 1463700: this doesn't handle calc() according to spec |
5165 | 0 | result = 0; // let |min| handle padding/border/margin |
5166 | 0 | } else if (GetAbsoluteCoord(aStyleSize, size) || |
5167 | 0 | GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame, |
5168 | 0 | PROP_WIDTH, size)) { |
5169 | 0 | result = size + coordOutsideSize; |
5170 | 0 | } |
5171 | 0 |
|
5172 | 0 | nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0; |
5173 | 0 | if (aFixedMaxSize || |
5174 | 0 | GetIntrinsicCoord(aStyleMaxSize, aRenderingContext, aFrame, |
5175 | 0 | PROP_MAX_WIDTH, maxSize)) { |
5176 | 0 | maxSize += coordOutsideSize; |
5177 | 0 | if (result > maxSize) { |
5178 | 0 | result = maxSize; |
5179 | 0 | } |
5180 | 0 | } |
5181 | 0 |
|
5182 | 0 | nscoord minSize = aFixedMinSize ? *aFixedMinSize : 0; |
5183 | 0 | if (aFixedMinSize || |
5184 | 0 | GetIntrinsicCoord(aStyleMinSize, aRenderingContext, aFrame, |
5185 | 0 | PROP_MIN_WIDTH, minSize)) { |
5186 | 0 | minSize += coordOutsideSize; |
5187 | 0 | if (result < minSize) { |
5188 | 0 | result = minSize; |
5189 | 0 | } |
5190 | 0 | } |
5191 | 0 |
|
5192 | 0 | if (result < min) { |
5193 | 0 | result = min; |
5194 | 0 | } |
5195 | 0 |
|
5196 | 0 | const nsStyleDisplay* disp = aFrame->StyleDisplay(); |
5197 | 0 | if (aFrame->IsThemed(disp)) { |
5198 | 0 | LayoutDeviceIntSize devSize; |
5199 | 0 | bool canOverride = true; |
5200 | 0 | nsPresContext* pc = aFrame->PresContext(); |
5201 | 0 | pc->GetTheme()->GetMinimumWidgetSize(pc, aFrame, disp->mAppearance, |
5202 | 0 | &devSize, &canOverride); |
5203 | 0 | nscoord themeSize = |
5204 | 0 | pc->DevPixelsToAppUnits(aAxis == eAxisVertical ? devSize.height |
5205 | 0 | : devSize.width); |
5206 | 0 | // GetMinimumWidgetSize() returns a border-box width. |
5207 | 0 | themeSize += aOffsets.hMargin; |
5208 | 0 | if (themeSize > result || !canOverride) { |
5209 | 0 | result = themeSize; |
5210 | 0 | } |
5211 | 0 | } |
5212 | 0 | return result; |
5213 | 0 | } |
5214 | | |
5215 | | static void |
5216 | | AddStateBitToAncestors(nsIFrame* aFrame, nsFrameState aBit) |
5217 | 0 | { |
5218 | 0 | for (nsIFrame* f = aFrame; f; f = f->GetParent()) { |
5219 | 0 | if (f->HasAnyStateBits(aBit)) { |
5220 | 0 | break; |
5221 | 0 | } |
5222 | 0 | f->AddStateBits(aBit); |
5223 | 0 | } |
5224 | 0 | } |
5225 | | |
5226 | | /* static */ nscoord |
5227 | | nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis, |
5228 | | gfxContext* aRenderingContext, |
5229 | | nsIFrame* aFrame, |
5230 | | IntrinsicISizeType aType, |
5231 | | const Maybe<LogicalSize>& aPercentageBasis, |
5232 | | uint32_t aFlags, |
5233 | | nscoord aMarginBoxMinSizeClamp) |
5234 | 0 | { |
5235 | 0 | MOZ_ASSERT(aFrame, "null frame"); |
5236 | 0 | MOZ_ASSERT(aFrame->GetParent(), |
5237 | 0 | "IntrinsicForAxis called on frame not in tree"); |
5238 | 0 | MOZ_ASSERT(aType == MIN_ISIZE || aType == PREF_ISIZE, "bad type"); |
5239 | 0 | MOZ_ASSERT(aFrame->GetParent()->Type() != LayoutFrameType::GridContainer || |
5240 | 0 | aPercentageBasis.isSome(), |
5241 | 0 | "grid layout should always pass a percentage basis"); |
5242 | 0 |
|
5243 | 0 | const bool horizontalAxis = MOZ_LIKELY(aAxis == eAxisHorizontal); |
5244 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5245 | | nsFrame::IndentBy(stderr, gNoiseIndent); |
5246 | | static_cast<nsFrame*>(aFrame)->ListTag(stderr); |
5247 | | printf_stderr(" %s %s intrinsic size for container:\n", |
5248 | | aType == MIN_ISIZE ? "min" : "pref", |
5249 | | horizontalAxis ? "horizontal" : "vertical"); |
5250 | | #endif |
5251 | |
|
5252 | 0 | // If aFrame is a container for font size inflation, then shrink |
5253 | 0 | // wrapping inside of it should not apply font size inflation. |
5254 | 0 | AutoMaybeDisableFontInflation an(aFrame); |
5255 | 0 |
|
5256 | 0 | // We want the size this frame will contribute to the parent's inline-size, |
5257 | 0 | // so we work in the parent's writing mode; but if aFrame is orthogonal to |
5258 | 0 | // its parent, we'll need to look at its BSize instead of min/pref-ISize. |
5259 | 0 | const nsStylePosition* stylePos = aFrame->StylePosition(); |
5260 | 0 | StyleBoxSizing boxSizing = stylePos->mBoxSizing; |
5261 | 0 |
|
5262 | 0 | const nsStyleCoord& styleMinISize = |
5263 | 0 | horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight; |
5264 | 0 | const nsStyleCoord& styleISize = |
5265 | 0 | (aFlags & MIN_INTRINSIC_ISIZE) ? styleMinISize : |
5266 | 0 | (horizontalAxis ? stylePos->mWidth : stylePos->mHeight); |
5267 | 0 | MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) || |
5268 | 0 | styleISize.GetUnit() == eStyleUnit_Auto || |
5269 | 0 | styleISize.GetUnit() == eStyleUnit_Enumerated, |
5270 | 0 | "should only use MIN_INTRINSIC_ISIZE for intrinsic values"); |
5271 | 0 | const nsStyleCoord& styleMaxISize = |
5272 | 0 | horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight; |
5273 | 0 |
|
5274 | 0 | // We build up two values starting with the content box, and then |
5275 | 0 | // adding padding, border and margin. The result is normally |
5276 | 0 | // |result|. Then, when we handle 'width', 'min-width', and |
5277 | 0 | // 'max-width', we use the results we've been building in |min| as a |
5278 | 0 | // minimum, overriding 'min-width'. This ensures two things: |
5279 | 0 | // * that we don't let a value of 'box-sizing' specifying a width |
5280 | 0 | // smaller than the padding/border inside the box-sizing box give |
5281 | 0 | // a content width less than zero |
5282 | 0 | // * that we prevent tables from becoming smaller than their |
5283 | 0 | // intrinsic minimum width |
5284 | 0 | nscoord result = 0, min = 0; |
5285 | 0 |
|
5286 | 0 | nscoord maxISize; |
5287 | 0 | bool haveFixedMaxISize = GetAbsoluteCoord(styleMaxISize, maxISize); |
5288 | 0 | nscoord minISize; |
5289 | 0 |
|
5290 | 0 | // Treat "min-width: auto" as 0. |
5291 | 0 | bool haveFixedMinISize; |
5292 | 0 | if (eStyleUnit_Auto == styleMinISize.GetUnit()) { |
5293 | 0 | // NOTE: Technically, "auto" is supposed to behave like "min-content" on |
5294 | 0 | // flex items. However, we don't need to worry about that here, because |
5295 | 0 | // flex items' min-sizes are intentionally ignored until the flex |
5296 | 0 | // container explicitly considers them during space distribution. |
5297 | 0 | minISize = 0; |
5298 | 0 | haveFixedMinISize = true; |
5299 | 0 | } else { |
5300 | 0 | haveFixedMinISize = GetAbsoluteCoord(styleMinISize, minISize); |
5301 | 0 | } |
5302 | 0 |
|
5303 | 0 | PhysicalAxis ourInlineAxis = |
5304 | 0 | aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline); |
5305 | 0 | const bool isInlineAxis = aAxis == ourInlineAxis; |
5306 | 0 | // If we have a specified width (or a specified 'min-width' greater |
5307 | 0 | // than the specified 'max-width', which works out to the same thing), |
5308 | 0 | // don't even bother getting the frame's intrinsic width, because in |
5309 | 0 | // this case GetAbsoluteCoord(styleISize, w) will always succeed, so |
5310 | 0 | // we'll never need the intrinsic dimensions. |
5311 | 0 | if (styleISize.GetUnit() == eStyleUnit_Enumerated && |
5312 | 0 | (styleISize.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || |
5313 | 0 | styleISize.GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT)) { |
5314 | 0 | // -moz-fit-content and -moz-available enumerated widths compute intrinsic |
5315 | 0 | // widths just like auto. |
5316 | 0 | // For -moz-max-content and -moz-min-content, we handle them like |
5317 | 0 | // specified widths, but ignore box-sizing. |
5318 | 0 | boxSizing = StyleBoxSizing::Content; |
5319 | 0 | } else if (!styleISize.ConvertsToLength() && |
5320 | 0 | !(haveFixedMinISize && haveFixedMaxISize && maxISize <= minISize)) { |
5321 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5322 | | ++gNoiseIndent; |
5323 | | #endif |
5324 | 0 | if (MOZ_UNLIKELY(!isInlineAxis)) { |
5325 | 0 | IntrinsicSize intrinsicSize = aFrame->GetIntrinsicSize(); |
5326 | 0 | const nsStyleCoord intrinsicBCoord = |
5327 | 0 | horizontalAxis ? intrinsicSize.width : intrinsicSize.height; |
5328 | 0 | if (intrinsicBCoord.GetUnit() == eStyleUnit_Coord) { |
5329 | 0 | result = intrinsicBCoord.GetCoordValue(); |
5330 | 0 | } else { |
5331 | 0 | // We don't have an intrinsic bsize and we need aFrame's block-dir size. |
5332 | 0 | if (aFlags & BAIL_IF_REFLOW_NEEDED) { |
5333 | 0 | return NS_INTRINSIC_WIDTH_UNKNOWN; |
5334 | 0 | } |
5335 | 0 | // XXX Unfortunately, we probably don't know this yet, so this is wrong... |
5336 | 0 | // but it's not clear what we should do. If aFrame's inline size hasn't |
5337 | 0 | // been determined yet, we can't necessarily figure out its block size |
5338 | 0 | // either. For now, authors who put orthogonal elements into things like |
5339 | 0 | // buttons or table cells may have to explicitly provide sizes rather |
5340 | 0 | // than expecting intrinsic sizing to work "perfectly" in underspecified |
5341 | 0 | // cases. |
5342 | 0 | result = aFrame->BSize(); |
5343 | 0 | } |
5344 | 0 | } else { |
5345 | 0 | result = aType == MIN_ISIZE |
5346 | 0 | ? aFrame->GetMinISize(aRenderingContext) |
5347 | 0 | : aFrame->GetPrefISize(aRenderingContext); |
5348 | 0 | } |
5349 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5350 | | --gNoiseIndent; |
5351 | | nsFrame::IndentBy(stderr, gNoiseIndent); |
5352 | | static_cast<nsFrame*>(aFrame)->ListTag(stderr); |
5353 | | printf_stderr(" %s %s intrinsic size from frame is %d.\n", |
5354 | | aType == MIN_ISIZE ? "min" : "pref", |
5355 | | horizontalAxis ? "horizontal" : "vertical", |
5356 | | result); |
5357 | | #endif |
5358 | |
|
5359 | 0 | // Handle elements with an intrinsic ratio (or size) and a specified |
5360 | 0 | // height, min-height, or max-height. |
5361 | 0 | // NOTE: We treat "min-height:auto" as "0" for the purpose of this code, |
5362 | 0 | // since that's what it means in all cases except for on flex items -- and |
5363 | 0 | // even there, we're supposed to ignore it (i.e. treat it as 0) until the |
5364 | 0 | // flex container explicitly considers it. |
5365 | 0 | const nsStyleCoord& styleBSize = |
5366 | 0 | horizontalAxis ? stylePos->mHeight : stylePos->mWidth; |
5367 | 0 | const nsStyleCoord& styleMinBSize = |
5368 | 0 | horizontalAxis ? stylePos->mMinHeight : stylePos->mMinWidth; |
5369 | 0 | const nsStyleCoord& styleMaxBSize = |
5370 | 0 | horizontalAxis ? stylePos->mMaxHeight : stylePos->mMaxWidth; |
5371 | 0 |
|
5372 | 0 | if (styleBSize.GetUnit() != eStyleUnit_Auto || |
5373 | 0 | !(styleMinBSize.GetUnit() == eStyleUnit_Auto || |
5374 | 0 | (styleMinBSize.GetUnit() == eStyleUnit_Coord && |
5375 | 0 | styleMinBSize.GetCoordValue() == 0)) || |
5376 | 0 | styleMaxBSize.GetUnit() != eStyleUnit_None) { |
5377 | 0 |
|
5378 | 0 | nsSize ratio(aFrame->GetIntrinsicRatio()); |
5379 | 0 | nscoord ratioISize = (horizontalAxis ? ratio.width : ratio.height); |
5380 | 0 | nscoord ratioBSize = (horizontalAxis ? ratio.height : ratio.width); |
5381 | 0 | if (ratioBSize != 0) { |
5382 | 0 | AddStateBitToAncestors(aFrame, |
5383 | 0 | NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); |
5384 | 0 |
|
5385 | 0 | nscoord bSizeTakenByBoxSizing = |
5386 | 0 | GetDefiniteSizeTakenByBoxSizing(boxSizing, aFrame, !isInlineAxis, |
5387 | 0 | aFlags & IGNORE_PADDING, |
5388 | 0 | aPercentageBasis); |
5389 | 0 | // NOTE: This is only the minContentSize if we've been passed MIN_INTRINSIC_ISIZE |
5390 | 0 | // (which is fine, because this should only be used inside a check for that flag). |
5391 | 0 | nscoord minContentSize = result; |
5392 | 0 | nscoord h; |
5393 | 0 | if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis, &h) || |
5394 | 0 | (aPercentageBasis.isNothing() && |
5395 | 0 | GetPercentBSize(styleBSize, aFrame, horizontalAxis, h))) { |
5396 | 0 | h = std::max(0, h - bSizeTakenByBoxSizing); |
5397 | 0 | result = NSCoordMulDiv(h, ratioISize, ratioBSize); |
5398 | 0 | } |
5399 | 0 |
|
5400 | 0 | if (GetDefiniteSize(styleMaxBSize, aFrame, !isInlineAxis, aPercentageBasis, &h) || |
5401 | 0 | (aPercentageBasis.isNothing() && |
5402 | 0 | GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h))) { |
5403 | 0 | h = std::max(0, h - bSizeTakenByBoxSizing); |
5404 | 0 | nscoord maxISize = NSCoordMulDiv(h, ratioISize, ratioBSize); |
5405 | 0 | if (maxISize < result) { |
5406 | 0 | result = maxISize; |
5407 | 0 | } |
5408 | 0 | if (maxISize < minContentSize) { |
5409 | 0 | minContentSize = maxISize; |
5410 | 0 | } |
5411 | 0 | } |
5412 | 0 |
|
5413 | 0 | if (GetDefiniteSize(styleMinBSize, aFrame, !isInlineAxis, aPercentageBasis, &h) || |
5414 | 0 | (aPercentageBasis.isNothing() && |
5415 | 0 | GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h))) { |
5416 | 0 | h = std::max(0, h - bSizeTakenByBoxSizing); |
5417 | 0 | nscoord minISize = NSCoordMulDiv(h, ratioISize, ratioBSize); |
5418 | 0 | if (minISize > result) { |
5419 | 0 | result = minISize; |
5420 | 0 | } |
5421 | 0 | if (minISize > minContentSize) { |
5422 | 0 | minContentSize = minISize; |
5423 | 0 | } |
5424 | 0 | } |
5425 | 0 | if (MOZ_UNLIKELY(aFlags & nsLayoutUtils::MIN_INTRINSIC_ISIZE)) { |
5426 | 0 | // This is the 'min-width/height:auto' "transferred size" piece of: |
5427 | 0 | // https://www.w3.org/TR/css-flexbox-1/#min-width-automatic-minimum-size |
5428 | 0 | // https://drafts.csswg.org/css-grid/#min-size-auto |
5429 | 0 | result = std::min(result, minContentSize); |
5430 | 0 | } |
5431 | 0 | } |
5432 | 0 | } |
5433 | 0 | } |
5434 | 0 |
|
5435 | 0 | if (aFrame->IsTableFrame()) { |
5436 | 0 | // Tables can't shrink smaller than their intrinsic minimum width, |
5437 | 0 | // no matter what. |
5438 | 0 | min = aFrame->GetMinISize(aRenderingContext); |
5439 | 0 | } |
5440 | 0 |
|
5441 | 0 | nscoord pmPercentageBasis = NS_UNCONSTRAINEDSIZE; |
5442 | 0 | if (aPercentageBasis.isSome()) { |
5443 | 0 | // The padding/margin percentage basis is the inline-size in the parent's |
5444 | 0 | // writing-mode. |
5445 | 0 | auto childWM = aFrame->GetWritingMode(); |
5446 | 0 | pmPercentageBasis = |
5447 | 0 | aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM) ? |
5448 | 0 | aPercentageBasis->BSize(childWM) : |
5449 | 0 | aPercentageBasis->ISize(childWM); |
5450 | 0 | } |
5451 | 0 | nsIFrame::IntrinsicISizeOffsetData offsets = |
5452 | 0 | MOZ_LIKELY(isInlineAxis) ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis) |
5453 | 0 | : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis); |
5454 | 0 | nscoord contentBoxSize = result; |
5455 | 0 | result = AddIntrinsicSizeOffset(aRenderingContext, aFrame, offsets, aType, |
5456 | 0 | boxSizing, result, min, styleISize, |
5457 | 0 | haveFixedMinISize ? &minISize : nullptr, |
5458 | 0 | styleMinISize, |
5459 | 0 | haveFixedMaxISize ? &maxISize : nullptr, |
5460 | 0 | styleMaxISize, |
5461 | 0 | aFlags, aAxis); |
5462 | 0 | nscoord overflow = result - aMarginBoxMinSizeClamp; |
5463 | 0 | if (MOZ_UNLIKELY(overflow > 0)) { |
5464 | 0 | nscoord newContentBoxSize = std::max(nscoord(0), contentBoxSize - overflow); |
5465 | 0 | result -= contentBoxSize - newContentBoxSize; |
5466 | 0 | } |
5467 | 0 |
|
5468 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5469 | | nsFrame::IndentBy(stderr, gNoiseIndent); |
5470 | | static_cast<nsFrame*>(aFrame)->ListTag(stderr); |
5471 | | printf_stderr(" %s %s intrinsic size for container is %d twips.\n", |
5472 | | aType == MIN_ISIZE ? "min" : "pref", |
5473 | | horizontalAxis ? "horizontal" : "vertical", |
5474 | | result); |
5475 | | #endif |
5476 | |
|
5477 | 0 | return result; |
5478 | 0 | } |
5479 | | |
5480 | | /* static */ nscoord |
5481 | | nsLayoutUtils::IntrinsicForContainer(gfxContext* aRenderingContext, |
5482 | | nsIFrame* aFrame, |
5483 | | IntrinsicISizeType aType, |
5484 | | uint32_t aFlags) |
5485 | 0 | { |
5486 | 0 | MOZ_ASSERT(aFrame && aFrame->GetParent()); |
5487 | 0 | // We want the size aFrame will contribute to its parent's inline-size. |
5488 | 0 | PhysicalAxis axis = |
5489 | 0 | aFrame->GetParent()->GetWritingMode().PhysicalAxis(eLogicalAxisInline); |
5490 | 0 | return IntrinsicForAxis(axis, aRenderingContext, aFrame, aType, Nothing(), aFlags); |
5491 | 0 | } |
5492 | | |
5493 | | /* static */ nscoord |
5494 | | nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, |
5495 | | gfxContext* aRC, |
5496 | | nsIFrame* aFrame, |
5497 | | IntrinsicISizeType aType, |
5498 | | const LogicalSize& aPercentageBasis, |
5499 | | uint32_t aFlags) |
5500 | 0 | { |
5501 | 0 | MOZ_ASSERT(aFrame); |
5502 | 0 | MOZ_ASSERT(aFrame->IsFlexOrGridItem(), |
5503 | 0 | "only grid/flex items have this behavior currently"); |
5504 | 0 |
|
5505 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5506 | | nsFrame::IndentBy(stderr, gNoiseIndent); |
5507 | | static_cast<nsFrame*>(aFrame)->ListTag(stderr); |
5508 | | printf_stderr(" %s min-isize for %s WM:\n", |
5509 | | aType == MIN_ISIZE ? "min" : "pref", |
5510 | | aWM.IsVertical() ? "vertical" : "horizontal"); |
5511 | | #endif |
5512 | |
|
5513 | 0 | // Note: this method is only meant for grid/flex items. |
5514 | 0 | const nsStylePosition* const stylePos = aFrame->StylePosition(); |
5515 | 0 | const nsStyleCoord* style = aAxis == eAxisHorizontal ? &stylePos->mMinWidth |
5516 | 0 | : &stylePos->mMinHeight; |
5517 | 0 | nscoord minSize; |
5518 | 0 | nscoord* fixedMinSize = nullptr; |
5519 | 0 | auto minSizeUnit = style->GetUnit(); |
5520 | 0 | if (minSizeUnit == eStyleUnit_Auto) { |
5521 | 0 | if (aFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) { |
5522 | 0 | style = aAxis == eAxisHorizontal ? &stylePos->mWidth |
5523 | 0 | : &stylePos->mHeight; |
5524 | 0 | if (GetAbsoluteCoord(*style, minSize)) { |
5525 | 0 | // We have a definite width/height. This is the "specified size" in: |
5526 | 0 | // https://drafts.csswg.org/css-grid/#min-size-auto |
5527 | 0 | fixedMinSize = &minSize; |
5528 | 0 | } else if (::IsReplacedBoxResolvedAgainstZero(aFrame, *style, |
5529 | 0 | aAxis == eAxisHorizontal ? stylePos->mMaxWidth |
5530 | 0 | : stylePos->mMaxHeight)) { |
5531 | 0 | // XXX bug 1463700: this doesn't handle calc() according to spec |
5532 | 0 | minSize = 0; |
5533 | 0 | fixedMinSize = &minSize; |
5534 | 0 | } |
5535 | 0 | // fall through - the caller will have to deal with "transferred size" |
5536 | 0 | } else { |
5537 | 0 | // min-[width|height]:auto with overflow != visible computes to zero. |
5538 | 0 | minSize = 0; |
5539 | 0 | fixedMinSize = &minSize; |
5540 | 0 | } |
5541 | 0 | } else if (GetAbsoluteCoord(*style, minSize)) { |
5542 | 0 | fixedMinSize = &minSize; |
5543 | 0 | } else if (minSizeUnit != eStyleUnit_Enumerated) { |
5544 | 0 | MOZ_ASSERT(style->HasPercent()); |
5545 | 0 | minSize = 0; |
5546 | 0 | fixedMinSize = &minSize; |
5547 | 0 | } |
5548 | 0 |
|
5549 | 0 | if (!fixedMinSize) { |
5550 | 0 | // Let the caller deal with the "content size" cases. |
5551 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5552 | | nsFrame::IndentBy(stderr, gNoiseIndent); |
5553 | | static_cast<nsFrame*>(aFrame)->ListTag(stderr); |
5554 | | printf_stderr(" %s min-isize is indefinite.\n", |
5555 | | aType == MIN_ISIZE ? "min" : "pref"); |
5556 | | #endif |
5557 | 0 | return NS_UNCONSTRAINEDSIZE; |
5558 | 0 | } |
5559 | 0 |
|
5560 | 0 | // If aFrame is a container for font size inflation, then shrink |
5561 | 0 | // wrapping inside of it should not apply font size inflation. |
5562 | 0 | AutoMaybeDisableFontInflation an(aFrame); |
5563 | 0 |
|
5564 | 0 | // The padding/margin percentage basis is the inline-size in the parent's |
5565 | 0 | // writing-mode. |
5566 | 0 | auto childWM = aFrame->GetWritingMode(); |
5567 | 0 | nscoord pmPercentageBasis = |
5568 | 0 | aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM) ? |
5569 | 0 | aPercentageBasis.BSize(childWM) : |
5570 | 0 | aPercentageBasis.ISize(childWM); |
5571 | 0 | PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(eLogicalAxisInline); |
5572 | 0 | nsIFrame::IntrinsicISizeOffsetData offsets = |
5573 | 0 | ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis) |
5574 | 0 | : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis); |
5575 | 0 | nscoord result = 0; |
5576 | 0 | nscoord min = 0; |
5577 | 0 | const nsStyleCoord& maxISize = |
5578 | 0 | aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight; |
5579 | 0 | result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType, |
5580 | 0 | stylePos->mBoxSizing, |
5581 | 0 | result, min, *style, fixedMinSize, |
5582 | 0 | *style, nullptr, maxISize, aFlags, aAxis); |
5583 | 0 |
|
5584 | | #ifdef DEBUG_INTRINSIC_WIDTH |
5585 | | nsFrame::IndentBy(stderr, gNoiseIndent); |
5586 | | static_cast<nsFrame*>(aFrame)->ListTag(stderr); |
5587 | | printf_stderr(" %s min-isize is %d twips.\n", |
5588 | | aType == MIN_ISIZE ? "min" : "pref", result); |
5589 | | #endif |
5590 | |
|
5591 | 0 | return result; |
5592 | 0 | } |
5593 | | |
5594 | | /* static */ nscoord |
5595 | | nsLayoutUtils::ComputeCBDependentValue(nscoord aPercentBasis, |
5596 | | const nsStyleCoord& aCoord) |
5597 | 0 | { |
5598 | 0 | NS_WARNING_ASSERTION( |
5599 | 0 | aPercentBasis != NS_UNCONSTRAINEDSIZE, |
5600 | 0 | "have unconstrained width or height; this should only result from very " |
5601 | 0 | "large sizes, not attempts at intrinsic size calculation"); |
5602 | 0 |
|
5603 | 0 | if (aCoord.IsCoordPercentCalcUnit()) { |
5604 | 0 | return aCoord.ComputeCoordPercentCalc(aPercentBasis); |
5605 | 0 | } |
5606 | 0 | NS_ASSERTION(aCoord.GetUnit() == eStyleUnit_None || |
5607 | 0 | aCoord.GetUnit() == eStyleUnit_Auto, |
5608 | 0 | "unexpected width value"); |
5609 | 0 | return 0; |
5610 | 0 | } |
5611 | | |
5612 | | /* static */ nscoord |
5613 | | nsLayoutUtils::ComputeBSizeDependentValue( |
5614 | | nscoord aContainingBlockBSize, |
5615 | | const nsStyleCoord& aCoord) |
5616 | 0 | { |
5617 | 0 | // XXXldb Some callers explicitly check aContainingBlockBSize |
5618 | 0 | // against NS_AUTOHEIGHT *and* unit against eStyleUnit_Percent or |
5619 | 0 | // calc()s containing percents before calling this function. |
5620 | 0 | // However, it would be much more likely to catch problems without |
5621 | 0 | // the unit conditions. |
5622 | 0 | // XXXldb Many callers pass a non-'auto' containing block height when |
5623 | 0 | // according to CSS2.1 they should be passing 'auto'. |
5624 | 0 | MOZ_ASSERT(NS_AUTOHEIGHT != aContainingBlockBSize || |
5625 | 0 | !aCoord.HasPercent(), |
5626 | 0 | "unexpected containing block block-size"); |
5627 | 0 |
|
5628 | 0 | if (aCoord.IsCoordPercentCalcUnit()) { |
5629 | 0 | return aCoord.ComputeCoordPercentCalc(aContainingBlockBSize); |
5630 | 0 | } |
5631 | 0 | |
5632 | 0 | NS_ASSERTION(aCoord.GetUnit() == eStyleUnit_None || |
5633 | 0 | aCoord.GetUnit() == eStyleUnit_Auto, |
5634 | 0 | "unexpected block-size value"); |
5635 | 0 | return 0; |
5636 | 0 | } |
5637 | | |
5638 | | /* static */ void |
5639 | | nsLayoutUtils::MarkDescendantsDirty(nsIFrame *aSubtreeRoot) |
5640 | 0 | { |
5641 | 0 | AutoTArray<nsIFrame*, 4> subtrees; |
5642 | 0 | subtrees.AppendElement(aSubtreeRoot); |
5643 | 0 |
|
5644 | 0 | // dirty descendants, iterating over subtrees that may include |
5645 | 0 | // additional subtrees associated with placeholders |
5646 | 0 | do { |
5647 | 0 | nsIFrame *subtreeRoot = subtrees.PopLastElement(); |
5648 | 0 |
|
5649 | 0 | // Mark all descendants dirty (using an nsTArray stack rather than |
5650 | 0 | // recursion). |
5651 | 0 | // Note that ReflowInput::InitResizeFlags has some similar |
5652 | 0 | // code; see comments there for how and why it differs. |
5653 | 0 | AutoTArray<nsIFrame*, 32> stack; |
5654 | 0 | stack.AppendElement(subtreeRoot); |
5655 | 0 |
|
5656 | 0 | do { |
5657 | 0 | nsIFrame *f = stack.PopLastElement(); |
5658 | 0 |
|
5659 | 0 | f->MarkIntrinsicISizesDirty(); |
5660 | 0 |
|
5661 | 0 | if (f->IsPlaceholderFrame()) { |
5662 | 0 | nsIFrame *oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); |
5663 | 0 | if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { |
5664 | 0 | // We have another distinct subtree we need to mark. |
5665 | 0 | subtrees.AppendElement(oof); |
5666 | 0 | } |
5667 | 0 | } |
5668 | 0 |
|
5669 | 0 | nsIFrame::ChildListIterator lists(f); |
5670 | 0 | for (; !lists.IsDone(); lists.Next()) { |
5671 | 0 | nsFrameList::Enumerator childFrames(lists.CurrentList()); |
5672 | 0 | for (; !childFrames.AtEnd(); childFrames.Next()) { |
5673 | 0 | nsIFrame* kid = childFrames.get(); |
5674 | 0 | stack.AppendElement(kid); |
5675 | 0 | } |
5676 | 0 | } |
5677 | 0 | } while (stack.Length() != 0); |
5678 | 0 | } while (subtrees.Length() != 0); |
5679 | 0 | } |
5680 | | |
5681 | | /* static */ |
5682 | | void |
5683 | | nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(nsIFrame* aFrame) |
5684 | 0 | { |
5685 | 0 | AutoTArray<nsIFrame*, 32> stack; |
5686 | 0 | stack.AppendElement(aFrame); |
5687 | 0 |
|
5688 | 0 | do { |
5689 | 0 | nsIFrame* f = stack.PopLastElement(); |
5690 | 0 |
|
5691 | 0 | if (!f->HasAnyStateBits( |
5692 | 0 | NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { |
5693 | 0 | continue; |
5694 | 0 | } |
5695 | 0 | f->MarkIntrinsicISizesDirty(); |
5696 | 0 |
|
5697 | 0 | for (nsIFrame::ChildListIterator lists(f); !lists.IsDone(); lists.Next()) { |
5698 | 0 | for (nsIFrame* kid : lists.CurrentList()) { |
5699 | 0 | stack.AppendElement(kid); |
5700 | 0 | } |
5701 | 0 | } |
5702 | 0 | } while (stack.Length() != 0); |
5703 | 0 | } |
5704 | | |
5705 | | nsSize |
5706 | | nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(nscoord minWidth, nscoord minHeight, |
5707 | | nscoord maxWidth, nscoord maxHeight, |
5708 | | nscoord tentWidth, nscoord tentHeight) |
5709 | 0 | { |
5710 | 0 | // Now apply min/max-width/height - CSS 2.1 sections 10.4 and 10.7: |
5711 | 0 |
|
5712 | 0 | if (minWidth > maxWidth) |
5713 | 0 | maxWidth = minWidth; |
5714 | 0 | if (minHeight > maxHeight) |
5715 | 0 | maxHeight = minHeight; |
5716 | 0 |
|
5717 | 0 | nscoord heightAtMaxWidth, heightAtMinWidth, |
5718 | 0 | widthAtMaxHeight, widthAtMinHeight; |
5719 | 0 |
|
5720 | 0 | if (tentWidth > 0) { |
5721 | 0 | heightAtMaxWidth = NSCoordMulDiv(maxWidth, tentHeight, tentWidth); |
5722 | 0 | if (heightAtMaxWidth < minHeight) |
5723 | 0 | heightAtMaxWidth = minHeight; |
5724 | 0 | heightAtMinWidth = NSCoordMulDiv(minWidth, tentHeight, tentWidth); |
5725 | 0 | if (heightAtMinWidth > maxHeight) |
5726 | 0 | heightAtMinWidth = maxHeight; |
5727 | 0 | } else { |
5728 | 0 | heightAtMaxWidth = heightAtMinWidth = NS_CSS_MINMAX(tentHeight, minHeight, maxHeight); |
5729 | 0 | } |
5730 | 0 |
|
5731 | 0 | if (tentHeight > 0) { |
5732 | 0 | widthAtMaxHeight = NSCoordMulDiv(maxHeight, tentWidth, tentHeight); |
5733 | 0 | if (widthAtMaxHeight < minWidth) |
5734 | 0 | widthAtMaxHeight = minWidth; |
5735 | 0 | widthAtMinHeight = NSCoordMulDiv(minHeight, tentWidth, tentHeight); |
5736 | 0 | if (widthAtMinHeight > maxWidth) |
5737 | 0 | widthAtMinHeight = maxWidth; |
5738 | 0 | } else { |
5739 | 0 | widthAtMaxHeight = widthAtMinHeight = NS_CSS_MINMAX(tentWidth, minWidth, maxWidth); |
5740 | 0 | } |
5741 | 0 |
|
5742 | 0 | // The table at http://www.w3.org/TR/CSS21/visudet.html#min-max-widths : |
5743 | 0 |
|
5744 | 0 | nscoord width, height; |
5745 | 0 |
|
5746 | 0 | if (tentWidth > maxWidth) { |
5747 | 0 | if (tentHeight > maxHeight) { |
5748 | 0 | if (int64_t(maxWidth) * int64_t(tentHeight) <= |
5749 | 0 | int64_t(maxHeight) * int64_t(tentWidth)) { |
5750 | 0 | width = maxWidth; |
5751 | 0 | height = heightAtMaxWidth; |
5752 | 0 | } else { |
5753 | 0 | width = widthAtMaxHeight; |
5754 | 0 | height = maxHeight; |
5755 | 0 | } |
5756 | 0 | } else { |
5757 | 0 | // This also covers "(w > max-width) and (h < min-height)" since in |
5758 | 0 | // that case (max-width/w < 1), and with (h < min-height): |
5759 | 0 | // max(max-width * h/w, min-height) == min-height |
5760 | 0 | width = maxWidth; |
5761 | 0 | height = heightAtMaxWidth; |
5762 | 0 | } |
5763 | 0 | } else if (tentWidth < minWidth) { |
5764 | 0 | if (tentHeight < minHeight) { |
5765 | 0 | if (int64_t(minWidth) * int64_t(tentHeight) <= |
5766 | 0 | int64_t(minHeight) * int64_t(tentWidth)) { |
5767 | 0 | width = widthAtMinHeight; |
5768 | 0 | height = minHeight; |
5769 | 0 | } else { |
5770 | 0 | width = minWidth; |
5771 | 0 | height = heightAtMinWidth; |
5772 | 0 | } |
5773 | 0 | } else { |
5774 | 0 | // This also covers "(w < min-width) and (h > max-height)" since in |
5775 | 0 | // that case (min-width/w > 1), and with (h > max-height): |
5776 | 0 | // min(min-width * h/w, max-height) == max-height |
5777 | 0 | width = minWidth; |
5778 | 0 | height = heightAtMinWidth; |
5779 | 0 | } |
5780 | 0 | } else { |
5781 | 0 | if (tentHeight > maxHeight) { |
5782 | 0 | width = widthAtMaxHeight; |
5783 | 0 | height = maxHeight; |
5784 | 0 | } else if (tentHeight < minHeight) { |
5785 | 0 | width = widthAtMinHeight; |
5786 | 0 | height = minHeight; |
5787 | 0 | } else { |
5788 | 0 | width = tentWidth; |
5789 | 0 | height = tentHeight; |
5790 | 0 | } |
5791 | 0 | } |
5792 | 0 |
|
5793 | 0 | return nsSize(width, height); |
5794 | 0 | } |
5795 | | |
5796 | | /* static */ nscoord |
5797 | | nsLayoutUtils::MinISizeFromInline(nsIFrame* aFrame, |
5798 | | gfxContext* aRenderingContext) |
5799 | 0 | { |
5800 | 0 | NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(), |
5801 | 0 | "should not be container for font size inflation"); |
5802 | 0 |
|
5803 | 0 | nsIFrame::InlineMinISizeData data; |
5804 | 0 | DISPLAY_MIN_INLINE_SIZE(aFrame, data.mPrevLines); |
5805 | 0 | aFrame->AddInlineMinISize(aRenderingContext, &data); |
5806 | 0 | data.ForceBreak(); |
5807 | 0 | return data.mPrevLines; |
5808 | 0 | } |
5809 | | |
5810 | | /* static */ nscoord |
5811 | | nsLayoutUtils::PrefISizeFromInline(nsIFrame* aFrame, |
5812 | | gfxContext* aRenderingContext) |
5813 | 0 | { |
5814 | 0 | NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(), |
5815 | 0 | "should not be container for font size inflation"); |
5816 | 0 |
|
5817 | 0 | nsIFrame::InlinePrefISizeData data; |
5818 | 0 | DISPLAY_PREF_INLINE_SIZE(aFrame, data.mPrevLines); |
5819 | 0 | aFrame->AddInlinePrefISize(aRenderingContext, &data); |
5820 | 0 | data.ForceBreak(); |
5821 | 0 | return data.mPrevLines; |
5822 | 0 | } |
5823 | | |
5824 | | static nscolor |
5825 | | DarkenColor(nscolor aColor) |
5826 | 0 | { |
5827 | 0 | uint16_t hue, sat, value; |
5828 | 0 | uint8_t alpha; |
5829 | 0 |
|
5830 | 0 | // convert the RBG to HSV so we can get the lightness (which is the v) |
5831 | 0 | NS_RGB2HSV(aColor, hue, sat, value, alpha); |
5832 | 0 |
|
5833 | 0 | // The goal here is to send white to black while letting colored |
5834 | 0 | // stuff stay colored... So we adopt the following approach. |
5835 | 0 | // Something with sat = 0 should end up with value = 0. Something |
5836 | 0 | // with a high sat can end up with a high value and it's ok.... At |
5837 | 0 | // the same time, we don't want to make things lighter. Do |
5838 | 0 | // something simple, since it seems to work. |
5839 | 0 | if (value > sat) { |
5840 | 0 | value = sat; |
5841 | 0 | // convert this color back into the RGB color space. |
5842 | 0 | NS_HSV2RGB(aColor, hue, sat, value, alpha); |
5843 | 0 | } |
5844 | 0 | return aColor; |
5845 | 0 | } |
5846 | | |
5847 | | // Check whether we should darken text/decoration colors. We need to do this if |
5848 | | // background images and colors are being suppressed, because that means |
5849 | | // light text will not be visible against the (presumed light-colored) background. |
5850 | | static bool |
5851 | | ShouldDarkenColors(nsPresContext* aPresContext) |
5852 | 0 | { |
5853 | 0 | return !aPresContext->GetBackgroundColorDraw() && |
5854 | 0 | !aPresContext->GetBackgroundImageDraw(); |
5855 | 0 | } |
5856 | | |
5857 | | nscolor |
5858 | | nsLayoutUtils::DarkenColorIfNeeded(nsIFrame* aFrame, nscolor aColor) |
5859 | 0 | { |
5860 | 0 | if (ShouldDarkenColors(aFrame->PresContext())) { |
5861 | 0 | return DarkenColor(aColor); |
5862 | 0 | } |
5863 | 0 | return aColor; |
5864 | 0 | } |
5865 | | |
5866 | | gfxFloat |
5867 | | nsLayoutUtils::GetSnappedBaselineY(nsIFrame* aFrame, gfxContext* aContext, |
5868 | | nscoord aY, nscoord aAscent) |
5869 | 0 | { |
5870 | 0 | gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel(); |
5871 | 0 | gfxFloat baseline = gfxFloat(aY) + aAscent; |
5872 | 0 | gfxRect putativeRect(0, baseline/appUnitsPerDevUnit, 1, 1); |
5873 | 0 | if (!aContext->UserToDevicePixelSnapped(putativeRect, true)) |
5874 | 0 | return baseline; |
5875 | 0 | return aContext->DeviceToUser(putativeRect.TopLeft()).y * appUnitsPerDevUnit; |
5876 | 0 | } |
5877 | | |
5878 | | gfxFloat |
5879 | | nsLayoutUtils::GetSnappedBaselineX(nsIFrame* aFrame, gfxContext* aContext, |
5880 | | nscoord aX, nscoord aAscent) |
5881 | 0 | { |
5882 | 0 | gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel(); |
5883 | 0 | gfxFloat baseline = gfxFloat(aX) + aAscent; |
5884 | 0 | gfxRect putativeRect(baseline / appUnitsPerDevUnit, 0, 1, 1); |
5885 | 0 | if (!aContext->UserToDevicePixelSnapped(putativeRect, true)) { |
5886 | 0 | return baseline; |
5887 | 0 | } |
5888 | 0 | return aContext->DeviceToUser(putativeRect.TopLeft()).x * appUnitsPerDevUnit; |
5889 | 0 | } |
5890 | | |
5891 | | // Hard limit substring lengths to 8000 characters ... this lets us statically |
5892 | | // size the cluster buffer array in FindSafeLength |
5893 | 0 | #define MAX_GFX_TEXT_BUF_SIZE 8000 |
5894 | | |
5895 | | static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength, |
5896 | | uint32_t aMaxChunkLength) |
5897 | 0 | { |
5898 | 0 | if (aLength <= aMaxChunkLength) |
5899 | 0 | return aLength; |
5900 | 0 | |
5901 | 0 | int32_t len = aMaxChunkLength; |
5902 | 0 |
|
5903 | 0 | // Ensure that we don't break inside a surrogate pair |
5904 | 0 | while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) { |
5905 | 0 | len--; |
5906 | 0 | } |
5907 | 0 | if (len == 0) { |
5908 | 0 | // We don't want our caller to go into an infinite loop, so don't |
5909 | 0 | // return zero. It's hard to imagine how we could actually get here |
5910 | 0 | // unless there are languages that allow clusters of arbitrary size. |
5911 | 0 | // If there are and someone feeds us a 500+ character cluster, too |
5912 | 0 | // bad. |
5913 | 0 | return aMaxChunkLength; |
5914 | 0 | } |
5915 | 0 | return len; |
5916 | 0 | } |
5917 | | |
5918 | | static int32_t GetMaxChunkLength(nsFontMetrics& aFontMetrics) |
5919 | 0 | { |
5920 | 0 | return std::min(aFontMetrics.GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE); |
5921 | 0 | } |
5922 | | |
5923 | | nscoord |
5924 | | nsLayoutUtils::AppUnitWidthOfString(const char16_t *aString, |
5925 | | uint32_t aLength, |
5926 | | nsFontMetrics& aFontMetrics, |
5927 | | DrawTarget* aDrawTarget) |
5928 | 0 | { |
5929 | 0 | uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); |
5930 | 0 | nscoord width = 0; |
5931 | 0 | while (aLength > 0) { |
5932 | 0 | int32_t len = FindSafeLength(aString, aLength, maxChunkLength); |
5933 | 0 | width += aFontMetrics.GetWidth(aString, len, aDrawTarget); |
5934 | 0 | aLength -= len; |
5935 | 0 | aString += len; |
5936 | 0 | } |
5937 | 0 | return width; |
5938 | 0 | } |
5939 | | |
5940 | | nscoord |
5941 | | nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString, |
5942 | | uint32_t aLength, |
5943 | | const nsIFrame* aFrame, |
5944 | | nsFontMetrics& aFontMetrics, |
5945 | | gfxContext& aContext) |
5946 | 0 | { |
5947 | 0 | nsPresContext* presContext = aFrame->PresContext(); |
5948 | 0 | if (presContext->BidiEnabled()) { |
5949 | 0 | nsBidiLevel level = |
5950 | 0 | nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style()); |
5951 | 0 | return nsBidiPresUtils::MeasureTextWidth(aString, aLength, level, |
5952 | 0 | presContext, aContext, |
5953 | 0 | aFontMetrics); |
5954 | 0 | } |
5955 | 0 | aFontMetrics.SetTextRunRTL(false); |
5956 | 0 | aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical()); |
5957 | 0 | aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation); |
5958 | 0 | return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics, |
5959 | 0 | aContext.GetDrawTarget()); |
5960 | 0 | } |
5961 | | |
5962 | | bool |
5963 | | nsLayoutUtils::StringWidthIsGreaterThan(const nsString& aString, |
5964 | | nsFontMetrics& aFontMetrics, |
5965 | | DrawTarget* aDrawTarget, |
5966 | | nscoord aWidth) |
5967 | 0 | { |
5968 | 0 | const char16_t *string = aString.get(); |
5969 | 0 | uint32_t length = aString.Length(); |
5970 | 0 | uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); |
5971 | 0 | nscoord width = 0; |
5972 | 0 | while (length > 0) { |
5973 | 0 | int32_t len = FindSafeLength(string, length, maxChunkLength); |
5974 | 0 | width += aFontMetrics.GetWidth(string, len, aDrawTarget); |
5975 | 0 | if (width > aWidth) { |
5976 | 0 | return true; |
5977 | 0 | } |
5978 | 0 | length -= len; |
5979 | 0 | string += len; |
5980 | 0 | } |
5981 | 0 | return false; |
5982 | 0 | } |
5983 | | |
5984 | | nsBoundingMetrics |
5985 | | nsLayoutUtils::AppUnitBoundsOfString(const char16_t* aString, |
5986 | | uint32_t aLength, |
5987 | | nsFontMetrics& aFontMetrics, |
5988 | | DrawTarget* aDrawTarget) |
5989 | 0 | { |
5990 | 0 | uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); |
5991 | 0 | int32_t len = FindSafeLength(aString, aLength, maxChunkLength); |
5992 | 0 | // Assign directly in the first iteration. This ensures that |
5993 | 0 | // negative ascent/descent can be returned and the left bearing |
5994 | 0 | // is properly initialized. |
5995 | 0 | nsBoundingMetrics totalMetrics = |
5996 | 0 | aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget); |
5997 | 0 | aLength -= len; |
5998 | 0 | aString += len; |
5999 | 0 |
|
6000 | 0 | while (aLength > 0) { |
6001 | 0 | len = FindSafeLength(aString, aLength, maxChunkLength); |
6002 | 0 | nsBoundingMetrics metrics = |
6003 | 0 | aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget); |
6004 | 0 | totalMetrics += metrics; |
6005 | 0 | aLength -= len; |
6006 | 0 | aString += len; |
6007 | 0 | } |
6008 | 0 | return totalMetrics; |
6009 | 0 | } |
6010 | | |
6011 | | void |
6012 | | nsLayoutUtils::DrawString(const nsIFrame* aFrame, |
6013 | | nsFontMetrics& aFontMetrics, |
6014 | | gfxContext* aContext, |
6015 | | const char16_t* aString, |
6016 | | int32_t aLength, |
6017 | | nsPoint aPoint, |
6018 | | ComputedStyle* aComputedStyle, |
6019 | | DrawStringFlags aFlags) |
6020 | 0 | { |
6021 | 0 | nsresult rv = NS_ERROR_FAILURE; |
6022 | 0 |
|
6023 | 0 | // If caller didn't pass a style, use the frame's. |
6024 | 0 | if (!aComputedStyle) { |
6025 | 0 | aComputedStyle = aFrame->Style(); |
6026 | 0 | } |
6027 | 0 |
|
6028 | 0 | if (aFlags & DrawStringFlags::eForceHorizontal) { |
6029 | 0 | aFontMetrics.SetVertical(false); |
6030 | 0 | } else { |
6031 | 0 | aFontMetrics.SetVertical(WritingMode(aComputedStyle).IsVertical()); |
6032 | 0 | } |
6033 | 0 |
|
6034 | 0 | aFontMetrics.SetTextOrientation( |
6035 | 0 | aComputedStyle->StyleVisibility()->mTextOrientation); |
6036 | 0 |
|
6037 | 0 | nsPresContext* presContext = aFrame->PresContext(); |
6038 | 0 | if (presContext->BidiEnabled()) { |
6039 | 0 | nsBidiLevel level = |
6040 | 0 | nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle); |
6041 | 0 | rv = nsBidiPresUtils::RenderText(aString, aLength, level, |
6042 | 0 | presContext, *aContext, |
6043 | 0 | aContext->GetDrawTarget(), aFontMetrics, |
6044 | 0 | aPoint.x, aPoint.y); |
6045 | 0 | } |
6046 | 0 | if (NS_FAILED(rv)) |
6047 | 0 | { |
6048 | 0 | aFontMetrics.SetTextRunRTL(false); |
6049 | 0 | DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext); |
6050 | 0 | } |
6051 | 0 | } |
6052 | | |
6053 | | void |
6054 | | nsLayoutUtils::DrawUniDirString(const char16_t* aString, |
6055 | | uint32_t aLength, |
6056 | | const nsPoint& aPoint, |
6057 | | nsFontMetrics& aFontMetrics, |
6058 | | gfxContext& aContext) |
6059 | 0 | { |
6060 | 0 | nscoord x = aPoint.x; |
6061 | 0 | nscoord y = aPoint.y; |
6062 | 0 |
|
6063 | 0 | uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); |
6064 | 0 | if (aLength <= maxChunkLength) { |
6065 | 0 | aFontMetrics.DrawString(aString, aLength, x, y, &aContext, |
6066 | 0 | aContext.GetDrawTarget()); |
6067 | 0 | return; |
6068 | 0 | } |
6069 | 0 | |
6070 | 0 | bool isRTL = aFontMetrics.GetTextRunRTL(); |
6071 | 0 |
|
6072 | 0 | // If we're drawing right to left, we must start at the end. |
6073 | 0 | if (isRTL) { |
6074 | 0 | x += nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics, |
6075 | 0 | aContext.GetDrawTarget()); |
6076 | 0 | } |
6077 | 0 |
|
6078 | 0 | while (aLength > 0) { |
6079 | 0 | int32_t len = FindSafeLength(aString, aLength, maxChunkLength); |
6080 | 0 | nscoord width = aFontMetrics.GetWidth(aString, len, aContext.GetDrawTarget()); |
6081 | 0 | if (isRTL) { |
6082 | 0 | x -= width; |
6083 | 0 | } |
6084 | 0 | aFontMetrics.DrawString(aString, len, x, y, &aContext, |
6085 | 0 | aContext.GetDrawTarget()); |
6086 | 0 | if (!isRTL) { |
6087 | 0 | x += width; |
6088 | 0 | } |
6089 | 0 | aLength -= len; |
6090 | 0 | aString += len; |
6091 | 0 | } |
6092 | 0 | } |
6093 | | |
6094 | | /* static */ void |
6095 | | nsLayoutUtils::PaintTextShadow(const nsIFrame* aFrame, |
6096 | | gfxContext* aContext, |
6097 | | const nsRect& aTextRect, |
6098 | | const nsRect& aDirtyRect, |
6099 | | const nscolor& aForegroundColor, |
6100 | | TextShadowCallback aCallback, |
6101 | | void* aCallbackData) |
6102 | 0 | { |
6103 | 0 | const nsStyleText* textStyle = aFrame->StyleText(); |
6104 | 0 | if (!textStyle->HasTextShadow()) |
6105 | 0 | return; |
6106 | 0 | |
6107 | 0 | // Text shadow happens with the last value being painted at the back, |
6108 | 0 | // ie. it is painted first. |
6109 | 0 | gfxContext* aDestCtx = aContext; |
6110 | 0 | for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) { |
6111 | 0 | nsCSSShadowItem* shadowDetails = textStyle->mTextShadow->ShadowAt(i - 1); |
6112 | 0 | nsPoint shadowOffset(shadowDetails->mXOffset, |
6113 | 0 | shadowDetails->mYOffset); |
6114 | 0 | nscoord blurRadius = std::max(shadowDetails->mRadius, 0); |
6115 | 0 |
|
6116 | 0 | nsRect shadowRect(aTextRect); |
6117 | 0 | shadowRect.MoveBy(shadowOffset); |
6118 | 0 |
|
6119 | 0 | nsPresContext* presCtx = aFrame->PresContext(); |
6120 | 0 | nsContextBoxBlur contextBoxBlur; |
6121 | 0 |
|
6122 | 0 | nscolor shadowColor = shadowDetails->mColor.CalcColor(aForegroundColor); |
6123 | 0 |
|
6124 | 0 | // Webrender just needs the shadow details |
6125 | 0 | if (auto* textDrawer = aContext->GetTextDrawer()) { |
6126 | 0 | wr::Shadow wrShadow; |
6127 | 0 |
|
6128 | 0 | wrShadow.offset = { |
6129 | 0 | presCtx->AppUnitsToFloatDevPixels(shadowDetails->mXOffset), |
6130 | 0 | presCtx->AppUnitsToFloatDevPixels(shadowDetails->mYOffset) |
6131 | 0 | }; |
6132 | 0 |
|
6133 | 0 | wrShadow.blur_radius = presCtx->AppUnitsToFloatDevPixels(shadowDetails->mRadius); |
6134 | 0 | wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor)); |
6135 | 0 |
|
6136 | 0 | textDrawer->AppendShadow(wrShadow); |
6137 | 0 | continue; |
6138 | 0 | } |
6139 | 0 | |
6140 | 0 | gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius, |
6141 | 0 | presCtx->AppUnitsPerDevPixel(), |
6142 | 0 | aDestCtx, aDirtyRect, nullptr, |
6143 | 0 | nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR); |
6144 | 0 | if (!shadowContext) |
6145 | 0 | continue; |
6146 | 0 | |
6147 | 0 | |
6148 | 0 | |
6149 | 0 | aDestCtx->Save(); |
6150 | 0 | aDestCtx->NewPath(); |
6151 | 0 | aDestCtx->SetColor(Color::FromABGR(shadowColor)); |
6152 | 0 |
|
6153 | 0 | // The callback will draw whatever we want to blur as a shadow. |
6154 | 0 | aCallback(shadowContext, shadowOffset, shadowColor, aCallbackData); |
6155 | 0 |
|
6156 | 0 | contextBoxBlur.DoPaint(); |
6157 | 0 | aDestCtx->Restore(); |
6158 | 0 | } |
6159 | 0 | } |
6160 | | |
6161 | | /* static */ nscoord |
6162 | | nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics, |
6163 | | nscoord aLineHeight, |
6164 | | bool aIsInverted) |
6165 | 0 | { |
6166 | 0 | nscoord fontAscent = aIsInverted ? aFontMetrics->MaxDescent() |
6167 | 0 | : aFontMetrics->MaxAscent(); |
6168 | 0 | nscoord fontHeight = aFontMetrics->MaxHeight(); |
6169 | 0 |
|
6170 | 0 | nscoord leading = aLineHeight - fontHeight; |
6171 | 0 | return fontAscent + leading/2; |
6172 | 0 | } |
6173 | | |
6174 | | |
6175 | | /* static */ bool |
6176 | | nsLayoutUtils::GetFirstLineBaseline(WritingMode aWritingMode, |
6177 | | const nsIFrame* aFrame, nscoord* aResult) |
6178 | 0 | { |
6179 | 0 | LinePosition position; |
6180 | 0 | if (!GetFirstLinePosition(aWritingMode, aFrame, &position)) |
6181 | 0 | return false; |
6182 | 0 | *aResult = position.mBaseline; |
6183 | 0 | return true; |
6184 | 0 | } |
6185 | | |
6186 | | /* static */ bool |
6187 | | nsLayoutUtils::GetFirstLinePosition(WritingMode aWM, |
6188 | | const nsIFrame* aFrame, |
6189 | | LinePosition* aResult) |
6190 | 0 | { |
6191 | 0 | if (aFrame->StyleDisplay()->IsContainSize()) { |
6192 | 0 | return false; |
6193 | 0 | } |
6194 | 0 | const nsBlockFrame* block = nsLayoutUtils::GetAsBlock(const_cast<nsIFrame*>(aFrame)); |
6195 | 0 | if (!block) { |
6196 | 0 | // For the first-line baseline we also have to check for a table, and if |
6197 | 0 | // so, use the baseline of its first row. |
6198 | 0 | LayoutFrameType fType = aFrame->Type(); |
6199 | 0 | if (fType == LayoutFrameType::TableWrapper || |
6200 | 0 | fType == LayoutFrameType::FlexContainer || |
6201 | 0 | fType == LayoutFrameType::GridContainer) { |
6202 | 0 | if ((fType == LayoutFrameType::GridContainer && |
6203 | 0 | aFrame->HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) || |
6204 | 0 | (fType == LayoutFrameType::FlexContainer && |
6205 | 0 | aFrame->HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) || |
6206 | 0 | (fType == LayoutFrameType::TableWrapper && |
6207 | 0 | static_cast<const nsTableWrapperFrame*>(aFrame)->GetRowCount() == 0)) { |
6208 | 0 | // empty grid/flex/table container |
6209 | 0 | aResult->mBStart = 0; |
6210 | 0 | aResult->mBaseline = aFrame->SynthesizeBaselineBOffsetFromBorderBox(aWM, |
6211 | 0 | BaselineSharingGroup::eFirst); |
6212 | 0 | aResult->mBEnd = aFrame->BSize(aWM); |
6213 | 0 | return true; |
6214 | 0 | } |
6215 | 0 | aResult->mBStart = 0; |
6216 | 0 | aResult->mBaseline = aFrame->GetLogicalBaseline(aWM); |
6217 | 0 | // This is what we want for the list bullet caller; not sure if |
6218 | 0 | // other future callers will want the same. |
6219 | 0 | aResult->mBEnd = aFrame->BSize(aWM); |
6220 | 0 | return true; |
6221 | 0 | } |
6222 | 0 | |
6223 | 0 | // For first-line baselines, we have to consider scroll frames. |
6224 | 0 | if (fType == LayoutFrameType::Scroll) { |
6225 | 0 | nsIScrollableFrame *sFrame = do_QueryFrame(const_cast<nsIFrame*>(aFrame)); |
6226 | 0 | if (!sFrame) { |
6227 | 0 | MOZ_ASSERT_UNREACHABLE("not scroll frame"); |
6228 | 0 | } |
6229 | 0 | LinePosition kidPosition; |
6230 | 0 | if (GetFirstLinePosition(aWM, |
6231 | 0 | sFrame->GetScrolledFrame(), &kidPosition)) { |
6232 | 0 | // Consider only the border and padding that contributes to the |
6233 | 0 | // kid's position, not the scrolling, so we get the initial |
6234 | 0 | // position. |
6235 | 0 | *aResult = kidPosition + |
6236 | 0 | aFrame->GetLogicalUsedBorderAndPadding(aWM).BStart(aWM); |
6237 | 0 | return true; |
6238 | 0 | } |
6239 | 0 | return false; |
6240 | 0 | } |
6241 | 0 | |
6242 | 0 | if (fType == LayoutFrameType::FieldSet) { |
6243 | 0 | LinePosition kidPosition; |
6244 | 0 | nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); |
6245 | 0 | // kid might be a legend frame here, but that's ok. |
6246 | 0 | if (GetFirstLinePosition(aWM, kid, &kidPosition)) { |
6247 | 0 | *aResult = kidPosition + |
6248 | 0 | kid->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM); |
6249 | 0 | return true; |
6250 | 0 | } |
6251 | 0 | return false; |
6252 | 0 | } |
6253 | 0 | |
6254 | 0 | // No baseline. |
6255 | 0 | return false; |
6256 | 0 | } |
6257 | 0 | |
6258 | 0 | for (nsBlockFrame::ConstLineIterator line = block->LinesBegin(), |
6259 | 0 | line_end = block->LinesEnd(); |
6260 | 0 | line != line_end; ++line) { |
6261 | 0 | if (line->IsBlock()) { |
6262 | 0 | nsIFrame *kid = line->mFirstChild; |
6263 | 0 | LinePosition kidPosition; |
6264 | 0 | if (GetFirstLinePosition(aWM, kid, &kidPosition)) { |
6265 | 0 | //XXX Not sure if this is the correct value to use for container |
6266 | 0 | // width here. It will only be used in vertical-rl layout, |
6267 | 0 | // which we don't have full support and testing for yet. |
6268 | 0 | const nsSize& containerSize = line->mContainerSize; |
6269 | 0 | *aResult = kidPosition + |
6270 | 0 | kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM); |
6271 | 0 | return true; |
6272 | 0 | } |
6273 | 0 | } else { |
6274 | 0 | // XXX Is this the right test? We have some bogus empty lines |
6275 | 0 | // floating around, but IsEmpty is perhaps too weak. |
6276 | 0 | if (line->BSize() != 0 || !line->IsEmpty()) { |
6277 | 0 | nscoord bStart = line->BStart(); |
6278 | 0 | aResult->mBStart = bStart; |
6279 | 0 | aResult->mBaseline = bStart + line->GetLogicalAscent(); |
6280 | 0 | aResult->mBEnd = bStart + line->BSize(); |
6281 | 0 | return true; |
6282 | 0 | } |
6283 | 0 | } |
6284 | 0 | } |
6285 | 0 | return false; |
6286 | 0 | } |
6287 | | |
6288 | | /* static */ bool |
6289 | | nsLayoutUtils::GetLastLineBaseline(WritingMode aWM, |
6290 | | const nsIFrame* aFrame, nscoord* aResult) |
6291 | 0 | { |
6292 | 0 | if (aFrame->StyleDisplay()->IsContainSize()) { |
6293 | 0 | return false; |
6294 | 0 | } |
6295 | 0 | |
6296 | 0 | const nsBlockFrame* block = nsLayoutUtils::GetAsBlock(const_cast<nsIFrame*>(aFrame)); |
6297 | 0 | if (!block) |
6298 | 0 | // No baseline. (We intentionally don't descend into scroll frames.) |
6299 | 0 | return false; |
6300 | 0 | |
6301 | 0 | for (nsBlockFrame::ConstReverseLineIterator line = block->LinesRBegin(), |
6302 | 0 | line_end = block->LinesREnd(); |
6303 | 0 | line != line_end; ++line) { |
6304 | 0 | if (line->IsBlock()) { |
6305 | 0 | nsIFrame *kid = line->mFirstChild; |
6306 | 0 | nscoord kidBaseline; |
6307 | 0 | const nsSize& containerSize = line->mContainerSize; |
6308 | 0 | if (GetLastLineBaseline(aWM, kid, &kidBaseline)) { |
6309 | 0 | // Ignore relative positioning for baseline calculations |
6310 | 0 | *aResult = kidBaseline + |
6311 | 0 | kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM); |
6312 | 0 | return true; |
6313 | 0 | } else if (kid->IsScrollFrame()) { |
6314 | 0 | // Defer to nsFrame::GetLogicalBaseline (which synthesizes a baseline |
6315 | 0 | // from the margin-box). |
6316 | 0 | kidBaseline = kid->GetLogicalBaseline(aWM); |
6317 | 0 | *aResult = kidBaseline + |
6318 | 0 | kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM); |
6319 | 0 | return true; |
6320 | 0 | } |
6321 | 0 | } else { |
6322 | 0 | // XXX Is this the right test? We have some bogus empty lines |
6323 | 0 | // floating around, but IsEmpty is perhaps too weak. |
6324 | 0 | if (line->BSize() != 0 || !line->IsEmpty()) { |
6325 | 0 | *aResult = line->BStart() + line->GetLogicalAscent(); |
6326 | 0 | return true; |
6327 | 0 | } |
6328 | 0 | } |
6329 | 0 | } |
6330 | 0 | return false; |
6331 | 0 | } |
6332 | | |
6333 | | static nscoord |
6334 | | CalculateBlockContentBEnd(WritingMode aWM, nsBlockFrame* aFrame) |
6335 | 0 | { |
6336 | 0 | MOZ_ASSERT(aFrame, "null ptr"); |
6337 | 0 |
|
6338 | 0 | nscoord contentBEnd = 0; |
6339 | 0 |
|
6340 | 0 | for (nsBlockFrame::LineIterator line = aFrame->LinesBegin(), |
6341 | 0 | line_end = aFrame->LinesEnd(); |
6342 | 0 | line != line_end; ++line) { |
6343 | 0 | if (line->IsBlock()) { |
6344 | 0 | nsIFrame* child = line->mFirstChild; |
6345 | 0 | const nsSize& containerSize = line->mContainerSize; |
6346 | 0 | nscoord offset = |
6347 | 0 | child->GetLogicalNormalPosition(aWM, containerSize).B(aWM); |
6348 | 0 | contentBEnd = |
6349 | 0 | std::max(contentBEnd, |
6350 | 0 | nsLayoutUtils::CalculateContentBEnd(aWM, child) + offset); |
6351 | 0 | } |
6352 | 0 | else { |
6353 | 0 | contentBEnd = std::max(contentBEnd, line->BEnd()); |
6354 | 0 | } |
6355 | 0 | } |
6356 | 0 | return contentBEnd; |
6357 | 0 | } |
6358 | | |
6359 | | /* static */ nscoord |
6360 | | nsLayoutUtils::CalculateContentBEnd(WritingMode aWM, nsIFrame* aFrame) |
6361 | 0 | { |
6362 | 0 | MOZ_ASSERT(aFrame, "null ptr"); |
6363 | 0 |
|
6364 | 0 | nscoord contentBEnd = aFrame->BSize(aWM); |
6365 | 0 |
|
6366 | 0 | // We want scrollable overflow rather than visual because this |
6367 | 0 | // calculation is intended to affect layout. |
6368 | 0 | LogicalSize overflowSize(aWM, aFrame->GetScrollableOverflowRect().Size()); |
6369 | 0 | if (overflowSize.BSize(aWM) > contentBEnd) { |
6370 | 0 | nsIFrame::ChildListIDs skip(nsIFrame::kOverflowList | |
6371 | 0 | nsIFrame::kExcessOverflowContainersList | |
6372 | 0 | nsIFrame::kOverflowOutOfFlowList); |
6373 | 0 | nsBlockFrame* blockFrame = GetAsBlock(aFrame); |
6374 | 0 | if (blockFrame) { |
6375 | 0 | contentBEnd = |
6376 | 0 | std::max(contentBEnd, CalculateBlockContentBEnd(aWM, blockFrame)); |
6377 | 0 | skip |= nsIFrame::kPrincipalList; |
6378 | 0 | } |
6379 | 0 | nsIFrame::ChildListIterator lists(aFrame); |
6380 | 0 | for (; !lists.IsDone(); lists.Next()) { |
6381 | 0 | if (!skip.Contains(lists.CurrentID())) { |
6382 | 0 | nsFrameList::Enumerator childFrames(lists.CurrentList()); |
6383 | 0 | for (; !childFrames.AtEnd(); childFrames.Next()) { |
6384 | 0 | nsIFrame* child = childFrames.get(); |
6385 | 0 | nscoord offset = |
6386 | 0 | child->GetLogicalNormalPosition(aWM, |
6387 | 0 | aFrame->GetSize()).B(aWM); |
6388 | 0 | contentBEnd = std::max(contentBEnd, |
6389 | 0 | CalculateContentBEnd(aWM, child) + offset); |
6390 | 0 | } |
6391 | 0 | } |
6392 | 0 | } |
6393 | 0 | } |
6394 | 0 | return contentBEnd; |
6395 | 0 | } |
6396 | | |
6397 | | /* static */ nsIFrame* |
6398 | | nsLayoutUtils::GetClosestLayer(nsIFrame* aFrame) |
6399 | 0 | { |
6400 | 0 | nsIFrame* layer; |
6401 | 0 | for (layer = aFrame; layer; layer = layer->GetParent()) { |
6402 | 0 | if (layer->IsAbsPosContainingBlock() || |
6403 | 0 | (layer->GetParent() && layer->GetParent()->IsScrollFrame())) |
6404 | 0 | break; |
6405 | 0 | } |
6406 | 0 | if (layer) |
6407 | 0 | return layer; |
6408 | 0 | return aFrame->PresShell()->GetRootFrame(); |
6409 | 0 | } |
6410 | | |
6411 | | SamplingFilter |
6412 | | nsLayoutUtils::GetSamplingFilterForFrame(nsIFrame* aForFrame) |
6413 | 0 | { |
6414 | 0 | SamplingFilter defaultFilter = SamplingFilter::GOOD; |
6415 | 0 | ComputedStyle *sc; |
6416 | 0 | if (nsCSSRendering::IsCanvasFrame(aForFrame)) { |
6417 | 0 | nsCSSRendering::FindBackground(aForFrame, &sc); |
6418 | 0 | } else { |
6419 | 0 | sc = aForFrame->Style(); |
6420 | 0 | } |
6421 | 0 |
|
6422 | 0 | switch (sc->StyleVisibility()->mImageRendering) { |
6423 | 0 | case NS_STYLE_IMAGE_RENDERING_OPTIMIZESPEED: |
6424 | 0 | return SamplingFilter::POINT; |
6425 | 0 | case NS_STYLE_IMAGE_RENDERING_OPTIMIZEQUALITY: |
6426 | 0 | return SamplingFilter::LINEAR; |
6427 | 0 | case NS_STYLE_IMAGE_RENDERING_CRISPEDGES: |
6428 | 0 | return SamplingFilter::POINT; |
6429 | 0 | default: |
6430 | 0 | return defaultFilter; |
6431 | 0 | } |
6432 | 0 | } |
6433 | | |
6434 | | /** |
6435 | | * Given an image being drawn into an appunit coordinate system, and |
6436 | | * a point in that coordinate system, map the point back into image |
6437 | | * pixel space. |
6438 | | * @param aSize the size of the image, in pixels |
6439 | | * @param aDest the rectangle that the image is being mapped into |
6440 | | * @param aPt a point in the same coordinate system as the rectangle |
6441 | | */ |
6442 | | static gfxPoint |
6443 | | MapToFloatImagePixels(const gfxSize& aSize, |
6444 | | const gfxRect& aDest, const gfxPoint& aPt) |
6445 | 0 | { |
6446 | 0 | return gfxPoint(((aPt.x - aDest.X())*aSize.width)/aDest.Width(), |
6447 | 0 | ((aPt.y - aDest.Y())*aSize.height)/aDest.Height()); |
6448 | 0 | } |
6449 | | |
6450 | | /** |
6451 | | * Given an image being drawn into an pixel-based coordinate system, and |
6452 | | * a point in image space, map the point into the pixel-based coordinate |
6453 | | * system. |
6454 | | * @param aSize the size of the image, in pixels |
6455 | | * @param aDest the rectangle that the image is being mapped into |
6456 | | * @param aPt a point in image space |
6457 | | */ |
6458 | | static gfxPoint |
6459 | | MapToFloatUserPixels(const gfxSize& aSize, |
6460 | | const gfxRect& aDest, const gfxPoint& aPt) |
6461 | 0 | { |
6462 | 0 | return gfxPoint(aPt.x*aDest.Width()/aSize.width + aDest.X(), |
6463 | 0 | aPt.y*aDest.Height()/aSize.height + aDest.Y()); |
6464 | 0 | } |
6465 | | |
6466 | | /* static */ gfxRect |
6467 | | nsLayoutUtils::RectToGfxRect(const nsRect& aRect, int32_t aAppUnitsPerDevPixel) |
6468 | 0 | { |
6469 | 0 | return gfxRect(gfxFloat(aRect.x) / aAppUnitsPerDevPixel, |
6470 | 0 | gfxFloat(aRect.y) / aAppUnitsPerDevPixel, |
6471 | 0 | gfxFloat(aRect.width) / aAppUnitsPerDevPixel, |
6472 | 0 | gfxFloat(aRect.height) / aAppUnitsPerDevPixel); |
6473 | 0 | } |
6474 | | |
6475 | | struct SnappedImageDrawingParameters { |
6476 | | // A transform from image space to device space. |
6477 | | gfxMatrix imageSpaceToDeviceSpace; |
6478 | | // The size at which the image should be drawn (which may not be its |
6479 | | // intrinsic size due to, for example, HQ scaling). |
6480 | | nsIntSize size; |
6481 | | // The region in tiled image space which will be drawn, with an associated |
6482 | | // region to which sampling should be restricted. |
6483 | | ImageRegion region; |
6484 | | // The default viewport size for SVG images, which we use unless a different |
6485 | | // one has been explicitly specified. This is the same as |size| except that |
6486 | | // it does not take into account any transformation on the gfxContext we're |
6487 | | // drawing to - for example, CSS transforms are not taken into account. |
6488 | | CSSIntSize svgViewportSize; |
6489 | | // Whether there's anything to draw at all. |
6490 | | bool shouldDraw; |
6491 | | |
6492 | | SnappedImageDrawingParameters() |
6493 | | : region(ImageRegion::Empty()) |
6494 | | , shouldDraw(false) |
6495 | 0 | {} |
6496 | | |
6497 | | SnappedImageDrawingParameters(const gfxMatrix& aImageSpaceToDeviceSpace, |
6498 | | const nsIntSize& aSize, |
6499 | | const ImageRegion& aRegion, |
6500 | | const CSSIntSize& aSVGViewportSize) |
6501 | | : imageSpaceToDeviceSpace(aImageSpaceToDeviceSpace) |
6502 | | , size(aSize) |
6503 | | , region(aRegion) |
6504 | | , svgViewportSize(aSVGViewportSize) |
6505 | | , shouldDraw(true) |
6506 | 0 | {} |
6507 | | }; |
6508 | | |
6509 | | /** |
6510 | | * Given two axis-aligned rectangles, returns the transformation that maps the |
6511 | | * first onto the second. |
6512 | | * |
6513 | | * @param aFrom The rect to be transformed. |
6514 | | * @param aTo The rect that aFrom should be mapped onto by the transformation. |
6515 | | */ |
6516 | | static gfxMatrix |
6517 | | TransformBetweenRects(const gfxRect& aFrom, const gfxRect& aTo) |
6518 | 0 | { |
6519 | 0 | gfxSize scale(aTo.width / aFrom.width, |
6520 | 0 | aTo.height / aFrom.height); |
6521 | 0 | gfxPoint translation(aTo.x - aFrom.x * scale.width, |
6522 | 0 | aTo.y - aFrom.y * scale.height); |
6523 | 0 | return gfxMatrix(scale.width, 0, 0, scale.height, |
6524 | 0 | translation.x, translation.y); |
6525 | 0 | } |
6526 | | |
6527 | | static nsRect |
6528 | | TileNearRect(const nsRect& aAnyTile, const nsRect& aTargetRect) |
6529 | 0 | { |
6530 | 0 | nsPoint distance = aTargetRect.TopLeft() - aAnyTile.TopLeft(); |
6531 | 0 | return aAnyTile + nsPoint(distance.x / aAnyTile.width * aAnyTile.width, |
6532 | 0 | distance.y / aAnyTile.height * aAnyTile.height); |
6533 | 0 | } |
6534 | | |
6535 | | static gfxFloat |
6536 | | StableRound(gfxFloat aValue) |
6537 | 0 | { |
6538 | 0 | // Values slightly less than 0.5 should round up like 0.5 would; we're |
6539 | 0 | // assuming they were meant to be 0.5. |
6540 | 0 | return floor(aValue + 0.5001); |
6541 | 0 | } |
6542 | | |
6543 | | static gfxPoint |
6544 | | StableRound(const gfxPoint& aPoint) |
6545 | 0 | { |
6546 | 0 | return gfxPoint(StableRound(aPoint.x), StableRound(aPoint.y)); |
6547 | 0 | } |
6548 | | |
6549 | | /** |
6550 | | * Given a set of input parameters, compute certain output parameters |
6551 | | * for drawing an image with the image snapping algorithm. |
6552 | | * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering |
6553 | | * |
6554 | | * @see nsLayoutUtils::DrawImage() for the descriptions of input parameters |
6555 | | */ |
6556 | | static SnappedImageDrawingParameters |
6557 | | ComputeSnappedImageDrawingParameters(gfxContext* aCtx, |
6558 | | int32_t aAppUnitsPerDevPixel, |
6559 | | const nsRect aDest, |
6560 | | const nsRect aFill, |
6561 | | const nsPoint aAnchor, |
6562 | | const nsRect aDirty, |
6563 | | imgIContainer* aImage, |
6564 | | const SamplingFilter aSamplingFilter, |
6565 | | uint32_t aImageFlags, |
6566 | | ExtendMode aExtendMode) |
6567 | 0 | { |
6568 | 0 | if (aDest.IsEmpty() || aFill.IsEmpty()) |
6569 | 0 | return SnappedImageDrawingParameters(); |
6570 | 0 | |
6571 | 0 | // Avoid unnecessarily large offsets. |
6572 | 0 | bool doTile = !aDest.Contains(aFill); |
6573 | 0 | nsRect appUnitDest = doTile ? TileNearRect(aDest, aFill.Intersect(aDirty)) |
6574 | 0 | : aDest; |
6575 | 0 | nsPoint anchor = aAnchor + (appUnitDest.TopLeft() - aDest.TopLeft()); |
6576 | 0 |
|
6577 | 0 | gfxRect devPixelDest = |
6578 | 0 | nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel); |
6579 | 0 | gfxRect devPixelFill = |
6580 | 0 | nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel); |
6581 | 0 | gfxRect devPixelDirty = |
6582 | 0 | nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel); |
6583 | 0 |
|
6584 | 0 | gfxMatrix currentMatrix = aCtx->CurrentMatrixDouble(); |
6585 | 0 | gfxRect fill = devPixelFill; |
6586 | 0 | gfxRect dest = devPixelDest; |
6587 | 0 | bool didSnap; |
6588 | 0 | // Snap even if we have a scale in the context. But don't snap if |
6589 | 0 | // we have something that's not translation+scale, or if the scale flips in |
6590 | 0 | // the X or Y direction, because snapped image drawing can't handle that yet. |
6591 | 0 | // Any changes to this algorithm will need to be reflected in |
6592 | 0 | // ComputeImageContainerDrawingParameters. |
6593 | 0 | if (!currentMatrix.HasNonAxisAlignedTransform() && |
6594 | 0 | currentMatrix._11 > 0.0 && currentMatrix._22 > 0.0 && |
6595 | 0 | aCtx->UserToDevicePixelSnapped(fill, true) && |
6596 | 0 | aCtx->UserToDevicePixelSnapped(dest, true)) { |
6597 | 0 | // We snapped. On this code path, |fill| and |dest| take into account |
6598 | 0 | // currentMatrix's transform. |
6599 | 0 | didSnap = true; |
6600 | 0 | } else { |
6601 | 0 | // We didn't snap. On this code path, |fill| and |dest| do not take into |
6602 | 0 | // account currentMatrix's transform. |
6603 | 0 | didSnap = false; |
6604 | 0 | fill = devPixelFill; |
6605 | 0 | dest = devPixelDest; |
6606 | 0 | } |
6607 | 0 |
|
6608 | 0 | // If we snapped above, |dest| already takes into account |currentMatrix|'s scale |
6609 | 0 | // and has integer coordinates. If not, we need these properties to compute |
6610 | 0 | // the optimal drawn image size, so compute |snappedDestSize| here. |
6611 | 0 | gfxSize snappedDestSize = dest.Size(); |
6612 | 0 | gfxSize scaleFactors = currentMatrix.ScaleFactors(true); |
6613 | 0 | if (!didSnap) { |
6614 | 0 | snappedDestSize.Scale(scaleFactors.width, scaleFactors.height); |
6615 | 0 | snappedDestSize.width = NS_round(snappedDestSize.width); |
6616 | 0 | snappedDestSize.height = NS_round(snappedDestSize.height); |
6617 | 0 | } |
6618 | 0 |
|
6619 | 0 | // We need to be sure that this is at least one pixel in width and height, |
6620 | 0 | // or we'll end up drawing nothing even if we have a nonempty fill. |
6621 | 0 | snappedDestSize.width = std::max(snappedDestSize.width, 1.0); |
6622 | 0 | snappedDestSize.height = std::max(snappedDestSize.height, 1.0); |
6623 | 0 |
|
6624 | 0 | // Bail if we're not going to end up drawing anything. |
6625 | 0 | if (fill.IsEmpty()) { |
6626 | 0 | return SnappedImageDrawingParameters(); |
6627 | 0 | } |
6628 | 0 | |
6629 | 0 | nsIntSize intImageSize = |
6630 | 0 | aImage->OptimalImageSizeForDest(snappedDestSize, |
6631 | 0 | imgIContainer::FRAME_CURRENT, |
6632 | 0 | aSamplingFilter, aImageFlags); |
6633 | 0 |
|
6634 | 0 | nsIntSize svgViewportSize; |
6635 | 0 | if (scaleFactors.width == 1.0 && scaleFactors.height == 1.0) { |
6636 | 0 | // intImageSize is scaled by currentMatrix. But since there are no scale |
6637 | 0 | // factors in currentMatrix, it is safe to assign intImageSize to |
6638 | 0 | // svgViewportSize directly. |
6639 | 0 | svgViewportSize = intImageSize; |
6640 | 0 | } else { |
6641 | 0 | // We should not take into account any transformation of currentMatrix |
6642 | 0 | // when computing svg viewport size. Since currentMatrix contains scale |
6643 | 0 | // factors, we need to recompute SVG viewport by unscaled devPixelDest. |
6644 | 0 | svgViewportSize = aImage->OptimalImageSizeForDest(devPixelDest.Size(), |
6645 | 0 | imgIContainer::FRAME_CURRENT, |
6646 | 0 | aSamplingFilter, |
6647 | 0 | aImageFlags); |
6648 | 0 | } |
6649 | 0 |
|
6650 | 0 | gfxSize imageSize(intImageSize.width, intImageSize.height); |
6651 | 0 |
|
6652 | 0 | // Compute the set of pixels that would be sampled by an ideal rendering |
6653 | 0 | gfxPoint subimageTopLeft = |
6654 | 0 | MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.TopLeft()); |
6655 | 0 | gfxPoint subimageBottomRight = |
6656 | 0 | MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.BottomRight()); |
6657 | 0 | gfxRect subimage; |
6658 | 0 | subimage.MoveTo(NSToIntFloor(subimageTopLeft.x), |
6659 | 0 | NSToIntFloor(subimageTopLeft.y)); |
6660 | 0 | subimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - subimage.x, |
6661 | 0 | NSToIntCeil(subimageBottomRight.y) - subimage.y); |
6662 | 0 |
|
6663 | 0 | if (subimage.IsEmpty()) { |
6664 | 0 | // Bail if the subimage is empty (we're not going to be drawing anything). |
6665 | 0 | return SnappedImageDrawingParameters(); |
6666 | 0 | } |
6667 | 0 | |
6668 | 0 | gfxMatrix transform; |
6669 | 0 | gfxMatrix invTransform; |
6670 | 0 |
|
6671 | 0 | bool anchorAtUpperLeft = anchor.x == appUnitDest.x && |
6672 | 0 | anchor.y == appUnitDest.y; |
6673 | 0 | bool exactlyOneImageCopy = aFill.IsEqualEdges(appUnitDest); |
6674 | 0 | if (anchorAtUpperLeft && exactlyOneImageCopy) { |
6675 | 0 | // The simple case: we can ignore the anchor point and compute the |
6676 | 0 | // transformation from the sampled region (the subimage) to the fill rect. |
6677 | 0 | // This approach is preferable when it works since it tends to produce |
6678 | 0 | // less numerical error. |
6679 | 0 | transform = TransformBetweenRects(subimage, fill); |
6680 | 0 | invTransform = TransformBetweenRects(fill, subimage); |
6681 | 0 | } else { |
6682 | 0 | // The more complicated case: we compute the transformation from the |
6683 | 0 | // image rect positioned at the image space anchor point to the dest rect |
6684 | 0 | // positioned at the device space anchor point. |
6685 | 0 |
|
6686 | 0 | // Compute the anchor point in both device space and image space. This |
6687 | 0 | // code assumes that pixel-based devices have one pixel per device unit! |
6688 | 0 | gfxPoint anchorPoint(gfxFloat(anchor.x)/aAppUnitsPerDevPixel, |
6689 | 0 | gfxFloat(anchor.y)/aAppUnitsPerDevPixel); |
6690 | 0 | gfxPoint imageSpaceAnchorPoint = |
6691 | 0 | MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint); |
6692 | 0 |
|
6693 | 0 | if (didSnap) { |
6694 | 0 | imageSpaceAnchorPoint = StableRound(imageSpaceAnchorPoint); |
6695 | 0 | anchorPoint = imageSpaceAnchorPoint; |
6696 | 0 | anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint); |
6697 | 0 | anchorPoint = currentMatrix.TransformPoint(anchorPoint); |
6698 | 0 | anchorPoint = StableRound(anchorPoint); |
6699 | 0 | } |
6700 | 0 |
|
6701 | 0 | // Compute an unsnapped version of the dest rect's size. We continue to |
6702 | 0 | // follow the pattern that we take |currentMatrix| into account only if |
6703 | 0 | // |didSnap| is true. |
6704 | 0 | gfxSize unsnappedDestSize |
6705 | 0 | = didSnap ? devPixelDest.Size() * currentMatrix.ScaleFactors(true) |
6706 | 0 | : devPixelDest.Size(); |
6707 | 0 |
|
6708 | 0 | gfxRect anchoredDestRect(anchorPoint, unsnappedDestSize); |
6709 | 0 | gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize); |
6710 | 0 |
|
6711 | 0 | // Calculate anchoredDestRect with snapped fill rect when the devPixelFill rect |
6712 | 0 | // corresponds to just a single tile in that direction |
6713 | 0 | if (fill.Width() != devPixelFill.Width() && |
6714 | 0 | devPixelDest.x == devPixelFill.x && |
6715 | 0 | devPixelDest.XMost() == devPixelFill.XMost()) { |
6716 | 0 | anchoredDestRect.width = fill.width; |
6717 | 0 | } |
6718 | 0 | if (fill.Height() != devPixelFill.Height() && |
6719 | 0 | devPixelDest.y == devPixelFill.y && |
6720 | 0 | devPixelDest.YMost() == devPixelFill.YMost()) { |
6721 | 0 | anchoredDestRect.height = fill.height; |
6722 | 0 | } |
6723 | 0 |
|
6724 | 0 | transform = TransformBetweenRects(anchoredImageRect, anchoredDestRect); |
6725 | 0 | invTransform = TransformBetweenRects(anchoredDestRect, anchoredImageRect); |
6726 | 0 | } |
6727 | 0 |
|
6728 | 0 | // If the transform is not a straight translation by integers, then |
6729 | 0 | // filtering will occur, and restricting the fill rect to the dirty rect |
6730 | 0 | // would change the values computed for edge pixels, which we can't allow. |
6731 | 0 | // Also, if 'didSnap' is false then rounding out 'devPixelDirty' might not |
6732 | 0 | // produce pixel-aligned coordinates, which would also break the values |
6733 | 0 | // computed for edge pixels. |
6734 | 0 | if (didSnap && !invTransform.HasNonIntegerTranslation()) { |
6735 | 0 | // This form of Transform is safe to call since non-axis-aligned |
6736 | 0 | // transforms wouldn't be snapped. |
6737 | 0 | devPixelDirty = currentMatrix.TransformRect(devPixelDirty); |
6738 | 0 | devPixelDirty.RoundOut(); |
6739 | 0 | fill = fill.Intersect(devPixelDirty); |
6740 | 0 | } |
6741 | 0 | if (fill.IsEmpty()) |
6742 | 0 | return SnappedImageDrawingParameters(); |
6743 | 0 | |
6744 | 0 | gfxRect imageSpaceFill(didSnap ? invTransform.TransformRect(fill) |
6745 | 0 | : invTransform.TransformBounds(fill)); |
6746 | 0 |
|
6747 | 0 | // If we didn't snap, we need to post-multiply the matrix on the context to |
6748 | 0 | // get the final matrix we'll draw with, because we didn't take it into |
6749 | 0 | // account when computing the matrices above. |
6750 | 0 | if (!didSnap) { |
6751 | 0 | transform = transform * currentMatrix; |
6752 | 0 | } |
6753 | 0 |
|
6754 | 0 | ExtendMode extendMode = (aImageFlags & imgIContainer::FLAG_CLAMP) |
6755 | 0 | ? ExtendMode::CLAMP |
6756 | 0 | : aExtendMode; |
6757 | 0 | // We were passed in the default extend mode but need to tile. |
6758 | 0 | if (extendMode == ExtendMode::CLAMP && doTile) { |
6759 | 0 | MOZ_ASSERT(!(aImageFlags & imgIContainer::FLAG_CLAMP)); |
6760 | 0 | extendMode = ExtendMode::REPEAT; |
6761 | 0 | } |
6762 | 0 |
|
6763 | 0 | ImageRegion region = |
6764 | 0 | ImageRegion::CreateWithSamplingRestriction(imageSpaceFill, subimage, extendMode); |
6765 | 0 |
|
6766 | 0 | return SnappedImageDrawingParameters(transform, intImageSize, |
6767 | 0 | region, |
6768 | 0 | CSSIntSize(svgViewportSize.width, |
6769 | 0 | svgViewportSize.height)); |
6770 | 0 | } |
6771 | | |
6772 | | static ImgDrawResult |
6773 | | DrawImageInternal(gfxContext& aContext, |
6774 | | nsPresContext* aPresContext, |
6775 | | imgIContainer* aImage, |
6776 | | const SamplingFilter aSamplingFilter, |
6777 | | const nsRect& aDest, |
6778 | | const nsRect& aFill, |
6779 | | const nsPoint& aAnchor, |
6780 | | const nsRect& aDirty, |
6781 | | const Maybe<SVGImageContext>& aSVGContext, |
6782 | | uint32_t aImageFlags, |
6783 | | ExtendMode aExtendMode = ExtendMode::CLAMP, |
6784 | | float aOpacity = 1.0) |
6785 | 0 | { |
6786 | 0 | ImgDrawResult result = ImgDrawResult::SUCCESS; |
6787 | 0 |
|
6788 | 0 | aImageFlags |= imgIContainer::FLAG_ASYNC_NOTIFY; |
6789 | 0 |
|
6790 | 0 | if (aPresContext->Type() == nsPresContext::eContext_Print) { |
6791 | 0 | // We want vector images to be passed on as vector commands, not a raster |
6792 | 0 | // image. |
6793 | 0 | aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE; |
6794 | 0 | } |
6795 | 0 | if (aDest.Contains(aFill)) { |
6796 | 0 | aImageFlags |= imgIContainer::FLAG_CLAMP; |
6797 | 0 | } |
6798 | 0 | int32_t appUnitsPerDevPixel = |
6799 | 0 | aPresContext->AppUnitsPerDevPixel(); |
6800 | 0 |
|
6801 | 0 | SnappedImageDrawingParameters params = |
6802 | 0 | ComputeSnappedImageDrawingParameters(&aContext, appUnitsPerDevPixel, aDest, |
6803 | 0 | aFill, aAnchor, aDirty, aImage, |
6804 | 0 | aSamplingFilter, aImageFlags, aExtendMode); |
6805 | 0 |
|
6806 | 0 | if (!params.shouldDraw) { |
6807 | 0 | return result; |
6808 | 0 | } |
6809 | 0 | |
6810 | 0 | { |
6811 | 0 | gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext); |
6812 | 0 |
|
6813 | 0 | aContext.SetMatrixDouble(params.imageSpaceToDeviceSpace); |
6814 | 0 |
|
6815 | 0 | Maybe<SVGImageContext> fallbackContext; |
6816 | 0 | if (!aSVGContext) { |
6817 | 0 | // Use the default viewport. |
6818 | 0 | fallbackContext.emplace(Some(params.svgViewportSize)); |
6819 | 0 | } |
6820 | 0 |
|
6821 | 0 | result = aImage->Draw(&aContext, params.size, params.region, |
6822 | 0 | imgIContainer::FRAME_CURRENT, aSamplingFilter, |
6823 | 0 | aSVGContext ? aSVGContext : fallbackContext, |
6824 | 0 | aImageFlags, aOpacity); |
6825 | 0 |
|
6826 | 0 | } |
6827 | 0 |
|
6828 | 0 | return result; |
6829 | 0 | } |
6830 | | |
6831 | | /* static */ ImgDrawResult |
6832 | | nsLayoutUtils::DrawSingleUnscaledImage(gfxContext& aContext, |
6833 | | nsPresContext* aPresContext, |
6834 | | imgIContainer* aImage, |
6835 | | const SamplingFilter aSamplingFilter, |
6836 | | const nsPoint& aDest, |
6837 | | const nsRect* aDirty, |
6838 | | const Maybe<SVGImageContext>& aSVGContext, |
6839 | | uint32_t aImageFlags, |
6840 | | const nsRect* aSourceArea) |
6841 | 0 | { |
6842 | 0 | CSSIntSize imageSize; |
6843 | 0 | aImage->GetWidth(&imageSize.width); |
6844 | 0 | aImage->GetHeight(&imageSize.height); |
6845 | 0 | if (imageSize.width < 1 || imageSize.height < 1) { |
6846 | 0 | NS_WARNING("Image width or height is non-positive"); |
6847 | 0 | return ImgDrawResult::TEMPORARY_ERROR; |
6848 | 0 | } |
6849 | 0 |
|
6850 | 0 | nsSize size(CSSPixel::ToAppUnits(imageSize)); |
6851 | 0 | nsRect source; |
6852 | 0 | if (aSourceArea) { |
6853 | 0 | source = *aSourceArea; |
6854 | 0 | } else { |
6855 | 0 | source.SizeTo(size); |
6856 | 0 | } |
6857 | 0 |
|
6858 | 0 | nsRect dest(aDest - source.TopLeft(), size); |
6859 | 0 | nsRect fill(aDest, source.Size()); |
6860 | 0 | // Ensure that only a single image tile is drawn. If aSourceArea extends |
6861 | 0 | // outside the image bounds, we want to honor the aSourceArea-to-aDest |
6862 | 0 | // translation but we don't want to actually tile the image. |
6863 | 0 | fill.IntersectRect(fill, dest); |
6864 | 0 | return DrawImageInternal(aContext, aPresContext, |
6865 | 0 | aImage, aSamplingFilter, |
6866 | 0 | dest, fill, aDest, aDirty ? *aDirty : dest, |
6867 | 0 | aSVGContext, aImageFlags); |
6868 | 0 | } |
6869 | | |
6870 | | /* static */ ImgDrawResult |
6871 | | nsLayoutUtils::DrawSingleImage(gfxContext& aContext, |
6872 | | nsPresContext* aPresContext, |
6873 | | imgIContainer* aImage, |
6874 | | const SamplingFilter aSamplingFilter, |
6875 | | const nsRect& aDest, |
6876 | | const nsRect& aDirty, |
6877 | | const Maybe<SVGImageContext>& aSVGContext, |
6878 | | uint32_t aImageFlags, |
6879 | | const nsPoint* aAnchorPoint, |
6880 | | const nsRect* aSourceArea) |
6881 | 0 | { |
6882 | 0 | nscoord appUnitsPerCSSPixel = AppUnitsPerCSSPixel(); |
6883 | 0 | CSSIntSize pixelImageSize(ComputeSizeForDrawingWithFallback(aImage, aDest.Size())); |
6884 | 0 | if (pixelImageSize.width < 1 || pixelImageSize.height < 1) { |
6885 | 0 | NS_ASSERTION(pixelImageSize.width >= 0 && pixelImageSize.height >= 0, |
6886 | 0 | "Image width or height is negative"); |
6887 | 0 | return ImgDrawResult::SUCCESS; // no point in drawing a zero size image |
6888 | 0 | } |
6889 | 0 |
|
6890 | 0 | nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize)); |
6891 | 0 | nsRect source; |
6892 | 0 | nsCOMPtr<imgIContainer> image; |
6893 | 0 | if (aSourceArea) { |
6894 | 0 | source = *aSourceArea; |
6895 | 0 | nsIntRect subRect(source.x, source.y, source.width, source.height); |
6896 | 0 | subRect.ScaleInverseRoundOut(appUnitsPerCSSPixel); |
6897 | 0 | image = ImageOps::Clip(aImage, subRect); |
6898 | 0 |
|
6899 | 0 | nsRect imageRect; |
6900 | 0 | imageRect.SizeTo(imageSize); |
6901 | 0 | nsRect clippedSource = imageRect.Intersect(source); |
6902 | 0 |
|
6903 | 0 | source -= clippedSource.TopLeft(); |
6904 | 0 | imageSize = clippedSource.Size(); |
6905 | 0 | } else { |
6906 | 0 | source.SizeTo(imageSize); |
6907 | 0 | image = aImage; |
6908 | 0 | } |
6909 | 0 |
|
6910 | 0 | nsRect dest = GetWholeImageDestination(imageSize, source, aDest); |
6911 | 0 |
|
6912 | 0 | // Ensure that only a single image tile is drawn. If aSourceArea extends |
6913 | 0 | // outside the image bounds, we want to honor the aSourceArea-to-aDest |
6914 | 0 | // transform but we don't want to actually tile the image. |
6915 | 0 | nsRect fill; |
6916 | 0 | fill.IntersectRect(aDest, dest); |
6917 | 0 | return DrawImageInternal(aContext, aPresContext, image, |
6918 | 0 | aSamplingFilter, dest, fill, |
6919 | 0 | aAnchorPoint ? *aAnchorPoint : fill.TopLeft(), |
6920 | 0 | aDirty, aSVGContext, aImageFlags); |
6921 | 0 | } |
6922 | | |
6923 | | /* static */ void |
6924 | | nsLayoutUtils::ComputeSizeForDrawing(imgIContainer *aImage, |
6925 | | CSSIntSize& aImageSize, /*outparam*/ |
6926 | | nsSize& aIntrinsicRatio, /*outparam*/ |
6927 | | bool& aGotWidth, /*outparam*/ |
6928 | | bool& aGotHeight /*outparam*/) |
6929 | 0 | { |
6930 | 0 | aGotWidth = NS_SUCCEEDED(aImage->GetWidth(&aImageSize.width)); |
6931 | 0 | aGotHeight = NS_SUCCEEDED(aImage->GetHeight(&aImageSize.height)); |
6932 | 0 | bool gotRatio = NS_SUCCEEDED(aImage->GetIntrinsicRatio(&aIntrinsicRatio)); |
6933 | 0 |
|
6934 | 0 | if (!(aGotWidth && aGotHeight) && !gotRatio) { |
6935 | 0 | // We hit an error (say, because the image failed to load or couldn't be |
6936 | 0 | // decoded) and should return zero size. |
6937 | 0 | aGotWidth = aGotHeight = true; |
6938 | 0 | aImageSize = CSSIntSize(0, 0); |
6939 | 0 | aIntrinsicRatio = nsSize(0, 0); |
6940 | 0 | } |
6941 | 0 | } |
6942 | | |
6943 | | /* static */ CSSIntSize |
6944 | | nsLayoutUtils::ComputeSizeForDrawingWithFallback(imgIContainer* aImage, |
6945 | | const nsSize& aFallbackSize) |
6946 | 0 | { |
6947 | 0 | CSSIntSize imageSize; |
6948 | 0 | nsSize imageRatio; |
6949 | 0 | bool gotHeight, gotWidth; |
6950 | 0 | ComputeSizeForDrawing(aImage, imageSize, imageRatio, gotWidth, gotHeight); |
6951 | 0 |
|
6952 | 0 | // If we didn't get both width and height, try to compute them using the |
6953 | 0 | // intrinsic ratio of the image. |
6954 | 0 | if (gotWidth != gotHeight) { |
6955 | 0 | if (!gotWidth) { |
6956 | 0 | if (imageRatio.height != 0) { |
6957 | 0 | imageSize.width = |
6958 | 0 | NSCoordSaturatingNonnegativeMultiply(imageSize.height, |
6959 | 0 | float(imageRatio.width) / |
6960 | 0 | float(imageRatio.height)); |
6961 | 0 | gotWidth = true; |
6962 | 0 | } |
6963 | 0 | } else { |
6964 | 0 | if (imageRatio.width != 0) { |
6965 | 0 | imageSize.height = |
6966 | 0 | NSCoordSaturatingNonnegativeMultiply(imageSize.width, |
6967 | 0 | float(imageRatio.height) / |
6968 | 0 | float(imageRatio.width)); |
6969 | 0 | gotHeight = true; |
6970 | 0 | } |
6971 | 0 | } |
6972 | 0 | } |
6973 | 0 |
|
6974 | 0 | // If we still don't have a width or height, just use the fallback size the |
6975 | 0 | // caller provided. |
6976 | 0 | if (!gotWidth) { |
6977 | 0 | imageSize.width = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.width); |
6978 | 0 | } |
6979 | 0 | if (!gotHeight) { |
6980 | 0 | imageSize.height = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.height); |
6981 | 0 | } |
6982 | 0 |
|
6983 | 0 | return imageSize; |
6984 | 0 | } |
6985 | | |
6986 | | /* static */ IntSize |
6987 | | nsLayoutUtils::ComputeImageContainerDrawingParameters(imgIContainer* aImage, |
6988 | | nsIFrame* aForFrame, |
6989 | | const LayoutDeviceRect& aDestRect, |
6990 | | const StackingContextHelper& aSc, |
6991 | | uint32_t aFlags, |
6992 | | Maybe<SVGImageContext>& aSVGContext) |
6993 | 0 | { |
6994 | 0 | MOZ_ASSERT(aImage); |
6995 | 0 | MOZ_ASSERT(aForFrame); |
6996 | 0 |
|
6997 | 0 | gfx::Size scaleFactors = aSc.GetInheritedScale(); |
6998 | 0 | SamplingFilter samplingFilter = |
6999 | 0 | nsLayoutUtils::GetSamplingFilterForFrame(aForFrame); |
7000 | 0 |
|
7001 | 0 | // Compute our SVG context parameters, if any. Don't replace the viewport |
7002 | 0 | // size if it was already set, prefer what the caller gave. |
7003 | 0 | SVGImageContext::MaybeStoreContextPaint(aSVGContext, aForFrame, aImage); |
7004 | 0 | if ((scaleFactors.width != 1.0 || scaleFactors.height != 1.0) && |
7005 | 0 | aImage->GetType() == imgIContainer::TYPE_VECTOR && |
7006 | 0 | (!aSVGContext || !aSVGContext->GetViewportSize())) { |
7007 | 0 | gfxSize gfxDestSize(aDestRect.Width(), aDestRect.Height()); |
7008 | 0 | IntSize viewportSize = |
7009 | 0 | aImage->OptimalImageSizeForDest(gfxDestSize, |
7010 | 0 | imgIContainer::FRAME_CURRENT, |
7011 | 0 | samplingFilter, aFlags); |
7012 | 0 |
|
7013 | 0 | CSSIntSize cssViewportSize(viewportSize.width, viewportSize.height); |
7014 | 0 | if (!aSVGContext) { |
7015 | 0 | aSVGContext.emplace(Some(cssViewportSize)); |
7016 | 0 | } else { |
7017 | 0 | aSVGContext->SetViewportSize(Some(cssViewportSize)); |
7018 | 0 | } |
7019 | 0 | } |
7020 | 0 |
|
7021 | 0 | // Attempt to snap pixels, the same as ComputeSnappedImageDrawingParameters. |
7022 | 0 | // Any changes to the algorithm here will need to be reflected there. |
7023 | 0 | bool snapped = false; |
7024 | 0 | gfxSize gfxLayerSize; |
7025 | 0 | const gfx::Matrix& itm = aSc.GetInheritedTransform(); |
7026 | 0 | if (!itm.HasNonAxisAlignedTransform() && |
7027 | 0 | itm._11 > 0.0 && |
7028 | 0 | itm._22 > 0.0) { |
7029 | 0 | gfxRect rect(gfxPoint(aDestRect.X(), aDestRect.Y()), |
7030 | 0 | gfxSize(aDestRect.Width(), aDestRect.Height())); |
7031 | 0 |
|
7032 | 0 | gfxPoint p1 = ThebesPoint(itm.TransformPoint(ToPoint(rect.TopLeft()))); |
7033 | 0 | gfxPoint p2 = ThebesPoint(itm.TransformPoint(ToPoint(rect.TopRight()))); |
7034 | 0 | gfxPoint p3 = ThebesPoint(itm.TransformPoint(ToPoint(rect.BottomRight()))); |
7035 | 0 |
|
7036 | 0 | if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) { |
7037 | 0 | p1.Round(); |
7038 | 0 | p3.Round(); |
7039 | 0 |
|
7040 | 0 | rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); |
7041 | 0 | rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), |
7042 | 0 | std::max(p1.y, p3.y) - rect.Y())); |
7043 | 0 |
|
7044 | 0 | // An empty size is unacceptable so we ensure our suggested size is at |
7045 | 0 | // least 1 pixel wide/tall. |
7046 | 0 | gfxLayerSize = gfxSize(std::max(rect.Width(), 1.0), |
7047 | 0 | std::max(rect.Height(), 1.0)); |
7048 | 0 | snapped = true; |
7049 | 0 | } |
7050 | 0 | } |
7051 | 0 |
|
7052 | 0 | if (!snapped) { |
7053 | 0 | // Compute our size in layer pixels. |
7054 | 0 | const LayerIntSize layerSize = |
7055 | 0 | RoundedToInt(LayerSize(aDestRect.Width() * scaleFactors.width, |
7056 | 0 | aDestRect.Height() * scaleFactors.height)); |
7057 | 0 |
|
7058 | 0 | // An empty size is unacceptable so we ensure our suggested size is at least |
7059 | 0 | // 1 pixel wide/tall. |
7060 | 0 | gfxLayerSize = gfxSize(std::max(layerSize.width, 1), |
7061 | 0 | std::max(layerSize.height, 1)); |
7062 | 0 | } |
7063 | 0 |
|
7064 | 0 | return aImage->OptimalImageSizeForDest(gfxLayerSize, |
7065 | 0 | imgIContainer::FRAME_CURRENT, |
7066 | 0 | samplingFilter, aFlags); |
7067 | 0 | } |
7068 | | |
7069 | | /* static */ nsPoint |
7070 | | nsLayoutUtils::GetBackgroundFirstTilePos(const nsPoint& aDest, |
7071 | | const nsPoint& aFill, |
7072 | | const nsSize& aRepeatSize) |
7073 | 0 | { |
7074 | 0 | return nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) * aRepeatSize.width, |
7075 | 0 | NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) * aRepeatSize.height) + |
7076 | 0 | aDest; |
7077 | 0 | } |
7078 | | |
7079 | | /* static */ ImgDrawResult |
7080 | | nsLayoutUtils::DrawBackgroundImage(gfxContext& aContext, |
7081 | | nsIFrame* aForFrame, |
7082 | | nsPresContext* aPresContext, |
7083 | | imgIContainer* aImage, |
7084 | | const CSSIntSize& aImageSize, |
7085 | | SamplingFilter aSamplingFilter, |
7086 | | const nsRect& aDest, |
7087 | | const nsRect& aFill, |
7088 | | const nsSize& aRepeatSize, |
7089 | | const nsPoint& aAnchor, |
7090 | | const nsRect& aDirty, |
7091 | | uint32_t aImageFlags, |
7092 | | ExtendMode aExtendMode, |
7093 | | float aOpacity) |
7094 | 0 | { |
7095 | 0 | AUTO_PROFILER_LABEL("nsLayoutUtils::DrawBackgroundImage", GRAPHICS); |
7096 | 0 |
|
7097 | 0 | Maybe<SVGImageContext> svgContext(Some(SVGImageContext(Some(aImageSize)))); |
7098 | 0 | SVGImageContext::MaybeStoreContextPaint(svgContext, aForFrame, aImage); |
7099 | 0 |
|
7100 | 0 | /* Fast path when there is no need for image spacing */ |
7101 | 0 | if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) { |
7102 | 0 | return DrawImageInternal(aContext, aPresContext, aImage, |
7103 | 0 | aSamplingFilter, aDest, aFill, aAnchor, |
7104 | 0 | aDirty, svgContext, aImageFlags, aExtendMode, |
7105 | 0 | aOpacity); |
7106 | 0 | } |
7107 | 0 | |
7108 | 0 | nsPoint firstTilePos = GetBackgroundFirstTilePos(aDest.TopLeft(), aFill.TopLeft(), aRepeatSize); |
7109 | 0 | for (int32_t i = firstTilePos.x; i < aFill.XMost(); i += aRepeatSize.width) { |
7110 | 0 | for (int32_t j = firstTilePos.y; j < aFill.YMost(); j += aRepeatSize.height) { |
7111 | 0 | nsRect dest(i, j, aDest.width, aDest.height); |
7112 | 0 | ImgDrawResult result = DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter, |
7113 | 0 | dest, dest, aAnchor, aDirty, svgContext, |
7114 | 0 | aImageFlags, ExtendMode::CLAMP, |
7115 | 0 | aOpacity); |
7116 | 0 | if (result != ImgDrawResult::SUCCESS) { |
7117 | 0 | return result; |
7118 | 0 | } |
7119 | 0 | } |
7120 | 0 | } |
7121 | 0 |
|
7122 | 0 | return ImgDrawResult::SUCCESS; |
7123 | 0 | } |
7124 | | |
7125 | | /* static */ ImgDrawResult |
7126 | | nsLayoutUtils::DrawImage(gfxContext& aContext, |
7127 | | ComputedStyle* aComputedStyle, |
7128 | | nsPresContext* aPresContext, |
7129 | | imgIContainer* aImage, |
7130 | | const SamplingFilter aSamplingFilter, |
7131 | | const nsRect& aDest, |
7132 | | const nsRect& aFill, |
7133 | | const nsPoint& aAnchor, |
7134 | | const nsRect& aDirty, |
7135 | | uint32_t aImageFlags, |
7136 | | float aOpacity) |
7137 | 0 | { |
7138 | 0 | Maybe<SVGImageContext> svgContext; |
7139 | 0 | SVGImageContext::MaybeStoreContextPaint(svgContext, aComputedStyle, aImage); |
7140 | 0 |
|
7141 | 0 | return DrawImageInternal(aContext, aPresContext, aImage, |
7142 | 0 | aSamplingFilter, aDest, aFill, aAnchor, |
7143 | 0 | aDirty, |
7144 | 0 | svgContext, |
7145 | 0 | aImageFlags, ExtendMode::CLAMP, |
7146 | 0 | aOpacity); |
7147 | 0 | } |
7148 | | |
7149 | | /* static */ nsRect |
7150 | | nsLayoutUtils::GetWholeImageDestination(const nsSize& aWholeImageSize, |
7151 | | const nsRect& aImageSourceArea, |
7152 | | const nsRect& aDestArea) |
7153 | 0 | { |
7154 | 0 | double scaleX = double(aDestArea.width)/aImageSourceArea.width; |
7155 | 0 | double scaleY = double(aDestArea.height)/aImageSourceArea.height; |
7156 | 0 | nscoord destOffsetX = NSToCoordRound(aImageSourceArea.x*scaleX); |
7157 | 0 | nscoord destOffsetY = NSToCoordRound(aImageSourceArea.y*scaleY); |
7158 | 0 | nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width*scaleX); |
7159 | 0 | nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height*scaleY); |
7160 | 0 | return nsRect(aDestArea.TopLeft() - nsPoint(destOffsetX, destOffsetY), |
7161 | 0 | nsSize(wholeSizeX, wholeSizeY)); |
7162 | 0 | } |
7163 | | |
7164 | | /* static */ already_AddRefed<imgIContainer> |
7165 | | nsLayoutUtils::OrientImage(imgIContainer* aContainer, |
7166 | | const StyleImageOrientation& aOrientation) |
7167 | 0 | { |
7168 | 0 | MOZ_ASSERT(aContainer, "Should have an image container"); |
7169 | 0 | nsCOMPtr<imgIContainer> img(aContainer); |
7170 | 0 |
|
7171 | 0 | if (aOrientation == StyleImageOrientation::FromImage) { |
7172 | 0 | img = ImageOps::Orient(img, img->GetOrientation()); |
7173 | 0 | } |
7174 | 0 |
|
7175 | 0 | return img.forget(); |
7176 | 0 | } |
7177 | | |
7178 | | static bool NonZeroStyleCoord(const nsStyleCoord& aCoord) |
7179 | 0 | { |
7180 | 0 | if (aCoord.IsCoordPercentCalcUnit()) { |
7181 | 0 | // Since negative results are clamped to 0, check > 0. |
7182 | 0 | return aCoord.ComputeCoordPercentCalc(nscoord_MAX) > 0 || |
7183 | 0 | aCoord.ComputeCoordPercentCalc(0) > 0; |
7184 | 0 | } |
7185 | 0 |
|
7186 | 0 | return true; |
7187 | 0 | } |
7188 | | |
7189 | | /* static */ bool |
7190 | | nsLayoutUtils::HasNonZeroCorner(const nsStyleCorners& aCorners) |
7191 | 0 | { |
7192 | 0 | NS_FOR_CSS_HALF_CORNERS(corner) { |
7193 | 0 | if (NonZeroStyleCoord(aCorners.Get(corner))) |
7194 | 0 | return true; |
7195 | 0 | } |
7196 | 0 | return false; |
7197 | 0 | } |
7198 | | |
7199 | | // aCorner is a "full corner" value, i.e. eCornerTopLeft etc. |
7200 | | static bool IsCornerAdjacentToSide(uint8_t aCorner, Side aSide) |
7201 | 0 | { |
7202 | 0 | static_assert((int)eSideTop == eCornerTopLeft, "Check for Full Corner"); |
7203 | 0 | static_assert((int)eSideRight == eCornerTopRight, "Check for Full Corner"); |
7204 | 0 | static_assert((int)eSideBottom == eCornerBottomRight, "Check for Full Corner"); |
7205 | 0 | static_assert((int)eSideLeft == eCornerBottomLeft, "Check for Full Corner"); |
7206 | 0 | static_assert((int)eSideTop == ((eCornerTopRight - 1)&3), "Check for Full Corner"); |
7207 | 0 | static_assert((int)eSideRight == ((eCornerBottomRight - 1)&3), "Check for Full Corner"); |
7208 | 0 | static_assert((int)eSideBottom == ((eCornerBottomLeft - 1)&3), "Check for Full Corner"); |
7209 | 0 | static_assert((int)eSideLeft == ((eCornerTopLeft - 1)&3), "Check for Full Corner"); |
7210 | 0 |
|
7211 | 0 | return aSide == aCorner || aSide == ((aCorner - 1)&3); |
7212 | 0 | } |
7213 | | |
7214 | | /* static */ bool |
7215 | | nsLayoutUtils::HasNonZeroCornerOnSide(const nsStyleCorners& aCorners, |
7216 | | Side aSide) |
7217 | 0 | { |
7218 | 0 | static_assert(eCornerTopLeftX/2 == eCornerTopLeft, "Check for Non Zero on side"); |
7219 | 0 | static_assert(eCornerTopLeftY/2 == eCornerTopLeft, "Check for Non Zero on side"); |
7220 | 0 | static_assert(eCornerTopRightX/2 == eCornerTopRight, "Check for Non Zero on side"); |
7221 | 0 | static_assert(eCornerTopRightY/2 == eCornerTopRight, "Check for Non Zero on side"); |
7222 | 0 | static_assert(eCornerBottomRightX/2 == eCornerBottomRight, "Check for Non Zero on side"); |
7223 | 0 | static_assert(eCornerBottomRightY/2 == eCornerBottomRight, "Check for Non Zero on side"); |
7224 | 0 | static_assert(eCornerBottomLeftX/2 == eCornerBottomLeft, "Check for Non Zero on side"); |
7225 | 0 | static_assert(eCornerBottomLeftY/2 == eCornerBottomLeft, "Check for Non Zero on side"); |
7226 | 0 |
|
7227 | 0 | NS_FOR_CSS_HALF_CORNERS(corner) { |
7228 | 0 | // corner is a "half corner" value, so dividing by two gives us a |
7229 | 0 | // "full corner" value. |
7230 | 0 | if (NonZeroStyleCoord(aCorners.Get(corner)) && |
7231 | 0 | IsCornerAdjacentToSide(corner/2, aSide)) |
7232 | 0 | return true; |
7233 | 0 | } |
7234 | 0 | return false; |
7235 | 0 | } |
7236 | | |
7237 | | /* static */ nsTransparencyMode |
7238 | | nsLayoutUtils::GetFrameTransparency(nsIFrame* aBackgroundFrame, |
7239 | 0 | nsIFrame* aCSSRootFrame) { |
7240 | 0 | if (aCSSRootFrame->StyleEffects()->mOpacity < 1.0f) |
7241 | 0 | return eTransparencyTransparent; |
7242 | 0 | |
7243 | 0 | if (HasNonZeroCorner(aCSSRootFrame->StyleBorder()->mBorderRadius)) |
7244 | 0 | return eTransparencyTransparent; |
7245 | 0 | |
7246 | 0 | if (aCSSRootFrame->StyleDisplay()->mAppearance == StyleAppearance::MozWinGlass) |
7247 | 0 | return eTransparencyGlass; |
7248 | 0 | |
7249 | 0 | if (aCSSRootFrame->StyleDisplay()->mAppearance == StyleAppearance::MozWinBorderlessGlass) |
7250 | 0 | return eTransparencyBorderlessGlass; |
7251 | 0 | |
7252 | 0 | nsITheme::Transparency transparency; |
7253 | 0 | if (aCSSRootFrame->IsThemed(&transparency)) |
7254 | 0 | return transparency == nsITheme::eTransparent |
7255 | 0 | ? eTransparencyTransparent |
7256 | 0 | : eTransparencyOpaque; |
7257 | 0 | |
7258 | 0 | // We need an uninitialized window to be treated as opaque because |
7259 | 0 | // doing otherwise breaks window display effects on some platforms, |
7260 | 0 | // specifically Vista. (bug 450322) |
7261 | 0 | if (aBackgroundFrame->IsViewportFrame() && |
7262 | 0 | !aBackgroundFrame->PrincipalChildList().FirstChild()) { |
7263 | 0 | return eTransparencyOpaque; |
7264 | 0 | } |
7265 | 0 | |
7266 | 0 | ComputedStyle* bgSC; |
7267 | 0 | if (!nsCSSRendering::FindBackground(aBackgroundFrame, &bgSC)) { |
7268 | 0 | return eTransparencyTransparent; |
7269 | 0 | } |
7270 | 0 | const nsStyleBackground* bg = bgSC->StyleBackground(); |
7271 | 0 | if (NS_GET_A(bg->BackgroundColor(bgSC)) < 255 || |
7272 | 0 | // bottom layer's clip is used for the color |
7273 | 0 | bg->BottomLayer().mClip != StyleGeometryBox::BorderBox) |
7274 | 0 | return eTransparencyTransparent; |
7275 | 0 | return eTransparencyOpaque; |
7276 | 0 | } |
7277 | | |
7278 | | static bool IsPopupFrame(const nsIFrame* aFrame) |
7279 | 0 | { |
7280 | 0 | // aFrame is a popup it's the list control frame dropdown for a combobox. |
7281 | 0 | LayoutFrameType frameType = aFrame->Type(); |
7282 | 0 | if (!nsLayoutUtils::IsContentSelectEnabled() && |
7283 | 0 | frameType == LayoutFrameType::ListControl) { |
7284 | 0 | const nsListControlFrame* lcf = static_cast<const nsListControlFrame*>(aFrame); |
7285 | 0 | return lcf->IsInDropDownMode(); |
7286 | 0 | } |
7287 | 0 | |
7288 | 0 | // ... or if it's a XUL menupopup frame. |
7289 | 0 | return frameType == LayoutFrameType::MenuPopup; |
7290 | 0 | } |
7291 | | |
7292 | | /* static */ bool |
7293 | | nsLayoutUtils::IsPopup(const nsIFrame* aFrame) |
7294 | 0 | { |
7295 | 0 | // Optimization: the frame can't possibly be a popup if it has no view. |
7296 | 0 | if (!aFrame->HasView()) { |
7297 | 0 | NS_ASSERTION(!IsPopupFrame(aFrame), "popup frame must have a view"); |
7298 | 0 | return false; |
7299 | 0 | } |
7300 | 0 | return IsPopupFrame(aFrame); |
7301 | 0 | } |
7302 | | |
7303 | | /* static */ nsIFrame* |
7304 | | nsLayoutUtils::GetDisplayRootFrame(nsIFrame* aFrame) |
7305 | 0 | { |
7306 | 0 | // We could use GetRootPresContext() here if the |
7307 | 0 | // NS_FRAME_IN_POPUP frame bit is set. |
7308 | 0 | nsIFrame* f = aFrame; |
7309 | 0 | for (;;) { |
7310 | 0 | if (!f->HasAnyStateBits(NS_FRAME_IN_POPUP)) { |
7311 | 0 | f = f->PresShell()->GetRootFrame(); |
7312 | 0 | if (!f) { |
7313 | 0 | return aFrame; |
7314 | 0 | } |
7315 | 0 | } else if (IsPopup(f)) { |
7316 | 0 | return f; |
7317 | 0 | } |
7318 | 0 | nsIFrame* parent = GetCrossDocParentFrame(f); |
7319 | 0 | if (!parent) |
7320 | 0 | return f; |
7321 | 0 | f = parent; |
7322 | 0 | } |
7323 | 0 | } |
7324 | | |
7325 | | /* static */ nsIFrame* |
7326 | | nsLayoutUtils::GetReferenceFrame(nsIFrame* aFrame) |
7327 | 0 | { |
7328 | 0 | nsIFrame *f = aFrame; |
7329 | 0 | for (;;) { |
7330 | 0 | const nsStyleDisplay* disp = f->StyleDisplay(); |
7331 | 0 | if (f->IsTransformed(disp) || f->IsPreserve3DLeaf(disp) || IsPopup(f)) { |
7332 | 0 | return f; |
7333 | 0 | } |
7334 | 0 | nsIFrame* parent = GetCrossDocParentFrame(f); |
7335 | 0 | if (!parent) { |
7336 | 0 | return f; |
7337 | 0 | } |
7338 | 0 | f = parent; |
7339 | 0 | } |
7340 | 0 | } |
7341 | | |
7342 | | /* static */ gfx::ShapedTextFlags |
7343 | | nsLayoutUtils::GetTextRunFlagsForStyle(ComputedStyle* aComputedStyle, |
7344 | | nsPresContext* aPresContext, |
7345 | | const nsStyleFont* aStyleFont, |
7346 | | const nsStyleText* aStyleText, |
7347 | | nscoord aLetterSpacing) |
7348 | 0 | { |
7349 | 0 | gfx::ShapedTextFlags result = gfx::ShapedTextFlags(); |
7350 | 0 | if (aLetterSpacing != 0 || |
7351 | 0 | aStyleText->mTextJustify == StyleTextJustify::InterCharacter) { |
7352 | 0 | result |= gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES; |
7353 | 0 | } |
7354 | 0 | if (aStyleText->mControlCharacterVisibility == NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN) { |
7355 | 0 | result |= gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS; |
7356 | 0 | } |
7357 | 0 | switch (aComputedStyle->StyleText()->mTextRendering) { |
7358 | 0 | case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED: |
7359 | 0 | result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED; |
7360 | 0 | break; |
7361 | 0 | case NS_STYLE_TEXT_RENDERING_AUTO: |
7362 | 0 | if (aStyleFont->mFont.size < aPresContext->GetAutoQualityMinFontSize()) { |
7363 | 0 | result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED; |
7364 | 0 | } |
7365 | 0 | break; |
7366 | 0 | default: |
7367 | 0 | break; |
7368 | 0 | } |
7369 | 0 | return result | GetTextRunOrientFlagsForStyle(aComputedStyle); |
7370 | 0 | } |
7371 | | |
7372 | | /* static */ gfx::ShapedTextFlags |
7373 | | nsLayoutUtils::GetTextRunOrientFlagsForStyle(ComputedStyle* aComputedStyle) |
7374 | 0 | { |
7375 | 0 | uint8_t writingMode = aComputedStyle->StyleVisibility()->mWritingMode; |
7376 | 0 | switch (writingMode) { |
7377 | 0 | case NS_STYLE_WRITING_MODE_HORIZONTAL_TB: |
7378 | 0 | return gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL; |
7379 | 0 |
|
7380 | 0 | case NS_STYLE_WRITING_MODE_VERTICAL_LR: |
7381 | 0 | case NS_STYLE_WRITING_MODE_VERTICAL_RL: |
7382 | 0 | switch (aComputedStyle->StyleVisibility()->mTextOrientation) { |
7383 | 0 | case NS_STYLE_TEXT_ORIENTATION_MIXED: |
7384 | 0 | return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED; |
7385 | 0 | case NS_STYLE_TEXT_ORIENTATION_UPRIGHT: |
7386 | 0 | return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; |
7387 | 0 | case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS: |
7388 | 0 | return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; |
7389 | 0 | default: |
7390 | 0 | MOZ_ASSERT_UNREACHABLE("unknown text-orientation"); |
7391 | 0 | return gfx::ShapedTextFlags(); |
7392 | 0 | } |
7393 | 0 |
|
7394 | 0 | case NS_STYLE_WRITING_MODE_SIDEWAYS_LR: |
7395 | 0 | return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; |
7396 | 0 |
|
7397 | 0 | case NS_STYLE_WRITING_MODE_SIDEWAYS_RL: |
7398 | 0 | return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; |
7399 | 0 |
|
7400 | 0 | default: |
7401 | 0 | MOZ_ASSERT_UNREACHABLE("unknown writing-mode"); |
7402 | 0 | return gfx::ShapedTextFlags(); |
7403 | 0 | } |
7404 | 0 | } |
7405 | | |
7406 | | /* static */ void |
7407 | | nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2, |
7408 | 0 | nsRect* aHStrip, nsRect* aVStrip) { |
7409 | 0 | NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(), |
7410 | 0 | "expected rects at the same position"); |
7411 | 0 | nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width), |
7412 | 0 | std::max(aR1.height, aR2.height)); |
7413 | 0 | nscoord VStripStart = std::min(aR1.width, aR2.width); |
7414 | 0 | nscoord HStripStart = std::min(aR1.height, aR2.height); |
7415 | 0 | *aVStrip = unionRect; |
7416 | 0 | aVStrip->x += VStripStart; |
7417 | 0 | aVStrip->width -= VStripStart; |
7418 | 0 | *aHStrip = unionRect; |
7419 | 0 | aHStrip->y += HStripStart; |
7420 | 0 | aHStrip->height -= HStripStart; |
7421 | 0 | } |
7422 | | |
7423 | | nsDeviceContext* |
7424 | | nsLayoutUtils::GetDeviceContextForScreenInfo(nsPIDOMWindowOuter* aWindow) |
7425 | 0 | { |
7426 | 0 | if (!aWindow) { |
7427 | 0 | return nullptr; |
7428 | 0 | } |
7429 | 0 | |
7430 | 0 | nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); |
7431 | 0 | while (docShell) { |
7432 | 0 | // Now make sure our size is up to date. That will mean that the device |
7433 | 0 | // context does the right thing on multi-monitor systems when we return it to |
7434 | 0 | // the caller. It will also make sure that our prescontext has been created, |
7435 | 0 | // if we're supposed to have one. |
7436 | 0 | nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow(); |
7437 | 0 | if (!win) { |
7438 | 0 | // No reason to go on |
7439 | 0 | return nullptr; |
7440 | 0 | } |
7441 | 0 | |
7442 | 0 | win->EnsureSizeAndPositionUpToDate(); |
7443 | 0 |
|
7444 | 0 | RefPtr<nsPresContext> presContext; |
7445 | 0 | docShell->GetPresContext(getter_AddRefs(presContext)); |
7446 | 0 | if (presContext) { |
7447 | 0 | nsDeviceContext* context = presContext->DeviceContext(); |
7448 | 0 | if (context) { |
7449 | 0 | return context; |
7450 | 0 | } |
7451 | 0 | } |
7452 | 0 | |
7453 | 0 | nsCOMPtr<nsIDocShellTreeItem> parentItem; |
7454 | 0 | docShell->GetParent(getter_AddRefs(parentItem)); |
7455 | 0 | docShell = do_QueryInterface(parentItem); |
7456 | 0 | } |
7457 | 0 |
|
7458 | 0 | return nullptr; |
7459 | 0 | } |
7460 | | |
7461 | | /* static */ bool |
7462 | | nsLayoutUtils::IsReallyFixedPos(const nsIFrame* aFrame) |
7463 | 0 | { |
7464 | 0 | MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED, |
7465 | 0 | "IsReallyFixedPos called on non-'position:fixed' frame"); |
7466 | 0 | return MayBeReallyFixedPos(aFrame); |
7467 | 0 | } |
7468 | | |
7469 | | |
7470 | | /* static */ bool |
7471 | | nsLayoutUtils::MayBeReallyFixedPos(const nsIFrame* aFrame) |
7472 | 0 | { |
7473 | 0 | MOZ_ASSERT(aFrame->GetParent(), |
7474 | 0 | "MayBeReallyFixedPos called on frame not in tree"); |
7475 | 0 | LayoutFrameType parentType = aFrame->GetParent()->Type(); |
7476 | 0 | return parentType == LayoutFrameType::Viewport || |
7477 | 0 | parentType == LayoutFrameType::PageContent; |
7478 | 0 | } |
7479 | | |
7480 | | nsLayoutUtils::SurfaceFromElementResult |
7481 | | nsLayoutUtils::SurfaceFromOffscreenCanvas(OffscreenCanvas* aOffscreenCanvas, |
7482 | | uint32_t aSurfaceFlags, |
7483 | | RefPtr<DrawTarget>& aTarget) |
7484 | 0 | { |
7485 | 0 | SurfaceFromElementResult result; |
7486 | 0 |
|
7487 | 0 | nsIntSize size = aOffscreenCanvas->GetWidthHeight(); |
7488 | 0 |
|
7489 | 0 | result.mSourceSurface = aOffscreenCanvas->GetSurfaceSnapshot(&result.mAlphaType); |
7490 | 0 | if (!result.mSourceSurface) { |
7491 | 0 | // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just |
7492 | 0 | // draw nothing, so return an empty surface. |
7493 | 0 | result.mAlphaType = gfxAlphaType::Opaque; |
7494 | 0 | RefPtr<DrawTarget> ref = |
7495 | 0 | aTarget ? aTarget |
7496 | 0 | : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); |
7497 | 0 | RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height), |
7498 | 0 | SurfaceFormat::B8G8R8A8); |
7499 | 0 | if (dt) { |
7500 | 0 | result.mSourceSurface = dt->Snapshot(); |
7501 | 0 | } |
7502 | 0 | } else if (aTarget) { |
7503 | 0 | RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface); |
7504 | 0 | if (opt) { |
7505 | 0 | result.mSourceSurface = opt; |
7506 | 0 | } |
7507 | 0 | } |
7508 | 0 |
|
7509 | 0 | result.mHasSize = true; |
7510 | 0 | result.mSize = size; |
7511 | 0 | result.mIsWriteOnly = aOffscreenCanvas->IsWriteOnly(); |
7512 | 0 |
|
7513 | 0 | return result; |
7514 | 0 | } |
7515 | | |
7516 | | nsLayoutUtils::SurfaceFromElementResult |
7517 | | nsLayoutUtils::SurfaceFromElement(nsIImageLoadingContent* aElement, |
7518 | | uint32_t aSurfaceFlags, |
7519 | | RefPtr<DrawTarget>& aTarget) |
7520 | 0 | { |
7521 | 0 | SurfaceFromElementResult result; |
7522 | 0 | nsresult rv; |
7523 | 0 |
|
7524 | 0 | nsCOMPtr<imgIRequest> imgRequest; |
7525 | 0 | rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, |
7526 | 0 | getter_AddRefs(imgRequest)); |
7527 | 0 | if (NS_FAILED(rv)) { |
7528 | 0 | return result; |
7529 | 0 | } |
7530 | 0 | |
7531 | 0 | if (!imgRequest) { |
7532 | 0 | // There's no image request. This is either because a request for |
7533 | 0 | // a non-empty URI failed, or the URI is the empty string. |
7534 | 0 | nsCOMPtr<nsIURI> currentURI; |
7535 | 0 | aElement->GetCurrentURI(getter_AddRefs(currentURI)); |
7536 | 0 | if (!currentURI) { |
7537 | 0 | // Treat the empty URI as available instead of broken state. |
7538 | 0 | result.mHasSize = true; |
7539 | 0 | } |
7540 | 0 | return result; |
7541 | 0 | } |
7542 | 0 |
|
7543 | 0 | uint32_t status; |
7544 | 0 | imgRequest->GetImageStatus(&status); |
7545 | 0 | result.mHasSize = status & imgIRequest::STATUS_SIZE_AVAILABLE; |
7546 | 0 | if ((status & imgIRequest::STATUS_LOAD_COMPLETE) == 0) { |
7547 | 0 | // Spec says to use GetComplete, but that only works on |
7548 | 0 | // HTMLImageElement, and we support all sorts of other stuff |
7549 | 0 | // here. Do this for now pending spec clarification. |
7550 | 0 | result.mIsStillLoading = (status & imgIRequest::STATUS_ERROR) == 0; |
7551 | 0 | return result; |
7552 | 0 | } |
7553 | 0 | |
7554 | 0 | nsCOMPtr<nsIPrincipal> principal; |
7555 | 0 | rv = imgRequest->GetImagePrincipal(getter_AddRefs(principal)); |
7556 | 0 | if (NS_FAILED(rv)) { |
7557 | 0 | return result; |
7558 | 0 | } |
7559 | 0 | |
7560 | 0 | nsCOMPtr<imgIContainer> imgContainer; |
7561 | 0 | rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); |
7562 | 0 | if (NS_FAILED(rv)) { |
7563 | 0 | return result; |
7564 | 0 | } |
7565 | 0 | |
7566 | 0 | uint32_t noRasterize = aSurfaceFlags & SFE_NO_RASTERIZING_VECTORS; |
7567 | 0 |
|
7568 | 0 | uint32_t whichFrame = (aSurfaceFlags & SFE_WANT_FIRST_FRAME_IF_IMAGE) |
7569 | 0 | ? (uint32_t) imgIContainer::FRAME_FIRST |
7570 | 0 | : (uint32_t) imgIContainer::FRAME_CURRENT; |
7571 | 0 | uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE |
7572 | 0 | | imgIContainer::FLAG_ASYNC_NOTIFY; |
7573 | 0 | if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION) |
7574 | 0 | frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION; |
7575 | 0 | if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) { |
7576 | 0 | frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA; |
7577 | 0 | } |
7578 | 0 |
|
7579 | 0 | int32_t imgWidth, imgHeight; |
7580 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(aElement); |
7581 | 0 | HTMLImageElement* element = HTMLImageElement::FromNodeOrNull(content); |
7582 | 0 | if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR && |
7583 | 0 | element && |
7584 | 0 | imgContainer->GetType() == imgIContainer::TYPE_VECTOR) { |
7585 | 0 | // We're holding a strong ref to "element" via "content". |
7586 | 0 | imgWidth = MOZ_KnownLive(element)->Width(); |
7587 | 0 | imgHeight = MOZ_KnownLive(element)->Height(); |
7588 | 0 | } else { |
7589 | 0 | rv = imgContainer->GetWidth(&imgWidth); |
7590 | 0 | nsresult rv2 = imgContainer->GetHeight(&imgHeight); |
7591 | 0 | if (NS_FAILED(rv) || NS_FAILED(rv2)) |
7592 | 0 | return result; |
7593 | 0 | } |
7594 | 0 | result.mSize = IntSize(imgWidth, imgHeight); |
7595 | 0 |
|
7596 | 0 | if (!noRasterize || imgContainer->GetType() == imgIContainer::TYPE_RASTER) { |
7597 | 0 | if (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) { |
7598 | 0 | frameFlags |= imgIContainer::FLAG_WANT_DATA_SURFACE; |
7599 | 0 | } |
7600 | 0 | result.mSourceSurface = imgContainer->GetFrameAtSize(result.mSize, whichFrame, frameFlags); |
7601 | 0 | if (!result.mSourceSurface) { |
7602 | 0 | return result; |
7603 | 0 | } |
7604 | 0 | // The surface we return is likely to be cached. We don't want to have to |
7605 | 0 | // convert to a surface that's compatible with aTarget each time it's used |
7606 | 0 | // (that would result in terrible performance), so we convert once here |
7607 | 0 | // upfront if aTarget is specified. |
7608 | 0 | if (aTarget) { |
7609 | 0 | RefPtr<SourceSurface> optSurface = |
7610 | 0 | aTarget->OptimizeSourceSurface(result.mSourceSurface); |
7611 | 0 | if (optSurface) { |
7612 | 0 | result.mSourceSurface = optSurface; |
7613 | 0 | } |
7614 | 0 | } |
7615 | 0 |
|
7616 | 0 | const auto& format = result.mSourceSurface->GetFormat(); |
7617 | 0 | if (IsOpaque(format)) { |
7618 | 0 | result.mAlphaType = gfxAlphaType::Opaque; |
7619 | 0 | } else if (frameFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) { |
7620 | 0 | result.mAlphaType = gfxAlphaType::NonPremult; |
7621 | 0 | } else { |
7622 | 0 | result.mAlphaType = gfxAlphaType::Premult; |
7623 | 0 | } |
7624 | 0 | } else { |
7625 | 0 | result.mDrawInfo.mImgContainer = imgContainer; |
7626 | 0 | result.mDrawInfo.mWhichFrame = whichFrame; |
7627 | 0 | result.mDrawInfo.mDrawingFlags = frameFlags; |
7628 | 0 | } |
7629 | 0 |
|
7630 | 0 | int32_t corsmode; |
7631 | 0 | if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) { |
7632 | 0 | result.mCORSUsed = (corsmode != imgIRequest::CORS_NONE); |
7633 | 0 | } |
7634 | 0 |
|
7635 | 0 | result.mPrincipal = principal.forget(); |
7636 | 0 | // no images, including SVG images, can load content from another domain. |
7637 | 0 | result.mIsWriteOnly = false; |
7638 | 0 | result.mImageRequest = imgRequest.forget(); |
7639 | 0 | return result; |
7640 | 0 | } |
7641 | | |
7642 | | nsLayoutUtils::SurfaceFromElementResult |
7643 | | nsLayoutUtils::SurfaceFromElement(HTMLImageElement *aElement, |
7644 | | uint32_t aSurfaceFlags, |
7645 | | RefPtr<DrawTarget>& aTarget) |
7646 | 0 | { |
7647 | 0 | return SurfaceFromElement(static_cast<nsIImageLoadingContent*>(aElement), |
7648 | 0 | aSurfaceFlags, aTarget); |
7649 | 0 | } |
7650 | | |
7651 | | nsLayoutUtils::SurfaceFromElementResult |
7652 | | nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement, |
7653 | | uint32_t aSurfaceFlags, |
7654 | | RefPtr<DrawTarget>& aTarget) |
7655 | 0 | { |
7656 | 0 | SurfaceFromElementResult result; |
7657 | 0 |
|
7658 | 0 | IntSize size = aElement->GetSize(); |
7659 | 0 |
|
7660 | 0 | result.mSourceSurface = aElement->GetSurfaceSnapshot(&result.mAlphaType); |
7661 | 0 | if (!result.mSourceSurface) { |
7662 | 0 | // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just |
7663 | 0 | // draw nothing, so return an empty surface. |
7664 | 0 | result.mAlphaType = gfxAlphaType::Opaque; |
7665 | 0 | RefPtr<DrawTarget> ref = |
7666 | 0 | aTarget ? aTarget |
7667 | 0 | : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); |
7668 | 0 | RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height), |
7669 | 0 | SurfaceFormat::B8G8R8A8); |
7670 | 0 | if (dt) { |
7671 | 0 | result.mSourceSurface = dt->Snapshot(); |
7672 | 0 | } |
7673 | 0 | } else if (aTarget) { |
7674 | 0 | RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface); |
7675 | 0 | if (opt) { |
7676 | 0 | result.mSourceSurface = opt; |
7677 | 0 | } |
7678 | 0 | } |
7679 | 0 |
|
7680 | 0 | // Ensure that any future changes to the canvas trigger proper invalidation, |
7681 | 0 | // in case this is being used by -moz-element() |
7682 | 0 | aElement->MarkContextClean(); |
7683 | 0 |
|
7684 | 0 | result.mHasSize = true; |
7685 | 0 | result.mSize = size; |
7686 | 0 | result.mPrincipal = aElement->NodePrincipal(); |
7687 | 0 | result.mIsWriteOnly = aElement->IsWriteOnly(); |
7688 | 0 |
|
7689 | 0 | return result; |
7690 | 0 | } |
7691 | | |
7692 | | nsLayoutUtils::SurfaceFromElementResult |
7693 | | nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement, |
7694 | | uint32_t aSurfaceFlags, |
7695 | | RefPtr<DrawTarget>& aTarget) |
7696 | 0 | { |
7697 | 0 | SurfaceFromElementResult result; |
7698 | 0 | result.mAlphaType = gfxAlphaType::Opaque; // Assume opaque. |
7699 | 0 |
|
7700 | 0 | if (aElement->ContainsRestrictedContent()) { |
7701 | 0 | return result; |
7702 | 0 | } |
7703 | 0 | |
7704 | 0 | uint16_t readyState = aElement->ReadyState(); |
7705 | 0 | if (readyState == HAVE_NOTHING || |
7706 | 0 | readyState == HAVE_METADATA) { |
7707 | 0 | result.mIsStillLoading = true; |
7708 | 0 | return result; |
7709 | 0 | } |
7710 | 0 | |
7711 | 0 | // If it doesn't have a principal, just bail |
7712 | 0 | nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentVideoPrincipal(); |
7713 | 0 | if (!principal) |
7714 | 0 | return result; |
7715 | 0 | |
7716 | 0 | result.mLayersImage = aElement->GetCurrentImage(); |
7717 | 0 | if (!result.mLayersImage) |
7718 | 0 | return result; |
7719 | 0 | |
7720 | 0 | if (aTarget) { |
7721 | 0 | // They gave us a DrawTarget to optimize for, so even though we have a layers::Image, |
7722 | 0 | // we should unconditionally grab a SourceSurface and try to optimize it. |
7723 | 0 | result.mSourceSurface = result.mLayersImage->GetAsSourceSurface(); |
7724 | 0 | if (!result.mSourceSurface) |
7725 | 0 | return result; |
7726 | 0 | |
7727 | 0 | RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface); |
7728 | 0 | if (opt) { |
7729 | 0 | result.mSourceSurface = opt; |
7730 | 0 | } |
7731 | 0 | } |
7732 | 0 |
|
7733 | 0 | result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE; |
7734 | 0 | result.mHasSize = true; |
7735 | 0 | result.mSize = result.mLayersImage->GetSize(); |
7736 | 0 | result.mPrincipal = principal.forget(); |
7737 | 0 | result.mIsWriteOnly = false; |
7738 | 0 |
|
7739 | 0 | return result; |
7740 | 0 | } |
7741 | | |
7742 | | nsLayoutUtils::SurfaceFromElementResult |
7743 | | nsLayoutUtils::SurfaceFromElement(dom::Element* aElement, |
7744 | | uint32_t aSurfaceFlags, |
7745 | | RefPtr<DrawTarget>& aTarget) |
7746 | 0 | { |
7747 | 0 | // If it's a <canvas>, we may be able to just grab its internal surface |
7748 | 0 | if (HTMLCanvasElement* canvas = |
7749 | 0 | HTMLCanvasElement::FromNodeOrNull(aElement)) { |
7750 | 0 | return SurfaceFromElement(canvas, aSurfaceFlags, aTarget); |
7751 | 0 | } |
7752 | 0 | |
7753 | 0 | // Maybe it's <video>? |
7754 | 0 | if (HTMLVideoElement* video = |
7755 | 0 | HTMLVideoElement::FromNodeOrNull(aElement)) { |
7756 | 0 | return SurfaceFromElement(video, aSurfaceFlags, aTarget); |
7757 | 0 | } |
7758 | 0 | |
7759 | 0 | // Finally, check if it's a normal image |
7760 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement); |
7761 | 0 |
|
7762 | 0 | if (!imageLoader) { |
7763 | 0 | return SurfaceFromElementResult(); |
7764 | 0 | } |
7765 | 0 | |
7766 | 0 | return SurfaceFromElement(imageLoader, aSurfaceFlags, aTarget); |
7767 | 0 | } |
7768 | | |
7769 | | /* static */ |
7770 | | Element* |
7771 | | nsLayoutUtils::GetEditableRootContentByContentEditable(nsIDocument* aDocument) |
7772 | 0 | { |
7773 | 0 | // If the document is in designMode we should return nullptr. |
7774 | 0 | if (!aDocument || aDocument->HasFlag(NODE_IS_EDITABLE)) { |
7775 | 0 | return nullptr; |
7776 | 0 | } |
7777 | 0 | |
7778 | 0 | // contenteditable only works with HTML document. |
7779 | 0 | // XXXbz should this test IsHTMLOrXHTML(), or just IsHTML()? |
7780 | 0 | if (!aDocument->IsHTMLOrXHTML()) { |
7781 | 0 | return nullptr; |
7782 | 0 | } |
7783 | 0 | |
7784 | 0 | Element* rootElement = aDocument->GetRootElement(); |
7785 | 0 | if (rootElement && rootElement->IsEditable()) { |
7786 | 0 | return rootElement; |
7787 | 0 | } |
7788 | 0 | |
7789 | 0 | // If there is no editable root element, check its <body> element. |
7790 | 0 | // Note that the body element could be <frameset> element. |
7791 | 0 | Element* bodyElement = aDocument->GetBody(); |
7792 | 0 | if (bodyElement && bodyElement->IsEditable()) { |
7793 | 0 | return bodyElement; |
7794 | 0 | } |
7795 | 0 | return nullptr; |
7796 | 0 | } |
7797 | | |
7798 | | #ifdef DEBUG |
7799 | | /* static */ void |
7800 | | nsLayoutUtils::AssertNoDuplicateContinuations(nsIFrame* aContainer, |
7801 | | const nsFrameList& aFrameList) |
7802 | | { |
7803 | | for (nsIFrame* f : aFrameList) { |
7804 | | // Check only later continuations of f; we deal with checking the |
7805 | | // earlier continuations when we hit those earlier continuations in |
7806 | | // the frame list. |
7807 | | for (nsIFrame *c = f; (c = c->GetNextInFlow());) { |
7808 | | NS_ASSERTION(c->GetParent() != aContainer || |
7809 | | !aFrameList.ContainsFrame(c), |
7810 | | "Two continuations of the same frame in the same " |
7811 | | "frame list"); |
7812 | | } |
7813 | | } |
7814 | | } |
7815 | | |
7816 | | // Is one of aFrame's ancestors a letter frame? |
7817 | | static bool |
7818 | | IsInLetterFrame(nsIFrame *aFrame) |
7819 | | { |
7820 | | for (nsIFrame *f = aFrame->GetParent(); f; f = f->GetParent()) { |
7821 | | if (f->IsLetterFrame()) { |
7822 | | return true; |
7823 | | } |
7824 | | } |
7825 | | return false; |
7826 | | } |
7827 | | |
7828 | | /* static */ void |
7829 | | nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(nsIFrame *aSubtreeRoot) |
7830 | | { |
7831 | | NS_ASSERTION(aSubtreeRoot->GetPrevInFlow(), |
7832 | | "frame tree not empty, but caller reported complete status"); |
7833 | | |
7834 | | // Also assert that text frames map no text. |
7835 | | int32_t start, end; |
7836 | | nsresult rv = aSubtreeRoot->GetOffsets(start, end); |
7837 | | NS_ASSERTION(NS_SUCCEEDED(rv), "GetOffsets failed"); |
7838 | | // In some cases involving :first-letter, we'll partially unlink a |
7839 | | // continuation in the middle of a continuation chain from its |
7840 | | // previous and next continuations before destroying it, presumably so |
7841 | | // that we don't also destroy the later continuations. Once we've |
7842 | | // done this, GetOffsets returns incorrect values. |
7843 | | // For examples, see list of tests in |
7844 | | // https://bugzilla.mozilla.org/show_bug.cgi?id=619021#c29 |
7845 | | NS_ASSERTION(start == end || IsInLetterFrame(aSubtreeRoot), |
7846 | | "frame tree not empty, but caller reported complete status"); |
7847 | | |
7848 | | nsIFrame::ChildListIterator lists(aSubtreeRoot); |
7849 | | for (; !lists.IsDone(); lists.Next()) { |
7850 | | nsFrameList::Enumerator childFrames(lists.CurrentList()); |
7851 | | for (; !childFrames.AtEnd(); childFrames.Next()) { |
7852 | | nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(childFrames.get()); |
7853 | | } |
7854 | | } |
7855 | | } |
7856 | | #endif |
7857 | | |
7858 | | static void |
7859 | | GetFontFacesForFramesInner(nsIFrame* aFrame, |
7860 | | nsLayoutUtils::UsedFontFaceTable& aFontFaces, |
7861 | | uint32_t aMaxRanges, |
7862 | | bool aSkipCollapsedWhitespace) |
7863 | 0 | { |
7864 | 0 | MOZ_ASSERT(aFrame, "NULL frame pointer"); |
7865 | 0 |
|
7866 | 0 | if (aFrame->IsTextFrame()) { |
7867 | 0 | if (!aFrame->GetPrevContinuation()) { |
7868 | 0 | nsLayoutUtils::GetFontFacesForText(aFrame, 0, INT32_MAX, true, |
7869 | 0 | aFontFaces, aMaxRanges, |
7870 | 0 | aSkipCollapsedWhitespace); |
7871 | 0 | } |
7872 | 0 | return; |
7873 | 0 | } |
7874 | 0 |
|
7875 | 0 | nsIFrame::ChildListID childLists[] = { nsIFrame::kPrincipalList, |
7876 | 0 | nsIFrame::kPopupList }; |
7877 | 0 | for (size_t i = 0; i < ArrayLength(childLists); ++i) { |
7878 | 0 | nsFrameList children(aFrame->GetChildList(childLists[i])); |
7879 | 0 | for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) { |
7880 | 0 | nsIFrame* child = e.get(); |
7881 | 0 | child = nsPlaceholderFrame::GetRealFrameFor(child); |
7882 | 0 | GetFontFacesForFramesInner(child, aFontFaces, aMaxRanges, |
7883 | 0 | aSkipCollapsedWhitespace); |
7884 | 0 | } |
7885 | 0 | } |
7886 | 0 | } |
7887 | | |
7888 | | /* static */ nsresult |
7889 | | nsLayoutUtils::GetFontFacesForFrames(nsIFrame* aFrame, |
7890 | | UsedFontFaceTable& aFontFaces, |
7891 | | uint32_t aMaxRanges, |
7892 | | bool aSkipCollapsedWhitespace) |
7893 | 0 | { |
7894 | 0 | MOZ_ASSERT(aFrame, "NULL frame pointer"); |
7895 | 0 |
|
7896 | 0 | while (aFrame) { |
7897 | 0 | GetFontFacesForFramesInner(aFrame, aFontFaces, aMaxRanges, |
7898 | 0 | aSkipCollapsedWhitespace); |
7899 | 0 | aFrame = GetNextContinuationOrIBSplitSibling(aFrame); |
7900 | 0 | } |
7901 | 0 |
|
7902 | 0 | return NS_OK; |
7903 | 0 | } |
7904 | | |
7905 | | static void |
7906 | | AddFontsFromTextRun(gfxTextRun* aTextRun, |
7907 | | nsTextFrame* aFrame, |
7908 | | gfxSkipCharsIterator& aSkipIter, |
7909 | | const gfxTextRun::Range& aRange, |
7910 | | nsLayoutUtils::UsedFontFaceTable& aFontFaces, |
7911 | | uint32_t aMaxRanges) |
7912 | 0 | { |
7913 | 0 | gfxTextRun::GlyphRunIterator glyphRuns(aTextRun, aRange); |
7914 | 0 | nsIContent* content = aFrame->GetContent(); |
7915 | 0 | int32_t contentLimit = aFrame->GetContentOffset() + |
7916 | 0 | aFrame->GetInFlowContentLength(); |
7917 | 0 | while (glyphRuns.NextRun()) { |
7918 | 0 | gfxFontEntry *fe = glyphRuns.GetGlyphRun()->mFont->GetFontEntry(); |
7919 | 0 | // if we have already listed this face, just make sure the match type is |
7920 | 0 | // recorded |
7921 | 0 | InspectorFontFace* fontFace = aFontFaces.Get(fe); |
7922 | 0 | if (fontFace) { |
7923 | 0 | fontFace->AddMatchType(glyphRuns.GetGlyphRun()->mMatchType); |
7924 | 0 | } else { |
7925 | 0 | // A new font entry we haven't seen before |
7926 | 0 | fontFace = new InspectorFontFace(fe, aTextRun->GetFontGroup(), |
7927 | 0 | glyphRuns.GetGlyphRun()->mMatchType); |
7928 | 0 | aFontFaces.Put(fe, fontFace); |
7929 | 0 | } |
7930 | 0 |
|
7931 | 0 | // Add this glyph run to the fontFace's list of ranges, unless we have |
7932 | 0 | // already collected as many as wanted. |
7933 | 0 | if (fontFace->RangeCount() < aMaxRanges) { |
7934 | 0 | int32_t start = |
7935 | 0 | aSkipIter.ConvertSkippedToOriginal(glyphRuns.GetStringStart()); |
7936 | 0 | int32_t end = |
7937 | 0 | aSkipIter.ConvertSkippedToOriginal(glyphRuns.GetStringEnd()); |
7938 | 0 |
|
7939 | 0 | // Mapping back from textrun offsets ("skipped" offsets that reflect the |
7940 | 0 | // text after whitespace collapsing, etc) to DOM content offsets in the |
7941 | 0 | // original text is ambiguous, because many original characters can |
7942 | 0 | // map to a single skipped offset. aSkipIter.ConvertSkippedToOriginal() |
7943 | 0 | // will return an "original" offset that corresponds to the *end* of |
7944 | 0 | // a collapsed run of characters in this case; but that might extend |
7945 | 0 | // beyond the current content node if the textrun mapped multiple nodes. |
7946 | 0 | // So we clamp the end offset to keep it valid for the content node |
7947 | 0 | // that corresponds to the current textframe. |
7948 | 0 | end = std::min(end, contentLimit); |
7949 | 0 |
|
7950 | 0 | if (end > start) { |
7951 | 0 | RefPtr<nsRange> range; |
7952 | 0 | if (NS_FAILED(nsRange::CreateRange(content, start, content, end, |
7953 | 0 | getter_AddRefs(range)))) { |
7954 | 0 | NS_WARNING("failed to create range"); |
7955 | 0 | } else { |
7956 | 0 | fontFace->AddRange(range); |
7957 | 0 | } |
7958 | 0 | } |
7959 | 0 | } |
7960 | 0 | } |
7961 | 0 | } |
7962 | | |
7963 | | /* static */ void |
7964 | | nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame, |
7965 | | int32_t aStartOffset, |
7966 | | int32_t aEndOffset, |
7967 | | bool aFollowContinuations, |
7968 | | UsedFontFaceTable& aFontFaces, |
7969 | | uint32_t aMaxRanges, |
7970 | | bool aSkipCollapsedWhitespace) |
7971 | 0 | { |
7972 | 0 | MOZ_ASSERT(aFrame, "NULL frame pointer"); |
7973 | 0 |
|
7974 | 0 | if (!aFrame->IsTextFrame()) { |
7975 | 0 | return; |
7976 | 0 | } |
7977 | 0 | |
7978 | 0 | if (!aFrame->StyleVisibility()->IsVisible()) { |
7979 | 0 | return; |
7980 | 0 | } |
7981 | 0 | |
7982 | 0 | nsTextFrame* curr = static_cast<nsTextFrame*>(aFrame); |
7983 | 0 | do { |
7984 | 0 | int32_t fstart = std::max(curr->GetContentOffset(), aStartOffset); |
7985 | 0 | int32_t fend = std::min(curr->GetContentEnd(), aEndOffset); |
7986 | 0 | if (fstart >= fend) { |
7987 | 0 | curr = static_cast<nsTextFrame*>(curr->GetNextContinuation()); |
7988 | 0 | continue; |
7989 | 0 | } |
7990 | 0 | |
7991 | 0 | // curr is overlapping with the offset we want |
7992 | 0 | gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated); |
7993 | 0 | gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated); |
7994 | 0 | if (!textRun) { |
7995 | 0 | NS_WARNING("failed to get textRun, low memory?"); |
7996 | 0 | return; |
7997 | 0 | } |
7998 | 0 |
|
7999 | 0 | // include continuations in the range that share the same textrun |
8000 | 0 | nsTextFrame* next = nullptr; |
8001 | 0 | if (aFollowContinuations && fend < aEndOffset) { |
8002 | 0 | next = static_cast<nsTextFrame*>(curr->GetNextContinuation()); |
8003 | 0 | while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) { |
8004 | 0 | fend = std::min(next->GetContentEnd(), aEndOffset); |
8005 | 0 | next = fend < aEndOffset ? |
8006 | 0 | static_cast<nsTextFrame*>(next->GetNextContinuation()) : nullptr; |
8007 | 0 | } |
8008 | 0 | } |
8009 | 0 |
|
8010 | 0 | if (!aSkipCollapsedWhitespace || |
8011 | 0 | (curr->HasAnyNoncollapsedCharacters() && |
8012 | 0 | curr->HasNonSuppressedText())) { |
8013 | 0 | gfxTextRun::Range range(iter.ConvertOriginalToSkipped(fstart), |
8014 | 0 | iter.ConvertOriginalToSkipped(fend)); |
8015 | 0 | AddFontsFromTextRun(textRun, curr, iter, range, aFontFaces, aMaxRanges); |
8016 | 0 | } |
8017 | 0 |
|
8018 | 0 | curr = next; |
8019 | 0 | } while (aFollowContinuations && curr); |
8020 | 0 | } |
8021 | | |
8022 | | /* static */ |
8023 | | size_t |
8024 | | nsLayoutUtils::SizeOfTextRunsForFrames(nsIFrame* aFrame, |
8025 | | MallocSizeOf aMallocSizeOf, |
8026 | | bool clear) |
8027 | 0 | { |
8028 | 0 | MOZ_ASSERT(aFrame, "NULL frame pointer"); |
8029 | 0 |
|
8030 | 0 | size_t total = 0; |
8031 | 0 |
|
8032 | 0 | if (aFrame->IsTextFrame()) { |
8033 | 0 | nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame); |
8034 | 0 | for (uint32_t i = 0; i < 2; ++i) { |
8035 | 0 | gfxTextRun *run = textFrame->GetTextRun( |
8036 | 0 | (i != 0) ? nsTextFrame::eInflated : nsTextFrame::eNotInflated); |
8037 | 0 | if (run) { |
8038 | 0 | if (clear) { |
8039 | 0 | run->ResetSizeOfAccountingFlags(); |
8040 | 0 | } else { |
8041 | 0 | total += run->MaybeSizeOfIncludingThis(aMallocSizeOf); |
8042 | 0 | } |
8043 | 0 | } |
8044 | 0 | } |
8045 | 0 | return total; |
8046 | 0 | } |
8047 | 0 |
|
8048 | 0 | AutoTArray<nsIFrame::ChildList,4> childListArray; |
8049 | 0 | aFrame->GetChildLists(&childListArray); |
8050 | 0 |
|
8051 | 0 | for (nsIFrame::ChildListArrayIterator childLists(childListArray); |
8052 | 0 | !childLists.IsDone(); childLists.Next()) { |
8053 | 0 | for (nsFrameList::Enumerator e(childLists.CurrentList()); |
8054 | 0 | !e.AtEnd(); e.Next()) { |
8055 | 0 | total += SizeOfTextRunsForFrames(e.get(), aMallocSizeOf, clear); |
8056 | 0 | } |
8057 | 0 | } |
8058 | 0 | return total; |
8059 | 0 | } |
8060 | | |
8061 | | /* static */ |
8062 | | void |
8063 | | nsLayoutUtils::Initialize() |
8064 | 3 | { |
8065 | 3 | Preferences::AddUintVarCache(&sFontSizeInflationMaxRatio, |
8066 | 3 | "font.size.inflation.maxRatio"); |
8067 | 3 | Preferences::AddUintVarCache(&sFontSizeInflationEmPerLine, |
8068 | 3 | "font.size.inflation.emPerLine"); |
8069 | 3 | Preferences::AddUintVarCache(&sFontSizeInflationMinTwips, |
8070 | 3 | "font.size.inflation.minTwips"); |
8071 | 3 | Preferences::AddUintVarCache(&sFontSizeInflationLineThreshold, |
8072 | 3 | "font.size.inflation.lineThreshold"); |
8073 | 3 | Preferences::AddIntVarCache(&sFontSizeInflationMappingIntercept, |
8074 | 3 | "font.size.inflation.mappingIntercept"); |
8075 | 3 | Preferences::AddBoolVarCache(&sFontSizeInflationForceEnabled, |
8076 | 3 | "font.size.inflation.forceEnabled"); |
8077 | 3 | Preferences::AddBoolVarCache(&sFontSizeInflationDisabledInMasterProcess, |
8078 | 3 | "font.size.inflation.disabledInMasterProcess"); |
8079 | 3 | Preferences::AddUintVarCache(&sSystemFontScale, |
8080 | 3 | "font.size.systemFontScale", 100); |
8081 | 3 | Preferences::AddUintVarCache(&sZoomMaxPercent, |
8082 | 3 | "zoom.maxPercent", 300); |
8083 | 3 | Preferences::AddUintVarCache(&sZoomMinPercent, |
8084 | 3 | "zoom.minPercent", 30); |
8085 | 3 | Preferences::AddBoolVarCache(&sInvalidationDebuggingIsEnabled, |
8086 | 3 | "nglayout.debug.invalidation"); |
8087 | 3 | Preferences::AddBoolVarCache(&sInterruptibleReflowEnabled, |
8088 | 3 | "layout.interruptible-reflow.enabled"); |
8089 | 3 | Preferences::AddBoolVarCache(&sSVGTransformBoxEnabled, |
8090 | 3 | "svg.transform-box.enabled"); |
8091 | 3 | Preferences::AddUintVarCache(&sIdlePeriodDeadlineLimit, |
8092 | 3 | "layout.idle_period.time_limit", |
8093 | 3 | DEFAULT_IDLE_PERIOD_TIME_LIMIT); |
8094 | 3 | Preferences::AddUintVarCache(&sQuiescentFramesBeforeIdlePeriod, |
8095 | 3 | "layout.idle_period.required_quiescent_frames", |
8096 | 3 | DEFAULT_QUIESCENT_FRAMES); |
8097 | 3 | |
8098 | 3 | nsComputedDOMStyle::RegisterPrefChangeCallbacks(); |
8099 | 3 | } |
8100 | | |
8101 | | /* static */ |
8102 | | void |
8103 | | nsLayoutUtils::Shutdown() |
8104 | 0 | { |
8105 | 0 | if (sContentMap) { |
8106 | 0 | delete sContentMap; |
8107 | 0 | sContentMap = nullptr; |
8108 | 0 | } |
8109 | 0 |
|
8110 | 0 | nsComputedDOMStyle::UnregisterPrefChangeCallbacks(); |
8111 | 0 |
|
8112 | 0 | // so the cached initial quotes array doesn't appear to be a leak |
8113 | 0 | nsStyleList::Shutdown(); |
8114 | 0 | } |
8115 | | |
8116 | | /* static */ |
8117 | | void |
8118 | | nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext, |
8119 | | imgIRequest* aRequest, |
8120 | | bool* aRequestRegistered) |
8121 | 0 | { |
8122 | 0 | if (!aPresContext) { |
8123 | 0 | return; |
8124 | 0 | } |
8125 | 0 | |
8126 | 0 | if (aRequestRegistered && *aRequestRegistered) { |
8127 | 0 | // Our request is already registered with the refresh driver, so |
8128 | 0 | // no need to register it again. |
8129 | 0 | return; |
8130 | 0 | } |
8131 | 0 | |
8132 | 0 | if (aRequest) { |
8133 | 0 | if (!aPresContext->RefreshDriver()->AddImageRequest(aRequest)) { |
8134 | 0 | NS_WARNING("Unable to add image request"); |
8135 | 0 | return; |
8136 | 0 | } |
8137 | 0 |
|
8138 | 0 | if (aRequestRegistered) { |
8139 | 0 | *aRequestRegistered = true; |
8140 | 0 | } |
8141 | 0 | } |
8142 | 0 | } |
8143 | | |
8144 | | /* static */ |
8145 | | void |
8146 | | nsLayoutUtils::RegisterImageRequestIfAnimated(nsPresContext* aPresContext, |
8147 | | imgIRequest* aRequest, |
8148 | | bool* aRequestRegistered) |
8149 | 0 | { |
8150 | 0 | if (!aPresContext) { |
8151 | 0 | return; |
8152 | 0 | } |
8153 | 0 | |
8154 | 0 | if (aRequestRegistered && *aRequestRegistered) { |
8155 | 0 | // Our request is already registered with the refresh driver, so |
8156 | 0 | // no need to register it again. |
8157 | 0 | return; |
8158 | 0 | } |
8159 | 0 | |
8160 | 0 | if (aRequest) { |
8161 | 0 | nsCOMPtr<imgIContainer> image; |
8162 | 0 | if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) { |
8163 | 0 | // Check to verify that the image is animated. If so, then add it to the |
8164 | 0 | // list of images tracked by the refresh driver. |
8165 | 0 | bool isAnimated = false; |
8166 | 0 | nsresult rv = image->GetAnimated(&isAnimated); |
8167 | 0 | if (NS_SUCCEEDED(rv) && isAnimated) { |
8168 | 0 | if (!aPresContext->RefreshDriver()->AddImageRequest(aRequest)) { |
8169 | 0 | NS_WARNING("Unable to add image request"); |
8170 | 0 | return; |
8171 | 0 | } |
8172 | 0 |
|
8173 | 0 | if (aRequestRegistered) { |
8174 | 0 | *aRequestRegistered = true; |
8175 | 0 | } |
8176 | 0 | } |
8177 | 0 | } |
8178 | 0 | } |
8179 | 0 | } |
8180 | | |
8181 | | /* static */ |
8182 | | void |
8183 | | nsLayoutUtils::DeregisterImageRequest(nsPresContext* aPresContext, |
8184 | | imgIRequest* aRequest, |
8185 | | bool* aRequestRegistered) |
8186 | 0 | { |
8187 | 0 | if (!aPresContext) { |
8188 | 0 | return; |
8189 | 0 | } |
8190 | 0 | |
8191 | 0 | // Deregister our imgIRequest with the refresh driver to |
8192 | 0 | // complete tear-down, but only if it has been registered |
8193 | 0 | if (aRequestRegistered && !*aRequestRegistered) { |
8194 | 0 | return; |
8195 | 0 | } |
8196 | 0 | |
8197 | 0 | if (aRequest) { |
8198 | 0 | nsCOMPtr<imgIContainer> image; |
8199 | 0 | if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) { |
8200 | 0 | aPresContext->RefreshDriver()->RemoveImageRequest(aRequest); |
8201 | 0 |
|
8202 | 0 | if (aRequestRegistered) { |
8203 | 0 | *aRequestRegistered = false; |
8204 | 0 | } |
8205 | 0 | } |
8206 | 0 | } |
8207 | 0 | } |
8208 | | |
8209 | | /* static */ |
8210 | | void |
8211 | | nsLayoutUtils::PostRestyleEvent(Element* aElement, |
8212 | | nsRestyleHint aRestyleHint, |
8213 | | nsChangeHint aMinChangeHint) |
8214 | 0 | { |
8215 | 0 | nsIDocument* doc = aElement->GetComposedDoc(); |
8216 | 0 | if (doc) { |
8217 | 0 | RefPtr<nsPresContext> presContext = doc->GetPresContext(); |
8218 | 0 | if (presContext) { |
8219 | 0 | presContext->RestyleManager()->PostRestyleEvent( |
8220 | 0 | aElement, aRestyleHint, aMinChangeHint); |
8221 | 0 | } |
8222 | 0 | } |
8223 | 0 | } |
8224 | | |
8225 | | nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, |
8226 | | nsAtom* aAttrName, |
8227 | | const nsAString& aValue) |
8228 | | : mozilla::Runnable("nsSetAttrRunnable") |
8229 | | , mElement(aElement) |
8230 | | , mAttrName(aAttrName) |
8231 | | , mValue(aValue) |
8232 | 0 | { |
8233 | 0 | NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash"); |
8234 | 0 | } |
8235 | | |
8236 | | nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, |
8237 | | nsAtom* aAttrName, |
8238 | | int32_t aValue) |
8239 | | : mozilla::Runnable("nsSetAttrRunnable") |
8240 | | , mElement(aElement) |
8241 | | , mAttrName(aAttrName) |
8242 | 0 | { |
8243 | 0 | NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash"); |
8244 | 0 | mValue.AppendInt(aValue); |
8245 | 0 | } |
8246 | | |
8247 | | NS_IMETHODIMP |
8248 | | nsSetAttrRunnable::Run() |
8249 | 0 | { |
8250 | 0 | return mElement->SetAttr(kNameSpaceID_None, mAttrName, mValue, true); |
8251 | 0 | } |
8252 | | |
8253 | | nsUnsetAttrRunnable::nsUnsetAttrRunnable(Element* aElement, |
8254 | | nsAtom* aAttrName) |
8255 | | : mozilla::Runnable("nsUnsetAttrRunnable") |
8256 | | , mElement(aElement) |
8257 | | , mAttrName(aAttrName) |
8258 | 0 | { |
8259 | 0 | NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash"); |
8260 | 0 | } |
8261 | | |
8262 | | NS_IMETHODIMP |
8263 | | nsUnsetAttrRunnable::Run() |
8264 | 0 | { |
8265 | 0 | return mElement->UnsetAttr(kNameSpaceID_None, mAttrName, true); |
8266 | 0 | } |
8267 | | |
8268 | | /** |
8269 | | * Compute the minimum font size inside of a container with the given |
8270 | | * width, such that **when the user zooms the container to fill the full |
8271 | | * width of the device**, the fonts satisfy our minima. |
8272 | | */ |
8273 | | static nscoord |
8274 | | MinimumFontSizeFor(nsPresContext* aPresContext, WritingMode aWritingMode, |
8275 | | nscoord aContainerISize) |
8276 | 0 | { |
8277 | 0 | nsIPresShell* presShell = aPresContext->PresShell(); |
8278 | 0 |
|
8279 | 0 | uint32_t emPerLine = presShell->FontSizeInflationEmPerLine(); |
8280 | 0 | uint32_t minTwips = presShell->FontSizeInflationMinTwips(); |
8281 | 0 | if (emPerLine == 0 && minTwips == 0) { |
8282 | 0 | return 0; |
8283 | 0 | } |
8284 | 0 | |
8285 | 0 | // Clamp the container width to the device dimensions |
8286 | 0 | nscoord iFrameISize = aWritingMode.IsVertical() |
8287 | 0 | ? aPresContext->GetVisibleArea().height |
8288 | 0 | : aPresContext->GetVisibleArea().width; |
8289 | 0 | nscoord effectiveContainerISize = std::min(iFrameISize, aContainerISize); |
8290 | 0 |
|
8291 | 0 | nscoord byLine = 0, byInch = 0; |
8292 | 0 | if (emPerLine != 0) { |
8293 | 0 | byLine = effectiveContainerISize / emPerLine; |
8294 | 0 | } |
8295 | 0 | if (minTwips != 0) { |
8296 | 0 | // REVIEW: Is this giving us app units and sizes *not* counting |
8297 | 0 | // viewport scaling? |
8298 | 0 | gfxSize screenSize = aPresContext->ScreenSizeInchesForFontInflation(); |
8299 | 0 | float deviceISizeInches = aWritingMode.IsVertical() |
8300 | 0 | ? screenSize.height : screenSize.width; |
8301 | 0 | byInch = NSToCoordRound(effectiveContainerISize / |
8302 | 0 | (deviceISizeInches * 1440 / |
8303 | 0 | minTwips )); |
8304 | 0 | } |
8305 | 0 | return std::max(byLine, byInch); |
8306 | 0 | } |
8307 | | |
8308 | | /* static */ float |
8309 | | nsLayoutUtils::FontSizeInflationInner(const nsIFrame *aFrame, |
8310 | | nscoord aMinFontSize) |
8311 | 0 | { |
8312 | 0 | // Note that line heights should be inflated by the same ratio as the |
8313 | 0 | // font size of the same text; thus we operate only on the font size |
8314 | 0 | // even when we're scaling a line height. |
8315 | 0 | nscoord styleFontSize = aFrame->StyleFont()->mFont.size; |
8316 | 0 | if (styleFontSize <= 0) { |
8317 | 0 | // Never scale zero font size. |
8318 | 0 | return 1.0; |
8319 | 0 | } |
8320 | 0 | |
8321 | 0 | if (aMinFontSize <= 0) { |
8322 | 0 | // No need to scale. |
8323 | 0 | return 1.0; |
8324 | 0 | } |
8325 | 0 | |
8326 | 0 | // If between this current frame and its font inflation container there is a |
8327 | 0 | // non-inline element with fixed width or height, then we should not inflate |
8328 | 0 | // fonts for this frame. |
8329 | 0 | for (const nsIFrame* f = aFrame; |
8330 | 0 | f && !f->IsContainerForFontSizeInflation(); |
8331 | 0 | f = f->GetParent()) { |
8332 | 0 | nsIContent* content = f->GetContent(); |
8333 | 0 | LayoutFrameType fType = f->Type(); |
8334 | 0 | nsIFrame* parent = f->GetParent(); |
8335 | 0 | // Also, if there is more than one frame corresponding to a single |
8336 | 0 | // content node, we want the outermost one. |
8337 | 0 | if (!(parent && parent->GetContent() == content) && |
8338 | 0 | // ignore width/height on inlines since they don't apply |
8339 | 0 | fType != LayoutFrameType::Inline && |
8340 | 0 | // ignore width on radios and checkboxes since we enlarge them and |
8341 | 0 | // they have width/height in ua.css |
8342 | 0 | fType != LayoutFrameType::CheckboxRadio) { |
8343 | 0 | // ruby annotations should have the same inflation as its |
8344 | 0 | // grandparent, which is the ruby frame contains the annotation. |
8345 | 0 | if (fType == LayoutFrameType::RubyText) { |
8346 | 0 | MOZ_ASSERT(parent && parent->IsRubyTextContainerFrame()); |
8347 | 0 | nsIFrame* grandparent = parent->GetParent(); |
8348 | 0 | MOZ_ASSERT(grandparent && grandparent->IsRubyFrame()); |
8349 | 0 | return FontSizeInflationFor(grandparent); |
8350 | 0 | } |
8351 | 0 | nsStyleCoord stylePosWidth = f->StylePosition()->mWidth; |
8352 | 0 | nsStyleCoord stylePosHeight = f->StylePosition()->mHeight; |
8353 | 0 | if (stylePosWidth.GetUnit() != eStyleUnit_Auto || |
8354 | 0 | stylePosHeight.GetUnit() != eStyleUnit_Auto) { |
8355 | 0 |
|
8356 | 0 | return 1.0; |
8357 | 0 | } |
8358 | 0 | } |
8359 | 0 | } |
8360 | 0 |
|
8361 | 0 | int32_t interceptParam = nsLayoutUtils::FontSizeInflationMappingIntercept(); |
8362 | 0 | float maxRatio = (float)nsLayoutUtils::FontSizeInflationMaxRatio() / 100.0f; |
8363 | 0 |
|
8364 | 0 | float ratio = float(styleFontSize) / float(aMinFontSize); |
8365 | 0 | float inflationRatio; |
8366 | 0 |
|
8367 | 0 | // Given a minimum inflated font size m, a specified font size s, we want to |
8368 | 0 | // find the inflated font size i and then return the ratio of i to s (i/s). |
8369 | 0 | if (interceptParam >= 0) { |
8370 | 0 | // Since the mapping intercept parameter P is greater than zero, we use it |
8371 | 0 | // to determine the point where our mapping function intersects the i=s |
8372 | 0 | // line. This means that we have an equation of the form: |
8373 | 0 | // |
8374 | 0 | // i = m + s·(P/2)/(1 + P/2), if s <= (1 + P/2)·m |
8375 | 0 | // i = s, if s >= (1 + P/2)·m |
8376 | 0 |
|
8377 | 0 | float intercept = 1 + float(interceptParam)/2.0f; |
8378 | 0 | if (ratio >= intercept) { |
8379 | 0 | // If we're already at 1+P/2 or more times the minimum, don't scale. |
8380 | 0 | return 1.0; |
8381 | 0 | } |
8382 | 0 | |
8383 | 0 | // The point (intercept, intercept) is where the part of the i vs. s graph |
8384 | 0 | // that's not slope 1 meets the i=s line. (This part of the |
8385 | 0 | // graph is a line from (0, m), to that point). We calculate the |
8386 | 0 | // intersection point to be ((1+P/2)m, (1+P/2)m), where P is the |
8387 | 0 | // intercept parameter above. We then need to return i/s. |
8388 | 0 | inflationRatio = (1.0f + (ratio * (intercept - 1) / intercept)) / ratio; |
8389 | 0 | } else { |
8390 | 0 | // This is the case where P is negative. We essentially want to implement |
8391 | 0 | // the case for P=infinity here, so we make i = s + m, which means that |
8392 | 0 | // i/s = s/s + m/s = 1 + 1/ratio |
8393 | 0 | inflationRatio = 1 + 1.0f / ratio; |
8394 | 0 | } |
8395 | 0 |
|
8396 | 0 | if (maxRatio > 1.0 && inflationRatio > maxRatio) { |
8397 | 0 | return maxRatio; |
8398 | 0 | } else { |
8399 | 0 | return inflationRatio; |
8400 | 0 | } |
8401 | 0 | } |
8402 | | |
8403 | | static bool |
8404 | | ShouldInflateFontsForContainer(const nsIFrame *aFrame) |
8405 | 0 | { |
8406 | 0 | // We only want to inflate fonts for text that is in a place |
8407 | 0 | // with room to expand. The question is what the best heuristic for |
8408 | 0 | // that is... |
8409 | 0 | // For now, we're going to use NS_FRAME_IN_CONSTRAINED_BSIZE, which |
8410 | 0 | // indicates whether the frame is inside something with a constrained |
8411 | 0 | // block-size (propagating down the tree), but the propagation stops when |
8412 | 0 | // we hit overflow-y [or -x, for vertical mode]: scroll or auto. |
8413 | 0 | const nsStyleText* styleText = aFrame->StyleText(); |
8414 | 0 |
|
8415 | 0 | return styleText->mTextSizeAdjust != NS_STYLE_TEXT_SIZE_ADJUST_NONE && |
8416 | 0 | !(aFrame->GetStateBits() & NS_FRAME_IN_CONSTRAINED_BSIZE) && |
8417 | 0 | // We also want to disable font inflation for containers that have |
8418 | 0 | // preformatted text. |
8419 | 0 | // MathML cells need special treatment. See bug 1002526 comment 56. |
8420 | 0 | (styleText->WhiteSpaceCanWrap(aFrame) || |
8421 | 0 | aFrame->IsFrameOfType(nsIFrame::eMathML)); |
8422 | 0 | } |
8423 | | |
8424 | | nscoord |
8425 | | nsLayoutUtils::InflationMinFontSizeFor(const nsIFrame *aFrame) |
8426 | 0 | { |
8427 | 0 | nsPresContext *presContext = aFrame->PresContext(); |
8428 | 0 | if (!FontSizeInflationEnabled(presContext) || |
8429 | 0 | presContext->mInflationDisabledForShrinkWrap) { |
8430 | 0 | return 0; |
8431 | 0 | } |
8432 | 0 | |
8433 | 0 | for (const nsIFrame *f = aFrame; f; f = f->GetParent()) { |
8434 | 0 | if (f->IsContainerForFontSizeInflation()) { |
8435 | 0 | if (!ShouldInflateFontsForContainer(f)) { |
8436 | 0 | return 0; |
8437 | 0 | } |
8438 | 0 | |
8439 | 0 | nsFontInflationData *data = |
8440 | 0 | nsFontInflationData::FindFontInflationDataFor(aFrame); |
8441 | 0 | // FIXME: The need to null-check here is sort of a bug, and might |
8442 | 0 | // lead to incorrect results. |
8443 | 0 | if (!data || !data->InflationEnabled()) { |
8444 | 0 | return 0; |
8445 | 0 | } |
8446 | 0 | |
8447 | 0 | return MinimumFontSizeFor(aFrame->PresContext(), |
8448 | 0 | aFrame->GetWritingMode(), |
8449 | 0 | data->EffectiveISize()); |
8450 | 0 | } |
8451 | 0 | } |
8452 | 0 |
|
8453 | 0 | MOZ_ASSERT(false, "root should always be container"); |
8454 | 0 |
|
8455 | 0 | return 0; |
8456 | 0 | } |
8457 | | |
8458 | | float |
8459 | | nsLayoutUtils::FontSizeInflationFor(const nsIFrame *aFrame) |
8460 | 0 | { |
8461 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) { |
8462 | 0 | const nsIFrame* container = aFrame; |
8463 | 0 | while (!container->IsSVGTextFrame()) { |
8464 | 0 | container = container->GetParent(); |
8465 | 0 | } |
8466 | 0 | NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame"); |
8467 | 0 | return |
8468 | 0 | static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor(); |
8469 | 0 | } |
8470 | 0 |
|
8471 | 0 | if (!FontSizeInflationEnabled(aFrame->PresContext())) { |
8472 | 0 | return 1.0f; |
8473 | 0 | } |
8474 | 0 | |
8475 | 0 | return FontSizeInflationInner(aFrame, InflationMinFontSizeFor(aFrame)); |
8476 | 0 | } |
8477 | | |
8478 | | /* static */ bool |
8479 | | nsLayoutUtils::FontSizeInflationEnabled(nsPresContext *aPresContext) |
8480 | 0 | { |
8481 | 0 | nsIPresShell* presShell = aPresContext->GetPresShell(); |
8482 | 0 |
|
8483 | 0 | if (!presShell) { |
8484 | 0 | return false; |
8485 | 0 | } |
8486 | 0 | |
8487 | 0 | return presShell->FontSizeInflationEnabled(); |
8488 | 0 | } |
8489 | | |
8490 | | /* static */ nsRect |
8491 | | nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame, |
8492 | | const nsSize& aFrameSize) |
8493 | 0 | { |
8494 | 0 | nsCSSShadowArray* boxShadows = aFrame->StyleEffects()->mBoxShadow; |
8495 | 0 | if (!boxShadows) { |
8496 | 0 | return nsRect(); |
8497 | 0 | } |
8498 | 0 | |
8499 | 0 | nsRect inputRect(nsPoint(0, 0), aFrameSize); |
8500 | 0 |
|
8501 | 0 | // According to the CSS spec, box-shadow should be based on the border box. |
8502 | 0 | // However, that looks broken when the background extends outside the border |
8503 | 0 | // box, as can be the case with native theming. To fix that we expand the |
8504 | 0 | // area that we shadow to include the bounds of any native theme drawing. |
8505 | 0 | const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay(); |
8506 | 0 | nsITheme::Transparency transparency; |
8507 | 0 | if (aFrame->IsThemed(styleDisplay, &transparency)) { |
8508 | 0 | // For opaque (rectangular) theme widgets we can take the generic |
8509 | 0 | // border-box path with border-radius disabled. |
8510 | 0 | if (transparency != nsITheme::eOpaque) { |
8511 | 0 | nsPresContext *presContext = aFrame->PresContext(); |
8512 | 0 | presContext->GetTheme()-> |
8513 | 0 | GetWidgetOverflow(presContext->DeviceContext(), aFrame, |
8514 | 0 | styleDisplay->mAppearance, &inputRect); |
8515 | 0 | } |
8516 | 0 | } |
8517 | 0 |
|
8518 | 0 | nsRect shadows; |
8519 | 0 | int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel(); |
8520 | 0 | for (uint32_t i = 0; i < boxShadows->Length(); ++i) { |
8521 | 0 | nsRect tmpRect = inputRect; |
8522 | 0 | nsCSSShadowItem* shadow = boxShadows->ShadowAt(i); |
8523 | 0 |
|
8524 | 0 | // inset shadows are never painted outside the frame |
8525 | 0 | if (shadow->mInset) |
8526 | 0 | continue; |
8527 | 0 | |
8528 | 0 | tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset)); |
8529 | 0 | tmpRect.Inflate(shadow->mSpread); |
8530 | 0 | tmpRect.Inflate( |
8531 | 0 | nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D)); |
8532 | 0 | shadows.UnionRect(shadows, tmpRect); |
8533 | 0 | } |
8534 | 0 | return shadows; |
8535 | 0 | } |
8536 | | |
8537 | | /* static */ bool |
8538 | | nsLayoutUtils::GetContentViewerSize(nsPresContext* aPresContext, |
8539 | | LayoutDeviceIntSize& aOutSize) |
8540 | 0 | { |
8541 | 0 | nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell(); |
8542 | 0 | if (!docShell) { |
8543 | 0 | return false; |
8544 | 0 | } |
8545 | 0 | |
8546 | 0 | nsCOMPtr<nsIContentViewer> cv; |
8547 | 0 | docShell->GetContentViewer(getter_AddRefs(cv)); |
8548 | 0 | if (!cv) { |
8549 | 0 | return false; |
8550 | 0 | } |
8551 | 0 | |
8552 | 0 | nsIntRect bounds; |
8553 | 0 | cv->GetBounds(bounds); |
8554 | 0 | aOutSize = LayoutDeviceIntRect::FromUnknownRect(bounds).Size(); |
8555 | 0 | return true; |
8556 | 0 | } |
8557 | | |
8558 | | static bool |
8559 | | UpdateCompositionBoundsForRCDRSF(ParentLayerRect& aCompBounds, |
8560 | | nsPresContext* aPresContext, |
8561 | | bool aScaleContentViewerSize) |
8562 | 0 | { |
8563 | 0 | nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame(); |
8564 | 0 | if (!rootFrame) { |
8565 | 0 | return false; |
8566 | 0 | } |
8567 | 0 | |
8568 | | #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT) |
8569 | | nsIWidget* widget = rootFrame->GetNearestWidget(); |
8570 | | #else |
8571 | 0 | nsView* view = rootFrame->GetView(); |
8572 | 0 | nsIWidget* widget = view ? view->GetWidget() : nullptr; |
8573 | 0 | #endif |
8574 | 0 |
|
8575 | 0 | if (widget) { |
8576 | 0 | LayoutDeviceIntRect widgetBounds = widget->GetBounds(); |
8577 | 0 | widgetBounds.MoveTo(0, 0); |
8578 | 0 | aCompBounds = ParentLayerRect( |
8579 | 0 | ViewAs<ParentLayerPixel>( |
8580 | 0 | widgetBounds, |
8581 | 0 | PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF)); |
8582 | 0 | return true; |
8583 | 0 | } |
8584 | 0 | |
8585 | 0 | LayoutDeviceIntSize contentSize; |
8586 | 0 | if (nsLayoutUtils::GetContentViewerSize(aPresContext, contentSize)) { |
8587 | 0 | LayoutDeviceToParentLayerScale scale; |
8588 | 0 | if (aScaleContentViewerSize && aPresContext->GetParentPresContext()) { |
8589 | 0 | scale = LayoutDeviceToParentLayerScale( |
8590 | 0 | aPresContext->GetParentPresContext()->PresShell()->GetCumulativeResolution()); |
8591 | 0 | } |
8592 | 0 | aCompBounds.SizeTo(contentSize * scale); |
8593 | 0 | return true; |
8594 | 0 | } |
8595 | 0 |
|
8596 | 0 | return false; |
8597 | 0 | } |
8598 | | |
8599 | | /* static */ nsMargin |
8600 | | nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(nsIFrame* aScrollFrame) |
8601 | 0 | { |
8602 | 0 | if (!aScrollFrame || !aScrollFrame->GetScrollTargetFrame()) { |
8603 | 0 | return nsMargin(); |
8604 | 0 | } |
8605 | 0 | nsPresContext* presContext = aScrollFrame->PresContext(); |
8606 | 0 | nsIPresShell* presShell = presContext->GetPresShell(); |
8607 | 0 | if (!presShell) { |
8608 | 0 | return nsMargin(); |
8609 | 0 | } |
8610 | 0 | bool isRootScrollFrame = aScrollFrame == presShell->GetRootScrollFrame(); |
8611 | 0 | bool isRootContentDocRootScrollFrame = isRootScrollFrame |
8612 | 0 | && presContext->IsRootContentDocument(); |
8613 | 0 | if (!isRootContentDocRootScrollFrame) { |
8614 | 0 | return nsMargin(); |
8615 | 0 | } |
8616 | 0 | if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars)) { |
8617 | 0 | return nsMargin(); |
8618 | 0 | } |
8619 | 0 | nsIScrollableFrame* scrollableFrame = aScrollFrame->GetScrollTargetFrame(); |
8620 | 0 | if (!scrollableFrame) { |
8621 | 0 | return nsMargin(); |
8622 | 0 | } |
8623 | 0 | return scrollableFrame->GetActualScrollbarSizes(); |
8624 | 0 | } |
8625 | | |
8626 | | /* static */ nsSize |
8627 | | nsLayoutUtils::CalculateCompositionSizeForFrame(nsIFrame* aFrame, bool aSubtractScrollbars) |
8628 | 0 | { |
8629 | 0 | // If we have a scrollable frame, restrict the composition bounds to its |
8630 | 0 | // scroll port. The scroll port excludes the frame borders and the scroll |
8631 | 0 | // bars, which we don't want to be part of the composition bounds. |
8632 | 0 | nsIScrollableFrame* scrollableFrame = aFrame->GetScrollTargetFrame(); |
8633 | 0 | nsRect rect = scrollableFrame ? scrollableFrame->GetScrollPortRect() : aFrame->GetRect(); |
8634 | 0 | nsSize size = rect.Size(); |
8635 | 0 |
|
8636 | 0 | nsPresContext* presContext = aFrame->PresContext(); |
8637 | 0 | nsIPresShell* presShell = presContext->PresShell(); |
8638 | 0 |
|
8639 | 0 | bool isRootContentDocRootScrollFrame = presContext->IsRootContentDocument() |
8640 | 0 | && aFrame == presShell->GetRootScrollFrame(); |
8641 | 0 | if (isRootContentDocRootScrollFrame) { |
8642 | 0 | ParentLayerRect compBounds; |
8643 | 0 | if (UpdateCompositionBoundsForRCDRSF(compBounds, presContext, false)) { |
8644 | 0 | int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); |
8645 | 0 | size = nsSize(compBounds.width * auPerDevPixel, compBounds.height * auPerDevPixel); |
8646 | 0 | } |
8647 | 0 | } |
8648 | 0 |
|
8649 | 0 | if (aSubtractScrollbars) { |
8650 | 0 | nsMargin margins = ScrollbarAreaToExcludeFromCompositionBoundsFor(aFrame); |
8651 | 0 | size.width -= margins.LeftRight(); |
8652 | 0 | size.height -= margins.TopBottom(); |
8653 | 0 | } |
8654 | 0 |
|
8655 | 0 | return size; |
8656 | 0 | } |
8657 | | |
8658 | | /* static */ CSSSize |
8659 | | nsLayoutUtils::CalculateRootCompositionSize(nsIFrame* aFrame, |
8660 | | bool aIsRootContentDocRootScrollFrame, |
8661 | | const FrameMetrics& aMetrics) |
8662 | 0 | { |
8663 | 0 |
|
8664 | 0 | if (aIsRootContentDocRootScrollFrame) { |
8665 | 0 | return ViewAs<LayerPixel>(aMetrics.GetCompositionBounds().Size(), |
8666 | 0 | PixelCastJustification::ParentLayerToLayerForRootComposition) |
8667 | 0 | * LayerToScreenScale(1.0f) |
8668 | 0 | / aMetrics.DisplayportPixelsPerCSSPixel(); |
8669 | 0 | } |
8670 | 0 | nsPresContext* presContext = aFrame->PresContext(); |
8671 | 0 | ScreenSize rootCompositionSize; |
8672 | 0 | nsPresContext* rootPresContext = |
8673 | 0 | presContext->GetToplevelContentDocumentPresContext(); |
8674 | 0 | if (!rootPresContext) { |
8675 | 0 | rootPresContext = presContext->GetRootPresContext(); |
8676 | 0 | } |
8677 | 0 | nsIPresShell* rootPresShell = nullptr; |
8678 | 0 | if (rootPresContext) { |
8679 | 0 | rootPresShell = rootPresContext->PresShell(); |
8680 | 0 | if (nsIFrame* rootFrame = rootPresShell->GetRootFrame()) { |
8681 | 0 | LayoutDeviceToLayerScale2D cumulativeResolution( |
8682 | 0 | rootPresShell->GetCumulativeResolution() |
8683 | 0 | * nsLayoutUtils::GetTransformToAncestorScale(rootFrame)); |
8684 | 0 | ParentLayerRect compBounds; |
8685 | 0 | if (UpdateCompositionBoundsForRCDRSF(compBounds, rootPresContext, true)) { |
8686 | 0 | rootCompositionSize = ViewAs<ScreenPixel>(compBounds.Size(), |
8687 | 0 | PixelCastJustification::ScreenIsParentLayerForRoot); |
8688 | 0 | } else { |
8689 | 0 | int32_t rootAUPerDevPixel = rootPresContext->AppUnitsPerDevPixel(); |
8690 | 0 | LayerSize frameSize = |
8691 | 0 | (LayoutDeviceRect::FromAppUnits(rootFrame->GetRect(), rootAUPerDevPixel) |
8692 | 0 | * cumulativeResolution).Size(); |
8693 | 0 | rootCompositionSize = frameSize * LayerToScreenScale(1.0f); |
8694 | 0 | } |
8695 | 0 | } |
8696 | 0 | } else { |
8697 | 0 | nsIWidget* widget = aFrame->GetNearestWidget(); |
8698 | 0 | LayoutDeviceIntRect widgetBounds = widget->GetBounds(); |
8699 | 0 | rootCompositionSize = ScreenSize( |
8700 | 0 | ViewAs<ScreenPixel>(widgetBounds.Size(), |
8701 | 0 | PixelCastJustification::LayoutDeviceIsScreenForBounds)); |
8702 | 0 | } |
8703 | 0 |
|
8704 | 0 | // Adjust composition size for the size of scroll bars. |
8705 | 0 | nsIFrame* rootRootScrollFrame = rootPresShell ? rootPresShell->GetRootScrollFrame() : nullptr; |
8706 | 0 | nsMargin scrollbarMargins = ScrollbarAreaToExcludeFromCompositionBoundsFor(rootRootScrollFrame); |
8707 | 0 | LayoutDeviceMargin margins = LayoutDeviceMargin::FromAppUnits(scrollbarMargins, |
8708 | 0 | rootPresContext->AppUnitsPerDevPixel()); |
8709 | 0 | // Scrollbars are not subject to resolution scaling, so LD pixels = layer pixels for them. |
8710 | 0 | rootCompositionSize.width -= margins.LeftRight(); |
8711 | 0 | rootCompositionSize.height -= margins.TopBottom(); |
8712 | 0 |
|
8713 | 0 | return rootCompositionSize / aMetrics.DisplayportPixelsPerCSSPixel(); |
8714 | 0 | } |
8715 | | |
8716 | | /* static */ nsRect |
8717 | | nsLayoutUtils::CalculateScrollableRectForFrame(nsIScrollableFrame* aScrollableFrame, nsIFrame* aRootFrame) |
8718 | 0 | { |
8719 | 0 | nsRect contentBounds; |
8720 | 0 | if (aScrollableFrame) { |
8721 | 0 | contentBounds = aScrollableFrame->GetScrollRange(); |
8722 | 0 |
|
8723 | 0 | nsPoint scrollPosition = aScrollableFrame->GetScrollPosition(); |
8724 | 0 | if (aScrollableFrame->GetScrollStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) { |
8725 | 0 | contentBounds.y = scrollPosition.y; |
8726 | 0 | contentBounds.height = 0; |
8727 | 0 | } |
8728 | 0 | if (aScrollableFrame->GetScrollStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) { |
8729 | 0 | contentBounds.x = scrollPosition.x; |
8730 | 0 | contentBounds.width = 0; |
8731 | 0 | } |
8732 | 0 |
|
8733 | 0 | contentBounds.width += aScrollableFrame->GetScrollPortRect().width; |
8734 | 0 | contentBounds.height += aScrollableFrame->GetScrollPortRect().height; |
8735 | 0 | } else { |
8736 | 0 | contentBounds = aRootFrame->GetRect(); |
8737 | 0 | } |
8738 | 0 | return contentBounds; |
8739 | 0 | } |
8740 | | |
8741 | | /* static */ nsRect |
8742 | | nsLayoutUtils::CalculateExpandedScrollableRect(nsIFrame* aFrame) |
8743 | 0 | { |
8744 | 0 | nsRect scrollableRect = |
8745 | 0 | CalculateScrollableRectForFrame(aFrame->GetScrollTargetFrame(), |
8746 | 0 | aFrame->PresShell()->GetRootFrame()); |
8747 | 0 | nsSize compSize = CalculateCompositionSizeForFrame(aFrame); |
8748 | 0 |
|
8749 | 0 | if (aFrame == aFrame->PresShell()->GetRootScrollFrame()) { |
8750 | 0 | // the composition size for the root scroll frame does not include the |
8751 | 0 | // local resolution, so we adjust. |
8752 | 0 | float res = aFrame->PresShell()->GetResolution(); |
8753 | 0 | compSize.width = NSToCoordRound(compSize.width / res); |
8754 | 0 | compSize.height = NSToCoordRound(compSize.height / res); |
8755 | 0 | } |
8756 | 0 |
|
8757 | 0 | if (scrollableRect.width < compSize.width) { |
8758 | 0 | scrollableRect.x = std::max(0, |
8759 | 0 | scrollableRect.x - (compSize.width - scrollableRect.width)); |
8760 | 0 | scrollableRect.width = compSize.width; |
8761 | 0 | } |
8762 | 0 |
|
8763 | 0 | if (scrollableRect.height < compSize.height) { |
8764 | 0 | scrollableRect.y = std::max(0, |
8765 | 0 | scrollableRect.y - (compSize.height - scrollableRect.height)); |
8766 | 0 | scrollableRect.height = compSize.height; |
8767 | 0 | } |
8768 | 0 | return scrollableRect; |
8769 | 0 | } |
8770 | | |
8771 | | /* static */ void |
8772 | | nsLayoutUtils::DoLogTestDataForPaint(LayerManager* aManager, |
8773 | | ViewID aScrollId, |
8774 | | const std::string& aKey, |
8775 | | const std::string& aValue) |
8776 | 0 | { |
8777 | 0 | MOZ_ASSERT(nsLayoutUtils::IsAPZTestLoggingEnabled(), "don't call me"); |
8778 | 0 | if (ClientLayerManager* mgr = aManager->AsClientLayerManager()) { |
8779 | 0 | mgr->LogTestDataForCurrentPaint(aScrollId, aKey, aValue); |
8780 | 0 | } else if (WebRenderLayerManager* wrlm = aManager->AsWebRenderLayerManager()) { |
8781 | 0 | wrlm->LogTestDataForCurrentPaint(aScrollId, aKey, aValue); |
8782 | 0 | } |
8783 | 0 | } |
8784 | | |
8785 | | /* static */ bool |
8786 | | nsLayoutUtils::IsAPZTestLoggingEnabled() |
8787 | 0 | { |
8788 | 0 | return gfxPrefs::APZTestLoggingEnabled(); |
8789 | 0 | } |
8790 | | |
8791 | | //////////////////////////////////////// |
8792 | | // SurfaceFromElementResult |
8793 | | |
8794 | | nsLayoutUtils::SurfaceFromElementResult::SurfaceFromElementResult() |
8795 | | // Use safe default values here |
8796 | | : mIsWriteOnly(true) |
8797 | | , mIsStillLoading(false) |
8798 | | , mHasSize(false) |
8799 | | , mCORSUsed(false) |
8800 | | , mAlphaType(gfxAlphaType::Opaque) |
8801 | 0 | { |
8802 | 0 | } |
8803 | | |
8804 | | const RefPtr<mozilla::gfx::SourceSurface>& |
8805 | | nsLayoutUtils::SurfaceFromElementResult::GetSourceSurface() |
8806 | 0 | { |
8807 | 0 | if (!mSourceSurface && mLayersImage) { |
8808 | 0 | mSourceSurface = mLayersImage->GetAsSourceSurface(); |
8809 | 0 | } |
8810 | 0 |
|
8811 | 0 | return mSourceSurface; |
8812 | 0 | } |
8813 | | |
8814 | | //////////////////////////////////////// |
8815 | | |
8816 | | bool |
8817 | | nsLayoutUtils::IsNonWrapperBlock(nsIFrame* aFrame) |
8818 | 0 | { |
8819 | 0 | return GetAsBlock(aFrame) && !aFrame->IsBlockWrapper(); |
8820 | 0 | } |
8821 | | |
8822 | | bool |
8823 | | nsLayoutUtils::NeedsPrintPreviewBackground(nsPresContext* aPresContext) |
8824 | 0 | { |
8825 | 0 | return aPresContext->IsRootPaginatedDocument() && |
8826 | 0 | (aPresContext->Type() == nsPresContext::eContext_PrintPreview || |
8827 | 0 | aPresContext->Type() == nsPresContext::eContext_PageLayout); |
8828 | 0 | } |
8829 | | |
8830 | | AutoMaybeDisableFontInflation::AutoMaybeDisableFontInflation(nsIFrame *aFrame) |
8831 | 0 | { |
8832 | 0 | // FIXME: Now that inflation calculations are based on the flow |
8833 | 0 | // root's NCA's (nearest common ancestor of its inflatable |
8834 | 0 | // descendants) width, we could probably disable inflation in |
8835 | 0 | // fewer cases than we currently do. |
8836 | 0 | // MathML cells need special treatment. See bug 1002526 comment 56. |
8837 | 0 | if (aFrame->IsContainerForFontSizeInflation() && |
8838 | 0 | !aFrame->IsFrameOfType(nsIFrame::eMathML)) { |
8839 | 0 | mPresContext = aFrame->PresContext(); |
8840 | 0 | mOldValue = mPresContext->mInflationDisabledForShrinkWrap; |
8841 | 0 | mPresContext->mInflationDisabledForShrinkWrap = true; |
8842 | 0 | } else { |
8843 | 0 | // indicate we have nothing to restore |
8844 | 0 | mPresContext = nullptr; |
8845 | 0 | mOldValue = false; |
8846 | 0 | } |
8847 | 0 | } |
8848 | | |
8849 | | AutoMaybeDisableFontInflation::~AutoMaybeDisableFontInflation() |
8850 | 0 | { |
8851 | 0 | if (mPresContext) { |
8852 | 0 | mPresContext->mInflationDisabledForShrinkWrap = mOldValue; |
8853 | 0 | } |
8854 | 0 | } |
8855 | | |
8856 | | namespace mozilla { |
8857 | | |
8858 | | Rect NSRectToRect(const nsRect& aRect, double aAppUnitsPerPixel) |
8859 | 0 | { |
8860 | 0 | // Note that by making aAppUnitsPerPixel a double we're doing floating-point |
8861 | 0 | // division using a larger type and avoiding rounding error. |
8862 | 0 | return Rect(Float(aRect.x / aAppUnitsPerPixel), |
8863 | 0 | Float(aRect.y / aAppUnitsPerPixel), |
8864 | 0 | Float(aRect.width / aAppUnitsPerPixel), |
8865 | 0 | Float(aRect.height / aAppUnitsPerPixel)); |
8866 | 0 | } |
8867 | | |
8868 | | Rect NSRectToSnappedRect(const nsRect& aRect, double aAppUnitsPerPixel, |
8869 | | const gfx::DrawTarget& aSnapDT) |
8870 | 0 | { |
8871 | 0 | // Note that by making aAppUnitsPerPixel a double we're doing floating-point |
8872 | 0 | // division using a larger type and avoiding rounding error. |
8873 | 0 | Rect rect(Float(aRect.x / aAppUnitsPerPixel), |
8874 | 0 | Float(aRect.y / aAppUnitsPerPixel), |
8875 | 0 | Float(aRect.width / aAppUnitsPerPixel), |
8876 | 0 | Float(aRect.height / aAppUnitsPerPixel)); |
8877 | 0 | MaybeSnapToDevicePixels(rect, aSnapDT, true); |
8878 | 0 | return rect; |
8879 | 0 | } |
8880 | | // Similar to a snapped rect, except an axis is left unsnapped if the snapping |
8881 | | // process results in a length of 0. |
8882 | | Rect NSRectToNonEmptySnappedRect(const nsRect& aRect, double aAppUnitsPerPixel, |
8883 | | const gfx::DrawTarget& aSnapDT) |
8884 | 0 | { |
8885 | 0 | // Note that by making aAppUnitsPerPixel a double we're doing floating-point |
8886 | 0 | // division using a larger type and avoiding rounding error. |
8887 | 0 | Rect rect(Float(aRect.x / aAppUnitsPerPixel), |
8888 | 0 | Float(aRect.y / aAppUnitsPerPixel), |
8889 | 0 | Float(aRect.width / aAppUnitsPerPixel), |
8890 | 0 | Float(aRect.height / aAppUnitsPerPixel)); |
8891 | 0 | MaybeSnapToDevicePixels(rect, aSnapDT, true, false); |
8892 | 0 | return rect; |
8893 | 0 | } |
8894 | | |
8895 | | void StrokeLineWithSnapping(const nsPoint& aP1, const nsPoint& aP2, |
8896 | | int32_t aAppUnitsPerDevPixel, |
8897 | | DrawTarget& aDrawTarget, |
8898 | | const Pattern& aPattern, |
8899 | | const StrokeOptions& aStrokeOptions, |
8900 | | const DrawOptions& aDrawOptions) |
8901 | 0 | { |
8902 | 0 | Point p1 = NSPointToPoint(aP1, aAppUnitsPerDevPixel); |
8903 | 0 | Point p2 = NSPointToPoint(aP2, aAppUnitsPerDevPixel); |
8904 | 0 | SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, |
8905 | 0 | aStrokeOptions.mLineWidth); |
8906 | 0 | aDrawTarget.StrokeLine(p1, p2, aPattern, aStrokeOptions, aDrawOptions); |
8907 | 0 | } |
8908 | | |
8909 | | namespace layout { |
8910 | | |
8911 | | void |
8912 | | MaybeSetupTransactionIdAllocator(layers::LayerManager* aManager, |
8913 | | nsPresContext* aPresContext) |
8914 | 0 | { |
8915 | 0 | auto backendType = aManager->GetBackendType(); |
8916 | 0 | if (backendType == LayersBackend::LAYERS_CLIENT || |
8917 | 0 | backendType == LayersBackend::LAYERS_WR) { |
8918 | 0 | aManager->SetTransactionIdAllocator(aPresContext->RefreshDriver()); |
8919 | 0 | } |
8920 | 0 | } |
8921 | | |
8922 | | } // namespace layout |
8923 | | } // namespace mozilla |
8924 | | |
8925 | | /* static */ bool |
8926 | | nsLayoutUtils::IsOutlineStyleAutoEnabled() |
8927 | 0 | { |
8928 | 0 | static bool sOutlineStyleAutoEnabled; |
8929 | 0 | static bool sOutlineStyleAutoPrefCached = false; |
8930 | 0 |
|
8931 | 0 | if (!sOutlineStyleAutoPrefCached) { |
8932 | 0 | sOutlineStyleAutoPrefCached = true; |
8933 | 0 | Preferences::AddBoolVarCache(&sOutlineStyleAutoEnabled, |
8934 | 0 | "layout.css.outline-style-auto.enabled", |
8935 | 0 | false); |
8936 | 0 | } |
8937 | 0 | return sOutlineStyleAutoEnabled; |
8938 | 0 | } |
8939 | | |
8940 | | /* static */ void |
8941 | | nsLayoutUtils::SetBSizeFromFontMetrics(const nsIFrame* aFrame, |
8942 | | ReflowOutput& aMetrics, |
8943 | | const LogicalMargin& aFramePadding, |
8944 | | WritingMode aLineWM, |
8945 | | WritingMode aFrameWM) |
8946 | 0 | { |
8947 | 0 | RefPtr<nsFontMetrics> fm = |
8948 | 0 | nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame); |
8949 | 0 |
|
8950 | 0 | if (fm) { |
8951 | 0 | // Compute final height of the frame. |
8952 | 0 | // |
8953 | 0 | // Do things the standard css2 way -- though it's hard to find it |
8954 | 0 | // in the css2 spec! It's actually found in the css1 spec section |
8955 | 0 | // 4.4 (you will have to read between the lines to really see |
8956 | 0 | // it). |
8957 | 0 | // |
8958 | 0 | // The height of our box is the sum of our font size plus the top |
8959 | 0 | // and bottom border and padding. The height of children do not |
8960 | 0 | // affect our height. |
8961 | 0 | aMetrics.SetBlockStartAscent(aLineWM.IsLineInverted() ? fm->MaxDescent() |
8962 | 0 | : fm->MaxAscent()); |
8963 | 0 | aMetrics.BSize(aLineWM) = fm->MaxHeight(); |
8964 | 0 | } else { |
8965 | 0 | NS_WARNING("Cannot get font metrics - defaulting sizes to 0"); |
8966 | 0 | aMetrics.SetBlockStartAscent(aMetrics.BSize(aLineWM) = 0); |
8967 | 0 | } |
8968 | 0 | aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() + |
8969 | 0 | aFramePadding.BStart(aFrameWM)); |
8970 | 0 | aMetrics.BSize(aLineWM) += aFramePadding.BStartEnd(aFrameWM); |
8971 | 0 | } |
8972 | | |
8973 | | /* static */ bool |
8974 | | nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(nsIPresShell* aShell) |
8975 | 0 | { |
8976 | 0 | if (nsIDocument* doc = aShell->GetDocument()) { |
8977 | 0 | WidgetEvent event(true, eVoidEvent); |
8978 | 0 | nsTArray<EventTarget*> targets; |
8979 | 0 | nsresult rv = EventDispatcher::Dispatch(doc, nullptr, &event, nullptr, |
8980 | 0 | nullptr, nullptr, &targets); |
8981 | 0 | NS_ENSURE_SUCCESS(rv, false); |
8982 | 0 | for (size_t i = 0; i < targets.Length(); i++) { |
8983 | 0 | if (targets[i]->IsApzAware()) { |
8984 | 0 | return true; |
8985 | 0 | } |
8986 | 0 | } |
8987 | 0 | } |
8988 | 0 | return false; |
8989 | 0 | } |
8990 | | |
8991 | | static void |
8992 | | MaybeReflowForInflationScreenSizeChange(nsPresContext *aPresContext) |
8993 | 0 | { |
8994 | 0 | if (aPresContext) { |
8995 | 0 | nsIPresShell* presShell = aPresContext->GetPresShell(); |
8996 | 0 | const bool fontInflationWasEnabled = presShell->FontSizeInflationEnabled(); |
8997 | 0 | presShell->RecomputeFontSizeInflationEnabled(); |
8998 | 0 | bool changed = false; |
8999 | 0 | if (presShell->FontSizeInflationEnabled() && |
9000 | 0 | presShell->FontSizeInflationMinTwips() != 0) { |
9001 | 0 | aPresContext->ScreenSizeInchesForFontInflation(&changed); |
9002 | 0 | } |
9003 | 0 |
|
9004 | 0 | changed = changed || |
9005 | 0 | fontInflationWasEnabled != presShell->FontSizeInflationEnabled(); |
9006 | 0 | if (changed) { |
9007 | 0 | nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell(); |
9008 | 0 | if (docShell) { |
9009 | 0 | nsCOMPtr<nsIContentViewer> cv; |
9010 | 0 | docShell->GetContentViewer(getter_AddRefs(cv)); |
9011 | 0 | if (cv) { |
9012 | 0 | nsTArray<nsCOMPtr<nsIContentViewer> > array; |
9013 | 0 | cv->AppendSubtree(array); |
9014 | 0 | for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) { |
9015 | 0 | nsCOMPtr<nsIPresShell> shell; |
9016 | 0 | nsCOMPtr<nsIContentViewer> cv = array[i]; |
9017 | 0 | cv->GetPresShell(getter_AddRefs(shell)); |
9018 | 0 | if (shell) { |
9019 | 0 | nsIFrame *rootFrame = shell->GetRootFrame(); |
9020 | 0 | if (rootFrame) { |
9021 | 0 | shell->FrameNeedsReflow(rootFrame, |
9022 | 0 | nsIPresShell::eStyleChange, |
9023 | 0 | NS_FRAME_IS_DIRTY); |
9024 | 0 | } |
9025 | 0 | } |
9026 | 0 | } |
9027 | 0 | } |
9028 | 0 | } |
9029 | 0 | } |
9030 | 0 | } |
9031 | 0 | } |
9032 | | |
9033 | | /* static */ void |
9034 | | nsLayoutUtils::SetVisualViewportSize(nsIPresShell* aPresShell, CSSSize aSize) |
9035 | 0 | { |
9036 | 0 | MOZ_ASSERT(aSize.width >= 0.0 && aSize.height >= 0.0); |
9037 | 0 |
|
9038 | 0 | aPresShell->SetVisualViewportSize( |
9039 | 0 | nsPresContext::CSSPixelsToAppUnits(aSize.width), |
9040 | 0 | nsPresContext::CSSPixelsToAppUnits(aSize.height)); |
9041 | 0 |
|
9042 | 0 | // When the "font.size.inflation.minTwips" preference is set, the |
9043 | 0 | // layout depends on the size of the screen. Since when the size |
9044 | 0 | // of the screen changes, the scroll position clamping scroll port |
9045 | 0 | // size also changes, we hook in the needed updates here rather |
9046 | 0 | // than adding a separate notification just for this change. |
9047 | 0 | nsPresContext* presContext = aPresShell->GetPresContext(); |
9048 | 0 | MaybeReflowForInflationScreenSizeChange(presContext); |
9049 | 0 | } |
9050 | | |
9051 | | /* static */ bool |
9052 | | nsLayoutUtils::CanScrollOriginClobberApz(nsAtom* aScrollOrigin) |
9053 | 0 | { |
9054 | 0 | return aScrollOrigin != nullptr |
9055 | 0 | && aScrollOrigin != nsGkAtoms::apz |
9056 | 0 | && aScrollOrigin != nsGkAtoms::restore; |
9057 | 0 | } |
9058 | | |
9059 | | /* static */ ScrollMetadata |
9060 | | nsLayoutUtils::ComputeScrollMetadata(nsIFrame* aForFrame, |
9061 | | nsIFrame* aScrollFrame, |
9062 | | nsIContent* aContent, |
9063 | | const nsIFrame* aReferenceFrame, |
9064 | | LayerManager* aLayerManager, |
9065 | | ViewID aScrollParentId, |
9066 | | const nsRect& aViewport, |
9067 | | const Maybe<nsRect>& aClipRect, |
9068 | | bool aIsRootContent, |
9069 | | const ContainerLayerParameters& aContainerParameters) |
9070 | 0 | { |
9071 | 0 | nsPresContext* presContext = aForFrame->PresContext(); |
9072 | 0 | int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); |
9073 | 0 |
|
9074 | 0 | nsIPresShell* presShell = presContext->GetPresShell(); |
9075 | 0 | ScrollMetadata metadata; |
9076 | 0 | FrameMetrics& metrics = metadata.GetMetrics(); |
9077 | 0 | metrics.SetViewport(CSSRect::FromAppUnits(aViewport)); |
9078 | 0 |
|
9079 | 0 | ViewID scrollId = FrameMetrics::NULL_SCROLL_ID; |
9080 | 0 | if (aContent) { |
9081 | 0 | if (void* paintRequestTime = aContent->GetProperty(nsGkAtoms::paintRequestTime)) { |
9082 | 0 | metrics.SetPaintRequestTime(*static_cast<TimeStamp*>(paintRequestTime)); |
9083 | 0 | aContent->DeleteProperty(nsGkAtoms::paintRequestTime); |
9084 | 0 | } |
9085 | 0 | scrollId = nsLayoutUtils::FindOrCreateIDFor(aContent); |
9086 | 0 | nsRect dp; |
9087 | 0 | if (nsLayoutUtils::GetDisplayPort(aContent, &dp)) { |
9088 | 0 | metrics.SetDisplayPort(CSSRect::FromAppUnits(dp)); |
9089 | 0 | if (IsAPZTestLoggingEnabled()) { |
9090 | 0 | LogTestDataForPaint(aLayerManager, scrollId, "displayport", |
9091 | 0 | metrics.GetDisplayPort()); |
9092 | 0 | } |
9093 | 0 | } |
9094 | 0 | if (nsLayoutUtils::GetCriticalDisplayPort(aContent, &dp)) { |
9095 | 0 | metrics.SetCriticalDisplayPort(CSSRect::FromAppUnits(dp)); |
9096 | 0 | if (IsAPZTestLoggingEnabled()) { |
9097 | 0 | LogTestDataForPaint(aLayerManager, scrollId, "criticalDisplayport", |
9098 | 0 | metrics.GetCriticalDisplayPort()); |
9099 | 0 | } |
9100 | 0 | } |
9101 | 0 | DisplayPortMarginsPropertyData* marginsData = |
9102 | 0 | static_cast<DisplayPortMarginsPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); |
9103 | 0 | if (marginsData) { |
9104 | 0 | metrics.SetDisplayPortMargins(marginsData->mMargins); |
9105 | 0 | } |
9106 | 0 | } |
9107 | 0 |
|
9108 | 0 | nsIScrollableFrame* scrollableFrame = nullptr; |
9109 | 0 | if (aScrollFrame) |
9110 | 0 | scrollableFrame = aScrollFrame->GetScrollTargetFrame(); |
9111 | 0 |
|
9112 | 0 | metrics.SetScrollableRect(CSSRect::FromAppUnits( |
9113 | 0 | nsLayoutUtils::CalculateScrollableRectForFrame(scrollableFrame, aForFrame))); |
9114 | 0 |
|
9115 | 0 | if (scrollableFrame) { |
9116 | 0 | CSSPoint scrollPosition = CSSPoint::FromAppUnits(scrollableFrame->GetScrollPosition()); |
9117 | 0 | metrics.SetScrollOffset(scrollPosition); |
9118 | 0 |
|
9119 | 0 | CSSRect viewport = metrics.GetViewport(); |
9120 | 0 | viewport.MoveTo(scrollPosition); |
9121 | 0 | metrics.SetViewport(viewport); |
9122 | 0 |
|
9123 | 0 | nsPoint smoothScrollPosition = scrollableFrame->LastScrollDestination(); |
9124 | 0 | metrics.SetSmoothScrollOffset(CSSPoint::FromAppUnits(smoothScrollPosition)); |
9125 | 0 |
|
9126 | 0 | // If the frame was scrolled since the last layers update, and by something |
9127 | 0 | // that is higher priority than APZ, we want to tell the APZ to update |
9128 | 0 | // its scroll offset. We want to distinguish the case where the scroll offset |
9129 | 0 | // was "restored" because in that case the restored scroll position should |
9130 | 0 | // not overwrite a user-driven scroll. |
9131 | 0 | if (scrollableFrame->LastScrollOrigin() == nsGkAtoms::restore) { |
9132 | 0 | metrics.SetScrollOffsetRestored(scrollableFrame->CurrentScrollGeneration()); |
9133 | 0 | } else if (CanScrollOriginClobberApz(scrollableFrame->LastScrollOrigin())) { |
9134 | 0 | metrics.SetScrollOffsetUpdated(scrollableFrame->CurrentScrollGeneration()); |
9135 | 0 | } |
9136 | 0 | scrollableFrame->AllowScrollOriginDowngrade(); |
9137 | 0 |
|
9138 | 0 | nsAtom* lastSmoothScrollOrigin = scrollableFrame->LastSmoothScrollOrigin(); |
9139 | 0 | if (lastSmoothScrollOrigin) { |
9140 | 0 | metrics.SetSmoothScrollOffsetUpdated(scrollableFrame->CurrentScrollGeneration()); |
9141 | 0 | } |
9142 | 0 |
|
9143 | 0 | nsSize lineScrollAmount = scrollableFrame->GetLineScrollAmount(); |
9144 | 0 | LayoutDeviceIntSize lineScrollAmountInDevPixels = |
9145 | 0 | LayoutDeviceIntSize::FromAppUnitsRounded(lineScrollAmount, presContext->AppUnitsPerDevPixel()); |
9146 | 0 | metadata.SetLineScrollAmount(lineScrollAmountInDevPixels); |
9147 | 0 |
|
9148 | 0 | nsSize pageScrollAmount = scrollableFrame->GetPageScrollAmount(); |
9149 | 0 | LayoutDeviceIntSize pageScrollAmountInDevPixels = |
9150 | 0 | LayoutDeviceIntSize::FromAppUnitsRounded(pageScrollAmount, presContext->AppUnitsPerDevPixel()); |
9151 | 0 | metadata.SetPageScrollAmount(pageScrollAmountInDevPixels); |
9152 | 0 |
|
9153 | 0 | if (aScrollFrame->GetParent()) { |
9154 | 0 | metadata.SetDisregardedDirection( |
9155 | 0 | WheelHandlingUtils::GetDisregardedWheelScrollDirection( |
9156 | 0 | aScrollFrame->GetParent())); |
9157 | 0 | } |
9158 | 0 |
|
9159 | 0 | metadata.SetUsesContainerScrolling(scrollableFrame->UsesContainerScrolling()); |
9160 | 0 |
|
9161 | 0 | metadata.SetSnapInfo(scrollableFrame->GetScrollSnapInfo()); |
9162 | 0 |
|
9163 | 0 | ScrollStyles scrollStyles = scrollableFrame->GetScrollStyles(); |
9164 | 0 | metadata.SetOverscrollBehavior(OverscrollBehaviorInfo::FromStyleConstants( |
9165 | 0 | scrollStyles.mOverscrollBehaviorX, |
9166 | 0 | scrollStyles.mOverscrollBehaviorY)); |
9167 | 0 | } |
9168 | 0 |
|
9169 | 0 | // If we have the scrollparent being the same as the scroll id, the |
9170 | 0 | // compositor-side code could get into an infinite loop while building the |
9171 | 0 | // overscroll handoff chain. |
9172 | 0 | MOZ_ASSERT(aScrollParentId == FrameMetrics::NULL_SCROLL_ID || scrollId != aScrollParentId); |
9173 | 0 | metrics.SetScrollId(scrollId); |
9174 | 0 | metrics.SetIsRootContent(aIsRootContent); |
9175 | 0 | metadata.SetScrollParentId(aScrollParentId); |
9176 | 0 |
|
9177 | 0 | nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); |
9178 | 0 | bool isRootScrollFrame = aScrollFrame == rootScrollFrame; |
9179 | 0 | nsIDocument* document = presShell->GetDocument(); |
9180 | 0 |
|
9181 | 0 | if (scrollId != FrameMetrics::NULL_SCROLL_ID && !presContext->GetParentPresContext()) { |
9182 | 0 | if ((aScrollFrame && isRootScrollFrame)) { |
9183 | 0 | metadata.SetIsLayersIdRoot(true); |
9184 | 0 | } else { |
9185 | 0 | MOZ_ASSERT(document, "A non-root-scroll frame must be in a document"); |
9186 | 0 | if (aContent == document->GetDocumentElement()) { |
9187 | 0 | metadata.SetIsLayersIdRoot(true); |
9188 | 0 | } |
9189 | 0 | } |
9190 | 0 | } |
9191 | 0 |
|
9192 | 0 | // Get whether the root content is RTL(E.g. it's true either if |
9193 | 0 | // "writing-mode: vertical-rl", or if |
9194 | 0 | // "writing-mode: horizontal-tb; direction: rtl;" in CSS). |
9195 | 0 | // For the concept of this and the reason why we need to get this kind of |
9196 | 0 | // information, see the definition of |mIsAutoDirRootContentRTL| in struct |
9197 | 0 | // |ScrollMetadata|. |
9198 | 0 | Element* bodyElement = document ? document->GetBodyElement() : nullptr; |
9199 | 0 | nsIFrame* primaryFrame = bodyElement ? bodyElement->GetPrimaryFrame() : |
9200 | 0 | rootScrollFrame; |
9201 | 0 | if (!primaryFrame) { |
9202 | 0 | primaryFrame = rootScrollFrame; |
9203 | 0 | } |
9204 | 0 | if (primaryFrame) { |
9205 | 0 | WritingMode writingModeOfRootScrollFrame = |
9206 | 0 | primaryFrame->GetWritingMode(); |
9207 | 0 | WritingMode::BlockDir blockDirOfRootScrollFrame = |
9208 | 0 | writingModeOfRootScrollFrame.GetBlockDir(); |
9209 | 0 | WritingMode::InlineDir inlineDirOfRootScrollFrame = |
9210 | 0 | writingModeOfRootScrollFrame.GetInlineDir(); |
9211 | 0 | if (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockRL || |
9212 | 0 | (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockTB && |
9213 | 0 | inlineDirOfRootScrollFrame == WritingMode::InlineDir::eInlineRTL)) { |
9214 | 0 | metadata.SetIsAutoDirRootContentRTL(true); |
9215 | 0 | } |
9216 | 0 | } |
9217 | 0 |
|
9218 | 0 | // Only the root scrollable frame for a given presShell should pick up |
9219 | 0 | // the presShell's resolution. All the other frames are 1.0. |
9220 | 0 | if (isRootScrollFrame) { |
9221 | 0 | metrics.SetPresShellResolution(presShell->GetResolution()); |
9222 | 0 | } else { |
9223 | 0 | metrics.SetPresShellResolution(1.0f); |
9224 | 0 | } |
9225 | 0 | // The cumulative resolution is the resolution at which the scroll frame's |
9226 | 0 | // content is actually rendered. It includes the pres shell resolutions of |
9227 | 0 | // all the pres shells from here up to the root, as well as any css-driven |
9228 | 0 | // resolution. We don't need to compute it as it's already stored in the |
9229 | 0 | // container parameters. |
9230 | 0 | metrics.SetCumulativeResolution(aContainerParameters.Scale()); |
9231 | 0 |
|
9232 | 0 | LayoutDeviceToScreenScale2D resolutionToScreen( |
9233 | 0 | presShell->GetCumulativeResolution() |
9234 | 0 | * nsLayoutUtils::GetTransformToAncestorScale(aScrollFrame ? aScrollFrame : aForFrame)); |
9235 | 0 | metrics.SetExtraResolution(metrics.GetCumulativeResolution() / resolutionToScreen); |
9236 | 0 |
|
9237 | 0 | metrics.SetDevPixelsPerCSSPixel(presContext->CSSToDevPixelScale()); |
9238 | 0 |
|
9239 | 0 | // Initially, AsyncPanZoomController should render the content to the screen |
9240 | 0 | // at the painted resolution. |
9241 | 0 | const LayerToParentLayerScale layerToParentLayerScale(1.0f); |
9242 | 0 | metrics.SetZoom(metrics.GetCumulativeResolution() * metrics.GetDevPixelsPerCSSPixel() |
9243 | 0 | * layerToParentLayerScale); |
9244 | 0 |
|
9245 | 0 | // Calculate the composition bounds as the size of the scroll frame and |
9246 | 0 | // its origin relative to the reference frame. |
9247 | 0 | // If aScrollFrame is null, we are in a document without a root scroll frame, |
9248 | 0 | // so it's a xul document. In this case, use the size of the viewport frame. |
9249 | 0 | nsIFrame* frameForCompositionBoundsCalculation = aScrollFrame ? aScrollFrame : aForFrame; |
9250 | 0 | nsRect compositionBounds(frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aReferenceFrame), |
9251 | 0 | frameForCompositionBoundsCalculation->GetSize()); |
9252 | 0 | if (scrollableFrame) { |
9253 | 0 | // If we have a scrollable frame, restrict the composition bounds to its |
9254 | 0 | // scroll port. The scroll port excludes the frame borders and the scroll |
9255 | 0 | // bars, which we don't want to be part of the composition bounds. |
9256 | 0 | nsRect scrollPort = scrollableFrame->GetScrollPortRect(); |
9257 | 0 | compositionBounds = nsRect(compositionBounds.TopLeft() + scrollPort.TopLeft(), |
9258 | 0 | scrollPort.Size()); |
9259 | 0 | } |
9260 | 0 | ParentLayerRect frameBounds = LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel) |
9261 | 0 | * metrics.GetCumulativeResolution() |
9262 | 0 | * layerToParentLayerScale; |
9263 | 0 |
|
9264 | 0 | if (aClipRect) { |
9265 | 0 | ParentLayerRect rect = LayoutDeviceRect::FromAppUnits(*aClipRect, auPerDevPixel) |
9266 | 0 | * metrics.GetCumulativeResolution() |
9267 | 0 | * layerToParentLayerScale; |
9268 | 0 | metadata.SetScrollClip(Some(LayerClip(RoundedToInt(rect)))); |
9269 | 0 | } |
9270 | 0 |
|
9271 | 0 | // For the root scroll frame of the root content document (RCD-RSF), the above calculation |
9272 | 0 | // will yield the size of the viewport frame as the composition bounds, which |
9273 | 0 | // doesn't actually correspond to what is visible when |
9274 | 0 | // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible area of |
9275 | 0 | // the prescontext that the viewport frame is reflowed into. In that case if our |
9276 | 0 | // document has a widget then the widget's bounds will correspond to what is |
9277 | 0 | // visible. If we don't have a widget the root view's bounds correspond to what |
9278 | 0 | // would be visible because they don't get modified by setCSSViewport. |
9279 | 0 | bool isRootContentDocRootScrollFrame = isRootScrollFrame |
9280 | 0 | && presContext->IsRootContentDocument(); |
9281 | 0 | if (isRootContentDocRootScrollFrame) { |
9282 | 0 | UpdateCompositionBoundsForRCDRSF(frameBounds, presContext, true); |
9283 | 0 | } |
9284 | 0 |
|
9285 | 0 | nsMargin sizes = ScrollbarAreaToExcludeFromCompositionBoundsFor(aScrollFrame); |
9286 | 0 | // Scrollbars are not subject to resolution scaling, so LD pixels = layer pixels for them. |
9287 | 0 | ParentLayerMargin boundMargins = LayoutDeviceMargin::FromAppUnits(sizes, auPerDevPixel) |
9288 | 0 | * LayoutDeviceToParentLayerScale(1.0f); |
9289 | 0 | frameBounds.Deflate(boundMargins); |
9290 | 0 |
|
9291 | 0 | metrics.SetCompositionBounds(frameBounds); |
9292 | 0 |
|
9293 | 0 | metrics.SetRootCompositionSize( |
9294 | 0 | nsLayoutUtils::CalculateRootCompositionSize(aScrollFrame ? aScrollFrame : aForFrame, |
9295 | 0 | isRootContentDocRootScrollFrame, metrics)); |
9296 | 0 |
|
9297 | 0 | if (gfxPrefs::APZPrintTree() || gfxPrefs::APZTestLoggingEnabled()) { |
9298 | 0 | if (nsIContent* content = frameForCompositionBoundsCalculation->GetContent()) { |
9299 | 0 | nsAutoString contentDescription; |
9300 | 0 | if (content->IsElement()) { |
9301 | 0 | content->AsElement()->Describe(contentDescription); |
9302 | 0 | } else { |
9303 | 0 | contentDescription.AssignLiteral("(not an element)"); |
9304 | 0 | } |
9305 | 0 | metadata.SetContentDescription(NS_LossyConvertUTF16toASCII(contentDescription)); |
9306 | 0 | if (IsAPZTestLoggingEnabled()) { |
9307 | 0 | LogTestDataForPaint(aLayerManager, scrollId, "contentDescription", |
9308 | 0 | metadata.GetContentDescription().get()); |
9309 | 0 | } |
9310 | 0 | } |
9311 | 0 | } |
9312 | 0 |
|
9313 | 0 | metrics.SetPresShellId(presShell->GetPresShellId()); |
9314 | 0 |
|
9315 | 0 | // If the scroll frame's content is marked 'scrollgrab', record this |
9316 | 0 | // in the FrameMetrics so APZ knows to provide the scroll grabbing |
9317 | 0 | // behaviour. |
9318 | 0 | if (aScrollFrame && nsContentUtils::HasScrollgrab(aScrollFrame->GetContent())) { |
9319 | 0 | metadata.SetHasScrollgrab(true); |
9320 | 0 | } |
9321 | 0 |
|
9322 | 0 | // Also compute and set the background color. |
9323 | 0 | // This is needed for APZ overscrolling support. |
9324 | 0 | if (aScrollFrame) { |
9325 | 0 | if (isRootScrollFrame) { |
9326 | 0 | metadata.SetBackgroundColor(Color::FromABGR( |
9327 | 0 | presShell->GetCanvasBackground())); |
9328 | 0 | } else { |
9329 | 0 | ComputedStyle* backgroundStyle; |
9330 | 0 | if (nsCSSRendering::FindBackground(aScrollFrame, &backgroundStyle)) { |
9331 | 0 | nscolor backgroundColor = backgroundStyle-> |
9332 | 0 | StyleBackground()->BackgroundColor(backgroundStyle); |
9333 | 0 | metadata.SetBackgroundColor(Color::FromABGR(backgroundColor)); |
9334 | 0 | } |
9335 | 0 | } |
9336 | 0 | } |
9337 | 0 |
|
9338 | 0 | if (ShouldDisableApzForElement(aContent)) { |
9339 | 0 | metadata.SetForceDisableApz(true); |
9340 | 0 | } |
9341 | 0 |
|
9342 | 0 | return metadata; |
9343 | 0 | } |
9344 | | |
9345 | | /*static*/ Maybe<ScrollMetadata> |
9346 | | nsLayoutUtils::GetRootMetadata(nsDisplayListBuilder* aBuilder, |
9347 | | LayerManager* aLayerManager, |
9348 | | const ContainerLayerParameters& aContainerParameters, |
9349 | | const std::function<bool(ViewID& aScrollId)>& aCallback) |
9350 | 0 | { |
9351 | 0 | nsIFrame* frame = aBuilder->RootReferenceFrame(); |
9352 | 0 | nsPresContext* presContext = frame->PresContext(); |
9353 | 0 | nsIPresShell* presShell = presContext->PresShell(); |
9354 | 0 | nsIDocument* document = presShell->GetDocument(); |
9355 | 0 |
|
9356 | 0 | // If we're using containerless scrolling, there is still one case where we |
9357 | 0 | // want the root container layer to have metrics. If the parent process is |
9358 | 0 | // using XUL windows, there is no root scrollframe, and without explicitly |
9359 | 0 | // creating metrics there will be no guaranteed top-level APZC. |
9360 | 0 | bool addMetrics = gfxPrefs::LayoutUseContainersForRootFrames() || |
9361 | 0 | (XRE_IsParentProcess() && !presShell->GetRootScrollFrame()); |
9362 | 0 |
|
9363 | 0 | // Add metrics if there are none in the layer tree with the id (create an id |
9364 | 0 | // if there isn't one already) of the root scroll frame/root content. |
9365 | 0 | bool ensureMetricsForRootId = |
9366 | 0 | nsLayoutUtils::AsyncPanZoomEnabled(frame) && |
9367 | 0 | !gfxPrefs::LayoutUseContainersForRootFrames() && |
9368 | 0 | aBuilder->IsPaintingToWindow() && |
9369 | 0 | !presContext->GetParentPresContext(); |
9370 | 0 |
|
9371 | 0 | nsIContent* content = nullptr; |
9372 | 0 | nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); |
9373 | 0 | if (rootScrollFrame) { |
9374 | 0 | content = rootScrollFrame->GetContent(); |
9375 | 0 | } else { |
9376 | 0 | // If there is no root scroll frame, pick the document element instead. |
9377 | 0 | // The only case we don't want to do this is in non-APZ fennec, where |
9378 | 0 | // we want the root xul document to get a null scroll id so that the root |
9379 | 0 | // content document gets the first non-null scroll id. |
9380 | 0 | content = document->GetDocumentElement(); |
9381 | 0 | } |
9382 | 0 |
|
9383 | 0 | if (ensureMetricsForRootId && content) { |
9384 | 0 | ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(content); |
9385 | 0 | if (aCallback(scrollId)) { |
9386 | 0 | ensureMetricsForRootId = false; |
9387 | 0 | } |
9388 | 0 | } |
9389 | 0 |
|
9390 | 0 | if (addMetrics || ensureMetricsForRootId) { |
9391 | 0 | bool isRootContent = presContext->IsRootContentDocument(); |
9392 | 0 |
|
9393 | 0 | nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize()); |
9394 | 0 | return Some(nsLayoutUtils::ComputeScrollMetadata(frame, |
9395 | 0 | rootScrollFrame, content, |
9396 | 0 | aBuilder->FindReferenceFrameFor(frame), |
9397 | 0 | aLayerManager, FrameMetrics::NULL_SCROLL_ID, viewport, Nothing(), |
9398 | 0 | isRootContent, aContainerParameters)); |
9399 | 0 | } |
9400 | 0 | |
9401 | 0 | return Nothing(); |
9402 | 0 | } |
9403 | | |
9404 | | /* static */ bool |
9405 | | nsLayoutUtils::ContainsMetricsWithId(const Layer* aLayer, const ViewID& aScrollId) |
9406 | 0 | { |
9407 | 0 | for (uint32_t i = aLayer->GetScrollMetadataCount(); i > 0; i--) { |
9408 | 0 | if (aLayer->GetFrameMetrics(i-1).GetScrollId() == aScrollId) { |
9409 | 0 | return true; |
9410 | 0 | } |
9411 | 0 | } |
9412 | 0 | for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { |
9413 | 0 | if (ContainsMetricsWithId(child, aScrollId)) { |
9414 | 0 | return true; |
9415 | 0 | } |
9416 | 0 | } |
9417 | 0 | return false; |
9418 | 0 | } |
9419 | | |
9420 | | /* static */ uint32_t |
9421 | | nsLayoutUtils::GetTouchActionFromFrame(nsIFrame* aFrame) |
9422 | 0 | { |
9423 | 0 | // If aFrame is null then return default value |
9424 | 0 | if (!aFrame) { |
9425 | 0 | return NS_STYLE_TOUCH_ACTION_AUTO; |
9426 | 0 | } |
9427 | 0 |
|
9428 | 0 | // The touch-action CSS property applies to: all elements except: |
9429 | 0 | // non-replaced inline elements, table rows, row groups, table columns, and column groups |
9430 | 0 | bool isNonReplacedInlineElement = aFrame->IsFrameOfType(nsIFrame::eLineParticipant); |
9431 | 0 | if (isNonReplacedInlineElement) { |
9432 | 0 | return NS_STYLE_TOUCH_ACTION_AUTO; |
9433 | 0 | } |
9434 | 0 |
|
9435 | 0 | const nsStyleDisplay* disp = aFrame->StyleDisplay(); |
9436 | 0 | bool isTableElement = disp->IsInternalTableStyleExceptCell(); |
9437 | 0 | if (isTableElement) { |
9438 | 0 | return NS_STYLE_TOUCH_ACTION_AUTO; |
9439 | 0 | } |
9440 | 0 |
|
9441 | 0 | return disp->mTouchAction; |
9442 | 0 | } |
9443 | | |
9444 | | /* static */ void |
9445 | | nsLayoutUtils::TransformToAncestorAndCombineRegions( |
9446 | | const nsRegion& aRegion, |
9447 | | nsIFrame* aFrame, |
9448 | | const nsIFrame* aAncestorFrame, |
9449 | | nsRegion* aPreciseTargetDest, |
9450 | | nsRegion* aImpreciseTargetDest, |
9451 | | Maybe<Matrix4x4Flagged>* aMatrixCache, |
9452 | | const DisplayItemClip* aClip) |
9453 | 0 | { |
9454 | 0 | if (aRegion.IsEmpty()) { |
9455 | 0 | return; |
9456 | 0 | } |
9457 | 0 | bool isPrecise; |
9458 | 0 | RegionBuilder<nsRegion> transformedRegion; |
9459 | 0 | for (nsRegion::RectIterator it = aRegion.RectIter(); !it.Done(); it.Next()) { |
9460 | 0 | nsRect transformed = TransformFrameRectToAncestor( |
9461 | 0 | aFrame, it.Get(), aAncestorFrame, &isPrecise, aMatrixCache); |
9462 | 0 | if (aClip) { |
9463 | 0 | transformed = aClip->ApplyNonRoundedIntersection(transformed); |
9464 | 0 | if (aClip->GetRoundedRectCount() > 0) { |
9465 | 0 | isPrecise = false; |
9466 | 0 | } |
9467 | 0 | } |
9468 | 0 | transformedRegion.OrWith(transformed); |
9469 | 0 | } |
9470 | 0 | nsRegion* dest = isPrecise ? aPreciseTargetDest : aImpreciseTargetDest; |
9471 | 0 | dest->OrWith(transformedRegion.ToRegion()); |
9472 | 0 | // If the region becomes too complex this has a large performance impact. |
9473 | 0 | // We limit its complexity here. |
9474 | 0 | if (dest->GetNumRects() > 12) { |
9475 | 0 | dest->SimplifyOutward(6); |
9476 | 0 | if (isPrecise) { |
9477 | 0 | aPreciseTargetDest->OrWith(*aImpreciseTargetDest); |
9478 | 0 | *aImpreciseTargetDest = std::move(*aPreciseTargetDest); |
9479 | 0 | aImpreciseTargetDest->SimplifyOutward(6); |
9480 | 0 | *aPreciseTargetDest = nsRegion(); |
9481 | 0 | } |
9482 | 0 | } |
9483 | 0 | } |
9484 | | |
9485 | | /* static */ bool |
9486 | | nsLayoutUtils::ShouldUseNoScriptSheet(nsIDocument* aDocument) |
9487 | 0 | { |
9488 | 0 | // also handle the case where print is done from print preview |
9489 | 0 | // see bug #342439 for more details |
9490 | 0 | if (aDocument->IsStaticDocument()) { |
9491 | 0 | aDocument = aDocument->GetOriginalDocument(); |
9492 | 0 | } |
9493 | 0 | return aDocument->IsScriptEnabled(); |
9494 | 0 | } |
9495 | | |
9496 | | /* static */ bool |
9497 | | nsLayoutUtils::ShouldUseNoFramesSheet(nsIDocument* aDocument) |
9498 | 0 | { |
9499 | 0 | bool allowSubframes = true; |
9500 | 0 | nsIDocShell* docShell = aDocument->GetDocShell(); |
9501 | 0 | if (docShell) { |
9502 | 0 | docShell->GetAllowSubframes(&allowSubframes); |
9503 | 0 | } |
9504 | 0 | return !allowSubframes; |
9505 | 0 | } |
9506 | | |
9507 | | /* static */ void |
9508 | | nsLayoutUtils::GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult) |
9509 | 0 | { |
9510 | 0 | aResult.Truncate(); |
9511 | 0 | AppendFrameTextContent(aFrame, aResult); |
9512 | 0 | } |
9513 | | |
9514 | | /* static */ void |
9515 | | nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult) |
9516 | 0 | { |
9517 | 0 | if (aFrame->IsTextFrame()) { |
9518 | 0 | auto textFrame = static_cast<nsTextFrame*>(aFrame); |
9519 | 0 | auto offset = textFrame->GetContentOffset(); |
9520 | 0 | auto length = textFrame->GetContentLength(); |
9521 | 0 | textFrame->GetContent()-> |
9522 | 0 | GetText()->AppendTo(aResult, offset, length); |
9523 | 0 | } else { |
9524 | 0 | for (nsIFrame* child : aFrame->PrincipalChildList()) { |
9525 | 0 | AppendFrameTextContent(child, aResult); |
9526 | 0 | } |
9527 | 0 | } |
9528 | 0 | } |
9529 | | |
9530 | | /* static */ |
9531 | | nsRect |
9532 | | nsLayoutUtils::GetSelectionBoundingRect(Selection* aSel) |
9533 | 0 | { |
9534 | 0 | nsRect res; |
9535 | 0 | // Bounding client rect may be empty after calling GetBoundingClientRect |
9536 | 0 | // when range is collapsed. So we get caret's rect when range is |
9537 | 0 | // collapsed. |
9538 | 0 | if (aSel->IsCollapsed()) { |
9539 | 0 | nsIFrame* frame = nsCaret::GetGeometry(aSel, &res); |
9540 | 0 | if (frame) { |
9541 | 0 | nsIFrame* relativeTo = GetContainingBlockForClientRect(frame); |
9542 | 0 | res = TransformFrameRectToAncestor(frame, res, relativeTo); |
9543 | 0 | } |
9544 | 0 | } else { |
9545 | 0 | int32_t rangeCount = aSel->RangeCount(); |
9546 | 0 | RectAccumulator accumulator; |
9547 | 0 | for (int32_t idx = 0; idx < rangeCount; ++idx) { |
9548 | 0 | nsRange* range = aSel->GetRangeAt(idx); |
9549 | 0 | nsRange::CollectClientRectsAndText(&accumulator, nullptr, range, |
9550 | 0 | range->GetStartContainer(), |
9551 | 0 | range->StartOffset(), |
9552 | 0 | range->GetEndContainer(), |
9553 | 0 | range->EndOffset(), |
9554 | 0 | true, false); |
9555 | 0 | } |
9556 | 0 | res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect : |
9557 | 0 | accumulator.mResultRect; |
9558 | 0 | } |
9559 | 0 |
|
9560 | 0 | return res; |
9561 | 0 | } |
9562 | | |
9563 | | /* static */ nsBlockFrame* |
9564 | | nsLayoutUtils::GetFloatContainingBlock(nsIFrame* aFrame) |
9565 | 0 | { |
9566 | 0 | nsIFrame* ancestor = aFrame->GetParent(); |
9567 | 0 | while (ancestor && !ancestor->IsFloatContainingBlock()) { |
9568 | 0 | ancestor = ancestor->GetParent(); |
9569 | 0 | } |
9570 | 0 | MOZ_ASSERT(!ancestor || GetAsBlock(ancestor), |
9571 | 0 | "Float containing block can only be block frame"); |
9572 | 0 | return static_cast<nsBlockFrame*>(ancestor); |
9573 | 0 | } |
9574 | | |
9575 | | // The implementation of this calculation is adapted from |
9576 | | // Element::GetBoundingClientRect(). |
9577 | | /* static */ CSSRect |
9578 | | nsLayoutUtils::GetBoundingContentRect(const nsIContent* aContent, |
9579 | 0 | const nsIScrollableFrame* aRootScrollFrame) { |
9580 | 0 | CSSRect result; |
9581 | 0 | if (nsIFrame* frame = aContent->GetPrimaryFrame()) { |
9582 | 0 | nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame(); |
9583 | 0 | result = CSSRect::FromAppUnits( |
9584 | 0 | nsLayoutUtils::GetAllInFlowRectsUnion( |
9585 | 0 | frame, |
9586 | 0 | relativeTo, |
9587 | 0 | nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS)); |
9588 | 0 |
|
9589 | 0 | // If the element is contained in a scrollable frame that is not |
9590 | 0 | // the root scroll frame, make sure to clip the result so that it is |
9591 | 0 | // not larger than the containing scrollable frame's bounds. |
9592 | 0 | nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(frame); |
9593 | 0 | if (scrollFrame && scrollFrame != aRootScrollFrame) { |
9594 | 0 | nsIFrame* subFrame = do_QueryFrame(scrollFrame); |
9595 | 0 | MOZ_ASSERT(subFrame); |
9596 | 0 | // Get the bounds of the scroll frame in the same coordinate space |
9597 | 0 | // as |result|. |
9598 | 0 | CSSRect subFrameRect = CSSRect::FromAppUnits( |
9599 | 0 | nsLayoutUtils::TransformFrameRectToAncestor( |
9600 | 0 | subFrame, |
9601 | 0 | subFrame->GetRectRelativeToSelf(), |
9602 | 0 | relativeTo)); |
9603 | 0 |
|
9604 | 0 | result = subFrameRect.Intersect(result); |
9605 | 0 | } |
9606 | 0 | } |
9607 | 0 | return result; |
9608 | 0 | } |
9609 | | |
9610 | | static already_AddRefed<nsIPresShell> |
9611 | | GetPresShell(const nsIContent* aContent) |
9612 | 0 | { |
9613 | 0 | nsCOMPtr<nsIPresShell> result; |
9614 | 0 | if (nsIDocument* doc = aContent->GetComposedDoc()) { |
9615 | 0 | result = doc->GetShell(); |
9616 | 0 | } |
9617 | 0 | return result.forget(); |
9618 | 0 | } |
9619 | | |
9620 | 0 | static void UpdateDisplayPortMarginsForPendingMetrics(FrameMetrics& aMetrics) { |
9621 | 0 | nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId()); |
9622 | 0 | if (!content) { |
9623 | 0 | return; |
9624 | 0 | } |
9625 | 0 | |
9626 | 0 | nsCOMPtr<nsIPresShell> shell = GetPresShell(content); |
9627 | 0 | if (!shell) { |
9628 | 0 | return; |
9629 | 0 | } |
9630 | 0 | |
9631 | 0 | MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins()); |
9632 | 0 |
|
9633 | 0 | if (gfxPrefs::APZAllowZooming() && aMetrics.IsRootContent()) { |
9634 | 0 | // See APZCCallbackHelper::UpdateRootFrame for details. |
9635 | 0 | float presShellResolution = shell->GetResolution(); |
9636 | 0 | if (presShellResolution != aMetrics.GetPresShellResolution()) { |
9637 | 0 | return; |
9638 | 0 | } |
9639 | 0 | } |
9640 | 0 | |
9641 | 0 | nsIScrollableFrame* frame = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); |
9642 | 0 |
|
9643 | 0 | if (!frame) { |
9644 | 0 | return; |
9645 | 0 | } |
9646 | 0 | |
9647 | 0 | if (APZCCallbackHelper::IsScrollInProgress(frame)) { |
9648 | 0 | // If these conditions are true, then the UpdateFrame |
9649 | 0 | // message may be ignored by the main-thread, so we |
9650 | 0 | // shouldn't update the displayport based on it. |
9651 | 0 | return; |
9652 | 0 | } |
9653 | 0 | |
9654 | 0 | DisplayPortMarginsPropertyData* currentData = |
9655 | 0 | static_cast<DisplayPortMarginsPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPortMargins)); |
9656 | 0 | if (!currentData) { |
9657 | 0 | return; |
9658 | 0 | } |
9659 | 0 | |
9660 | 0 | CSSPoint frameScrollOffset = CSSPoint::FromAppUnits(frame->GetScrollPosition()); |
9661 | 0 | APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, frameScrollOffset); |
9662 | 0 |
|
9663 | 0 | nsLayoutUtils::SetDisplayPortMargins(content, shell, |
9664 | 0 | aMetrics.GetDisplayPortMargins(), 0); |
9665 | 0 | } |
9666 | | |
9667 | | /* static */ void |
9668 | | nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages() |
9669 | 0 | { |
9670 | 0 | if (XRE_IsContentProcess() && |
9671 | 0 | mozilla::layers::CompositorBridgeChild::Get() && |
9672 | 0 | mozilla::layers::CompositorBridgeChild::Get()->GetIPCChannel()) { |
9673 | 0 | CompositorBridgeChild::Get()->GetIPCChannel()->PeekMessages( |
9674 | 0 | [](const IPC::Message& aMsg) -> bool { |
9675 | 0 | if (aMsg.type() == mozilla::layers::PAPZ::Msg_RequestContentRepaint__ID) { |
9676 | 0 | PickleIterator iter(aMsg); |
9677 | 0 | FrameMetrics frame; |
9678 | 0 | if (!IPC::ReadParam(&aMsg, &iter, &frame)) { |
9679 | 0 | MOZ_ASSERT(false); |
9680 | 0 | return true; |
9681 | 0 | } |
9682 | 0 |
|
9683 | 0 | UpdateDisplayPortMarginsForPendingMetrics(frame); |
9684 | 0 | } |
9685 | 0 | return true; |
9686 | 0 | }); |
9687 | 0 | } |
9688 | 0 | } |
9689 | | |
9690 | | /* static */ bool |
9691 | | nsLayoutUtils::IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame) |
9692 | 0 | { |
9693 | 0 | for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) { |
9694 | 0 | if (f->IsTransformed()) { |
9695 | 0 | return true; |
9696 | 0 | } |
9697 | 0 | } |
9698 | 0 | return false; |
9699 | 0 | } |
9700 | | |
9701 | | /*static*/ CSSPoint |
9702 | | nsLayoutUtils::GetCumulativeApzCallbackTransform(nsIFrame* aFrame) |
9703 | 0 | { |
9704 | 0 | CSSPoint delta; |
9705 | 0 | if (!aFrame) { |
9706 | 0 | return delta; |
9707 | 0 | } |
9708 | 0 | nsIFrame* frame = aFrame; |
9709 | 0 | nsCOMPtr<nsIContent> content = frame->GetContent(); |
9710 | 0 | nsCOMPtr<nsIContent> lastContent; |
9711 | 0 | while (frame) { |
9712 | 0 | if (content && (content != lastContent)) { |
9713 | 0 | void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform); |
9714 | 0 | if (property) { |
9715 | 0 | delta += *static_cast<CSSPoint*>(property); |
9716 | 0 | } |
9717 | 0 | } |
9718 | 0 | frame = GetCrossDocParentFrame(frame); |
9719 | 0 | lastContent = content; |
9720 | 0 | content = frame ? frame->GetContent() : nullptr; |
9721 | 0 | } |
9722 | 0 | return delta; |
9723 | 0 | } |
9724 | | |
9725 | | /* static */ nsRect |
9726 | | nsLayoutUtils::ComputePartialPrerenderArea(const nsRect& aDirtyRect, |
9727 | | const nsRect& aOverflow, |
9728 | | const nsSize& aPrerenderSize) |
9729 | 0 | { |
9730 | 0 | // Simple calculation for now: center the pre-render area on the dirty rect, |
9731 | 0 | // and clamp to the overflow area. Later we can do more advanced things like |
9732 | 0 | // redistributing from one axis to another, or from one side to another. |
9733 | 0 | nscoord xExcess = std::max(aPrerenderSize.width - aDirtyRect.width, 0); |
9734 | 0 | nscoord yExcess = std::max(aPrerenderSize.height - aDirtyRect.height, 0); |
9735 | 0 | nsRect result = aDirtyRect; |
9736 | 0 | result.Inflate(xExcess / 2, yExcess / 2); |
9737 | 0 | return result.MoveInsideAndClamp(aOverflow); |
9738 | 0 | } |
9739 | | |
9740 | | static |
9741 | | bool |
9742 | | LineHasNonEmptyContentWorker(nsIFrame* aFrame) |
9743 | 0 | { |
9744 | 0 | // Look for non-empty frames, but ignore inline and br frames. |
9745 | 0 | // For inline frames, descend into the children, if any. |
9746 | 0 | if (aFrame->IsInlineFrame()) { |
9747 | 0 | for (nsIFrame* child : aFrame->PrincipalChildList()) { |
9748 | 0 | if (LineHasNonEmptyContentWorker(child)) { |
9749 | 0 | return true; |
9750 | 0 | } |
9751 | 0 | } |
9752 | 0 | } else { |
9753 | 0 | if (!aFrame->IsBrFrame() && !aFrame->IsEmpty()) { |
9754 | 0 | return true; |
9755 | 0 | } |
9756 | 0 | } |
9757 | 0 | return false; |
9758 | 0 | } |
9759 | | |
9760 | | static |
9761 | | bool |
9762 | | LineHasNonEmptyContent(nsLineBox* aLine) |
9763 | 0 | { |
9764 | 0 | int32_t count = aLine->GetChildCount(); |
9765 | 0 | for (nsIFrame* frame = aLine->mFirstChild; count > 0; |
9766 | 0 | --count, frame = frame->GetNextSibling()) { |
9767 | 0 | if (LineHasNonEmptyContentWorker(frame)) { |
9768 | 0 | return true; |
9769 | 0 | } |
9770 | 0 | } |
9771 | 0 | return false; |
9772 | 0 | } |
9773 | | |
9774 | | /* static */ bool |
9775 | | nsLayoutUtils::IsInvisibleBreak(nsINode* aNode, nsIFrame** aNextLineFrame) |
9776 | 0 | { |
9777 | 0 | if (aNextLineFrame) { |
9778 | 0 | *aNextLineFrame = nullptr; |
9779 | 0 | } |
9780 | 0 |
|
9781 | 0 | if (!aNode->IsElement() || !aNode->IsEditable()) { |
9782 | 0 | return false; |
9783 | 0 | } |
9784 | 0 | nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame(); |
9785 | 0 | if (!frame || !frame->IsBrFrame()) { |
9786 | 0 | return false; |
9787 | 0 | } |
9788 | 0 | |
9789 | 0 | nsContainerFrame* f = frame->GetParent(); |
9790 | 0 | while (f && f->IsFrameOfType(nsBox::eLineParticipant)) { |
9791 | 0 | f = f->GetParent(); |
9792 | 0 | } |
9793 | 0 | nsBlockFrame* blockAncestor = do_QueryFrame(f); |
9794 | 0 | if (!blockAncestor) { |
9795 | 0 | // The container frame doesn't support line breaking. |
9796 | 0 | return false; |
9797 | 0 | } |
9798 | 0 | |
9799 | 0 | bool valid = false; |
9800 | 0 | nsBlockInFlowLineIterator iter(blockAncestor, frame, &valid); |
9801 | 0 | if (!valid) { |
9802 | 0 | return false; |
9803 | 0 | } |
9804 | 0 | |
9805 | 0 | bool lineNonEmpty = LineHasNonEmptyContent(iter.GetLine()); |
9806 | 0 | if (!lineNonEmpty) { |
9807 | 0 | return false; |
9808 | 0 | } |
9809 | 0 | |
9810 | 0 | while (iter.Next()) { |
9811 | 0 | auto currentLine = iter.GetLine(); |
9812 | 0 | // Completely skip empty lines. |
9813 | 0 | if (!currentLine->IsEmpty()) { |
9814 | 0 | // If we come across an inline line, the BR has caused a visible line break. |
9815 | 0 | if (currentLine->IsInline()) { |
9816 | 0 | if (aNextLineFrame) { |
9817 | 0 | *aNextLineFrame = currentLine->mFirstChild; |
9818 | 0 | } |
9819 | 0 | return false; |
9820 | 0 | } |
9821 | 0 | break; |
9822 | 0 | } |
9823 | 0 | } |
9824 | 0 |
|
9825 | 0 | return lineNonEmpty; |
9826 | 0 | } |
9827 | | |
9828 | | static nsRect |
9829 | | ComputeSVGReferenceRect(nsIFrame* aFrame, |
9830 | | StyleGeometryBox aGeometryBox) |
9831 | 0 | { |
9832 | 0 | MOZ_ASSERT(aFrame->GetContent()->IsSVGElement()); |
9833 | 0 | nsRect r; |
9834 | 0 |
|
9835 | 0 | // For SVG elements without associated CSS layout box, the used value for |
9836 | 0 | // content-box, padding-box, border-box and margin-box is fill-box. |
9837 | 0 | switch (aGeometryBox) { |
9838 | 0 | case StyleGeometryBox::StrokeBox: { |
9839 | 0 | // XXX Bug 1299876 |
9840 | 0 | // The size of srtoke-box is not correct if this graphic element has |
9841 | 0 | // specific stroke-linejoin or stroke-linecap. |
9842 | 0 | gfxRect bbox = nsSVGUtils::GetBBox(aFrame, |
9843 | 0 | nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeStroke); |
9844 | 0 | r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, |
9845 | 0 | AppUnitsPerCSSPixel()); |
9846 | 0 | break; |
9847 | 0 | } |
9848 | 0 | case StyleGeometryBox::ViewBox: { |
9849 | 0 | nsIContent* content = aFrame->GetContent(); |
9850 | 0 | nsSVGElement* element = static_cast<nsSVGElement*>(content); |
9851 | 0 | SVGViewportElement* svgElement = element->GetCtx(); |
9852 | 0 | MOZ_ASSERT(svgElement); |
9853 | 0 |
|
9854 | 0 | if (svgElement && svgElement->HasViewBoxRect()) { |
9855 | 0 | // If a ‘viewBox‘ attribute is specified for the SVG viewport creating |
9856 | 0 | // element: |
9857 | 0 | // 1. The reference box is positioned at the origin of the coordinate |
9858 | 0 | // system established by the ‘viewBox‘ attribute. |
9859 | 0 | // 2. The dimension of the reference box is set to the width and height |
9860 | 0 | // values of the ‘viewBox‘ attribute. |
9861 | 0 | nsSVGViewBox* viewBox = svgElement->GetViewBox(); |
9862 | 0 | const nsSVGViewBoxRect& value = viewBox->GetAnimValue(); |
9863 | 0 | r = nsRect(nsPresContext::CSSPixelsToAppUnits(value.x), |
9864 | 0 | nsPresContext::CSSPixelsToAppUnits(value.y), |
9865 | 0 | nsPresContext::CSSPixelsToAppUnits(value.width), |
9866 | 0 | nsPresContext::CSSPixelsToAppUnits(value.height)); |
9867 | 0 | } else { |
9868 | 0 | // No viewBox is specified, uses the nearest SVG viewport as reference |
9869 | 0 | // box. |
9870 | 0 | svgFloatSize viewportSize = svgElement->GetViewportSize(); |
9871 | 0 | r = nsRect(0, 0, |
9872 | 0 | nsPresContext::CSSPixelsToAppUnits(viewportSize.width), |
9873 | 0 | nsPresContext::CSSPixelsToAppUnits(viewportSize.height)); |
9874 | 0 | } |
9875 | 0 |
|
9876 | 0 | break; |
9877 | 0 | } |
9878 | 0 | case StyleGeometryBox::NoBox: |
9879 | 0 | case StyleGeometryBox::BorderBox: |
9880 | 0 | case StyleGeometryBox::ContentBox: |
9881 | 0 | case StyleGeometryBox::PaddingBox: |
9882 | 0 | case StyleGeometryBox::MarginBox: |
9883 | 0 | case StyleGeometryBox::FillBox: { |
9884 | 0 | gfxRect bbox = nsSVGUtils::GetBBox(aFrame, |
9885 | 0 | nsSVGUtils::eBBoxIncludeFill); |
9886 | 0 | r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, |
9887 | 0 | AppUnitsPerCSSPixel()); |
9888 | 0 | break; |
9889 | 0 | } |
9890 | 0 | default:{ |
9891 | 0 | MOZ_ASSERT_UNREACHABLE("unknown StyleGeometryBox type"); |
9892 | 0 | gfxRect bbox = nsSVGUtils::GetBBox(aFrame, |
9893 | 0 | nsSVGUtils::eBBoxIncludeFill); |
9894 | 0 | r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, |
9895 | 0 | AppUnitsPerCSSPixel()); |
9896 | 0 | break; |
9897 | 0 | } |
9898 | 0 | } |
9899 | 0 |
|
9900 | 0 | return r; |
9901 | 0 | } |
9902 | | |
9903 | | static nsRect |
9904 | | ComputeHTMLReferenceRect(nsIFrame* aFrame, |
9905 | | StyleGeometryBox aGeometryBox) |
9906 | 0 | { |
9907 | 0 | nsRect r; |
9908 | 0 |
|
9909 | 0 | // For elements with associated CSS layout box, the used value for fill-box, |
9910 | 0 | // stroke-box and view-box is border-box. |
9911 | 0 | switch (aGeometryBox) { |
9912 | 0 | case StyleGeometryBox::ContentBox: |
9913 | 0 | r = aFrame->GetContentRectRelativeToSelf(); |
9914 | 0 | break; |
9915 | 0 | case StyleGeometryBox::PaddingBox: |
9916 | 0 | r = aFrame->GetPaddingRectRelativeToSelf(); |
9917 | 0 | break; |
9918 | 0 | case StyleGeometryBox::MarginBox: |
9919 | 0 | r = aFrame->GetMarginRectRelativeToSelf(); |
9920 | 0 | break; |
9921 | 0 | case StyleGeometryBox::NoBox: |
9922 | 0 | case StyleGeometryBox::BorderBox: |
9923 | 0 | case StyleGeometryBox::FillBox: |
9924 | 0 | case StyleGeometryBox::StrokeBox: |
9925 | 0 | case StyleGeometryBox::ViewBox: |
9926 | 0 | r = aFrame->GetRectRelativeToSelf(); |
9927 | 0 | break; |
9928 | 0 | default: |
9929 | 0 | MOZ_ASSERT_UNREACHABLE("unknown StyleGeometryBox type"); |
9930 | 0 | r = aFrame->GetRectRelativeToSelf(); |
9931 | 0 | break; |
9932 | 0 | } |
9933 | 0 |
|
9934 | 0 | return r; |
9935 | 0 | } |
9936 | | |
9937 | | /* static */ nsRect |
9938 | | nsLayoutUtils::ComputeGeometryBox(nsIFrame* aFrame, |
9939 | | StyleGeometryBox aGeometryBox) |
9940 | 0 | { |
9941 | 0 | // We use ComputeSVGReferenceRect for all SVG elements, except <svg> |
9942 | 0 | // element, which does have an associated CSS layout box. In this case we |
9943 | 0 | // should still use ComputeHTMLReferenceRect for region computing. |
9944 | 0 | nsRect r = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) |
9945 | 0 | ? ComputeSVGReferenceRect(aFrame, aGeometryBox) |
9946 | 0 | : ComputeHTMLReferenceRect(aFrame, aGeometryBox); |
9947 | 0 |
|
9948 | 0 | return r; |
9949 | 0 | } |
9950 | | |
9951 | | /* static */ nsPoint |
9952 | | nsLayoutUtils::ComputeOffsetToUserSpace(nsDisplayListBuilder* aBuilder, |
9953 | | nsIFrame* aFrame) |
9954 | 0 | { |
9955 | 0 | nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(aFrame) - |
9956 | 0 | nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame); |
9957 | 0 | if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { |
9958 | 0 | // Snap the offset if the reference frame is not a SVG frame, since other |
9959 | 0 | // frames will be snapped to pixel when rendering. |
9960 | 0 | offsetToBoundingBox = nsPoint( |
9961 | 0 | aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x), |
9962 | 0 | aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y)); |
9963 | 0 | } |
9964 | 0 |
|
9965 | 0 | // During SVG painting, the offset computed here is applied to the gfxContext |
9966 | 0 | // "ctx" used to paint the mask. After applying only "offsetToBoundingBox", |
9967 | 0 | // "ctx" would have its origin at the top left corner of frame's bounding box |
9968 | 0 | // (over all continuations). |
9969 | 0 | // However, SVG painting needs the origin to be located at the origin of the |
9970 | 0 | // SVG frame's "user space", i.e. the space in which, for example, the |
9971 | 0 | // frame's BBox lives. |
9972 | 0 | // SVG geometry frames and foreignObject frames apply their own offsets, so |
9973 | 0 | // their position is relative to their user space. So for these frame types, |
9974 | 0 | // if we want "ctx" to be in user space, we first need to subtract the |
9975 | 0 | // frame's position so that SVG painting can later add it again and the |
9976 | 0 | // frame is painted in the right place. |
9977 | 0 | gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); |
9978 | 0 | nsPoint toUserSpace = |
9979 | 0 | nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), |
9980 | 0 | nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); |
9981 | 0 |
|
9982 | 0 | return (offsetToBoundingBox - toUserSpace); |
9983 | 0 | } |
9984 | | |
9985 | | /* static */ uint8_t |
9986 | | nsLayoutUtils::ControlCharVisibilityDefault() |
9987 | 0 | { |
9988 | 0 | return StaticPrefs::layout_css_control_characters_visible() |
9989 | 0 | ? NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE |
9990 | 0 | : NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN; |
9991 | 0 | } |
9992 | | |
9993 | | /* static */ |
9994 | | already_AddRefed<nsFontMetrics> |
9995 | | nsLayoutUtils::GetMetricsFor(nsPresContext* aPresContext, |
9996 | | bool aIsVertical, |
9997 | | const nsStyleFont* aStyleFont, |
9998 | | nscoord aFontSize, |
9999 | | bool aUseUserFontSet, |
10000 | | FlushUserFontSet aFlushUserFontSet) |
10001 | 0 | { |
10002 | 0 | nsFont font = aStyleFont->mFont; |
10003 | 0 | font.size = aFontSize; |
10004 | 0 | gfxFont::Orientation orientation |
10005 | 0 | = aIsVertical ? gfxFont::eVertical : gfxFont::eHorizontal; |
10006 | 0 | nsFontMetrics::Params params; |
10007 | 0 | params.language = aStyleFont->mLanguage; |
10008 | 0 | params.explicitLanguage = aStyleFont->mExplicitLanguage; |
10009 | 0 | params.orientation = orientation; |
10010 | 0 | params.userFontSet = aUseUserFontSet |
10011 | 0 | ? aPresContext->GetUserFontSet(aFlushUserFontSet == FlushUserFontSet::Yes) |
10012 | 0 | : nullptr; |
10013 | 0 | params.textPerf = aPresContext->GetTextPerfMetrics(); |
10014 | 0 | return aPresContext->DeviceContext()->GetMetricsFor(font, params); |
10015 | 0 | } |
10016 | | |
10017 | | /* static */ void |
10018 | | nsLayoutUtils::FixupNoneGeneric(nsFont* aFont, |
10019 | | const nsPresContext* aPresContext, |
10020 | | uint8_t aGenericFontID, |
10021 | | const nsFont* aDefaultVariableFont) |
10022 | 0 | { |
10023 | 0 | bool useDocumentFonts = |
10024 | 0 | aPresContext->GetCachedBoolPref(kPresContext_UseDocumentFonts); |
10025 | 0 | if (aGenericFontID == kGenericFont_NONE || |
10026 | 0 | (!useDocumentFonts && (aGenericFontID == kGenericFont_cursive || |
10027 | 0 | aGenericFontID == kGenericFont_fantasy))) { |
10028 | 0 | FontFamilyType defaultGeneric = |
10029 | 0 | aDefaultVariableFont->fontlist.GetDefaultFontType(); |
10030 | 0 | MOZ_ASSERT(aDefaultVariableFont->fontlist.IsEmpty() && |
10031 | 0 | (defaultGeneric == eFamily_serif || |
10032 | 0 | defaultGeneric == eFamily_sans_serif)); |
10033 | 0 | if (defaultGeneric != eFamily_none) { |
10034 | 0 | if (useDocumentFonts) { |
10035 | 0 | aFont->fontlist.SetDefaultFontType(defaultGeneric); |
10036 | 0 | } else { |
10037 | 0 | // Either prioritize the first generic in the list, |
10038 | 0 | // or (if there isn't one) prepend the default variable font. |
10039 | 0 | if (!aFont->fontlist.PrioritizeFirstGeneric()) { |
10040 | 0 | aFont->fontlist.PrependGeneric(defaultGeneric); |
10041 | 0 | } |
10042 | 0 | } |
10043 | 0 | } |
10044 | 0 | } else { |
10045 | 0 | aFont->fontlist.SetDefaultFontType(eFamily_none); |
10046 | 0 | } |
10047 | 0 | } |
10048 | | |
10049 | | /* static */ void |
10050 | | nsLayoutUtils::ApplyMinFontSize(nsStyleFont* aFont, |
10051 | | const nsPresContext* aPresContext, |
10052 | | nscoord aMinFontSize) |
10053 | 0 | { |
10054 | 0 | nscoord fontSize = aFont->mSize; |
10055 | 0 |
|
10056 | 0 | // enforce the user' specified minimum font-size on the value that we expose |
10057 | 0 | // (but don't change font-size:0, since that would unhide hidden text) |
10058 | 0 | if (fontSize > 0) { |
10059 | 0 | if (aMinFontSize < 0) { |
10060 | 0 | aMinFontSize = 0; |
10061 | 0 | } else { |
10062 | 0 | aMinFontSize = (aMinFontSize * aFont->mMinFontSizeRatio) / 100; |
10063 | 0 | } |
10064 | 0 | if (fontSize < aMinFontSize && !aPresContext->IsChrome()) { |
10065 | 0 | // override the minimum font-size constraint |
10066 | 0 | fontSize = aMinFontSize; |
10067 | 0 | } |
10068 | 0 | } |
10069 | 0 | aFont->mFont.size = fontSize; |
10070 | 0 | } |
10071 | | |
10072 | | /* static */ void |
10073 | | nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont, LookAndFeel::FontID aFontID, |
10074 | | const nsPresContext* aPresContext, |
10075 | | const nsFont* aDefaultVariableFont) |
10076 | 0 | { |
10077 | 0 | gfxFontStyle fontStyle; |
10078 | 0 | float devPerCSS = |
10079 | 0 | (float)AppUnitsPerCSSPixel() / |
10080 | 0 | aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom(); |
10081 | 0 | nsAutoString systemFontName; |
10082 | 0 | if (LookAndFeel::GetFont(aFontID, systemFontName, fontStyle, devPerCSS)) { |
10083 | 0 | systemFontName.Trim("\"'"); |
10084 | 0 | aSystemFont->fontlist = |
10085 | 0 | FontFamilyList(NS_ConvertUTF16toUTF8(systemFontName), eUnquotedName); |
10086 | 0 | aSystemFont->fontlist.SetDefaultFontType(eFamily_none); |
10087 | 0 | aSystemFont->style = fontStyle.style; |
10088 | 0 | aSystemFont->systemFont = fontStyle.systemFont; |
10089 | 0 | aSystemFont->weight = fontStyle.weight; |
10090 | 0 | aSystemFont->stretch = fontStyle.stretch; |
10091 | 0 | aSystemFont->size = |
10092 | 0 | NSFloatPixelsToAppUnits(fontStyle.size, |
10093 | 0 | aPresContext->DeviceContext()-> |
10094 | 0 | AppUnitsPerDevPixelAtUnitFullZoom()); |
10095 | 0 | //aSystemFont->langGroup = fontStyle.langGroup; |
10096 | 0 | aSystemFont->sizeAdjust = fontStyle.sizeAdjust; |
10097 | 0 |
|
10098 | | #ifdef XP_WIN |
10099 | | // XXXldb This platform-specific stuff should be in the |
10100 | | // LookAndFeel implementation, not here. |
10101 | | // XXXzw Should we even still *have* this code? It looks to be making |
10102 | | // old, probably obsolete assumptions. |
10103 | | |
10104 | | if (aFontID == LookAndFeel::eFont_Field || |
10105 | | aFontID == LookAndFeel::eFont_Button || |
10106 | | aFontID == LookAndFeel::eFont_List) { |
10107 | | // As far as I can tell the system default fonts and sizes |
10108 | | // on MS-Windows for Buttons, Listboxes/Comboxes and Text Fields are |
10109 | | // all pre-determined and cannot be changed by either the control panel |
10110 | | // or programmatically. |
10111 | | // Fields (text fields) |
10112 | | // Button and Selects (listboxes/comboboxes) |
10113 | | // We use whatever font is defined by the system. Which it appears |
10114 | | // (and the assumption is) it is always a proportional font. Then we |
10115 | | // always use 2 points smaller than what the browser has defined as |
10116 | | // the default proportional font. |
10117 | | // Assumption: system defined font is proportional |
10118 | | aSystemFont->size = |
10119 | | std::max(aDefaultVariableFont->size - |
10120 | | nsPresContext::CSSPointsToAppUnits(2), 0); |
10121 | | } |
10122 | | #endif |
10123 | | } |
10124 | 0 | } |
10125 | | |
10126 | | static inline void |
10127 | | AssertValidFontTag(const nsString& aString) |
10128 | 0 | { |
10129 | 0 | // To be valid as a font feature tag, a string MUST be: |
10130 | 0 | MOZ_ASSERT(aString.Length() == 4 && // (1) exactly 4 chars long |
10131 | 0 | NS_IsAscii(aString.BeginReading()) && // (2) entirely ASCII |
10132 | 0 | isprint(aString[0]) && // (3) all printable chars |
10133 | 0 | isprint(aString[1]) && |
10134 | 0 | isprint(aString[2]) && |
10135 | 0 | isprint(aString[3])); |
10136 | 0 | } |
10137 | | |
10138 | | /* static */ void |
10139 | | nsLayoutUtils::ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList, |
10140 | | nsTArray<gfxFontFeature>& aFeatureSettings) |
10141 | 0 | { |
10142 | 0 | aFeatureSettings.Clear(); |
10143 | 0 | for (const nsCSSValuePairList* p = aFeaturesList; p; p = p->mNext) { |
10144 | 0 | gfxFontFeature feat; |
10145 | 0 |
|
10146 | 0 | MOZ_ASSERT(aFeaturesList->mXValue.GetUnit() == eCSSUnit_String, |
10147 | 0 | "unexpected value unit"); |
10148 | 0 |
|
10149 | 0 | // tag is a 4-byte ASCII sequence |
10150 | 0 | nsAutoString tag; |
10151 | 0 | p->mXValue.GetStringValue(tag); |
10152 | 0 | AssertValidFontTag(tag); |
10153 | 0 | if (tag.Length() != 4) { |
10154 | 0 | continue; |
10155 | 0 | } |
10156 | 0 | // parsing validates that these are ASCII chars |
10157 | 0 | // tags are always big-endian |
10158 | 0 | feat.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8) | tag[3]; |
10159 | 0 |
|
10160 | 0 | // value |
10161 | 0 | NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Integer, |
10162 | 0 | "should have found an integer unit"); |
10163 | 0 | feat.mValue = p->mYValue.GetIntValue(); |
10164 | 0 |
|
10165 | 0 | aFeatureSettings.AppendElement(feat); |
10166 | 0 | } |
10167 | 0 | } |
10168 | | |
10169 | | /* static */ void |
10170 | | nsLayoutUtils::ComputeFontVariations(const nsCSSValuePairList* aVariationsList, |
10171 | | nsTArray<gfxFontVariation>& aVariationSettings) |
10172 | 0 | { |
10173 | 0 | aVariationSettings.Clear(); |
10174 | 0 | for (const nsCSSValuePairList* p = aVariationsList; p; p = p->mNext) { |
10175 | 0 | gfxFontVariation var; |
10176 | 0 |
|
10177 | 0 | MOZ_ASSERT(aVariationsList->mXValue.GetUnit() == eCSSUnit_String, |
10178 | 0 | "unexpected value unit"); |
10179 | 0 |
|
10180 | 0 | // tag is a 4-byte ASCII sequence |
10181 | 0 | nsAutoString tag; |
10182 | 0 | p->mXValue.GetStringValue(tag); |
10183 | 0 | AssertValidFontTag(tag); |
10184 | 0 | if (tag.Length() != 4) { |
10185 | 0 | continue; |
10186 | 0 | } |
10187 | 0 | // parsing validates that these are ASCII chars |
10188 | 0 | // tags are always big-endian |
10189 | 0 | var.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8) | tag[3]; |
10190 | 0 |
|
10191 | 0 | // value |
10192 | 0 | NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Number, |
10193 | 0 | "should have found a number unit"); |
10194 | 0 | var.mValue = p->mYValue.GetFloatValue(); |
10195 | 0 |
|
10196 | 0 | aVariationSettings.AppendElement(var); |
10197 | 0 | } |
10198 | 0 | } |
10199 | | |
10200 | | /* static */ uint32_t |
10201 | | nsLayoutUtils::ParseFontLanguageOverride(const nsAString& aLangTag) |
10202 | 0 | { |
10203 | 0 | if (!aLangTag.Length() || aLangTag.Length() > 4) { |
10204 | 0 | return NO_FONT_LANGUAGE_OVERRIDE; |
10205 | 0 | } |
10206 | 0 | uint32_t index, result = 0; |
10207 | 0 | for (index = 0; index < aLangTag.Length(); ++index) { |
10208 | 0 | char16_t ch = aLangTag[index]; |
10209 | 0 | if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII |
10210 | 0 | return NO_FONT_LANGUAGE_OVERRIDE; |
10211 | 0 | } |
10212 | 0 | result = (result << 8) + ch; |
10213 | 0 | } |
10214 | 0 | while (index++ < 4) { |
10215 | 0 | result = (result << 8) + 0x20; |
10216 | 0 | } |
10217 | 0 | return result; |
10218 | 0 | } |
10219 | | |
10220 | | /* static */ ComputedStyle* |
10221 | | nsLayoutUtils::StyleForScrollbar(nsIFrame* aScrollbarPart) |
10222 | 0 | { |
10223 | 0 | // Get the closest content node which is not an anonymous scrollbar |
10224 | 0 | // part. It should be the originating element of the scrollbar part. |
10225 | 0 | nsIContent* content = aScrollbarPart->GetContent(); |
10226 | 0 | // Note that the content may be a normal element with scrollbar part |
10227 | 0 | // value specified for its -moz-appearance, so don't rely on it being |
10228 | 0 | // a native anonymous. Also note that we have to check the node name |
10229 | 0 | // because anonymous element like generated content may originate a |
10230 | 0 | // scrollbar. |
10231 | 0 | MOZ_ASSERT(content, "No content for the scrollbar part?"); |
10232 | 0 | while (content && content->IsInNativeAnonymousSubtree() && |
10233 | 0 | content->IsAnyOfXULElements(nsGkAtoms::scrollbar, |
10234 | 0 | nsGkAtoms::scrollbarbutton, |
10235 | 0 | nsGkAtoms::scrollcorner, |
10236 | 0 | nsGkAtoms::slider, |
10237 | 0 | nsGkAtoms::thumb)) { |
10238 | 0 | content = content->GetParent(); |
10239 | 0 | } |
10240 | 0 | MOZ_ASSERT(content, "Native anonymous element with no originating node?"); |
10241 | 0 | // Use the style from the primary frame of the content. |
10242 | 0 | // Note: it is important to use the primary frame rather than an |
10243 | 0 | // ancestor frame of the scrollbar part for the correct handling of |
10244 | 0 | // viewport scrollbar. The content of the scroll frame of the viewport |
10245 | 0 | // is the root element, but its style inherits from the viewport. |
10246 | 0 | // Since we need to use the style of root element for the viewport |
10247 | 0 | // scrollbar, we have to get the style from the primary frame. |
10248 | 0 | if (nsIFrame* primaryFrame = content->GetPrimaryFrame()) { |
10249 | 0 | return primaryFrame->Style(); |
10250 | 0 | } |
10251 | 0 | // If the element doesn't have primary frame, get the computed style |
10252 | 0 | // from the element directly. This can happen on viewport, because |
10253 | 0 | // the scrollbar of viewport may be shown when the root element has |
10254 | 0 | // > display: none; overflow: scroll; |
10255 | 0 | nsPresContext* pc = aScrollbarPart->PresContext(); |
10256 | 0 | MOZ_ASSERT(content == pc->Document()->GetRootElement(), |
10257 | 0 | "Root element is the only case for this fallback " |
10258 | 0 | "path to be triggered"); |
10259 | 0 | RefPtr<ComputedStyle> style = |
10260 | 0 | pc->StyleSet()->ResolveServoStyle(*content->AsElement()); |
10261 | 0 | // Dropping the strong reference is fine because the style should be |
10262 | 0 | // held strongly by the element. |
10263 | 0 | return style.get(); |
10264 | 0 | } |
10265 | | |
10266 | | static float |
10267 | | ResolveTransformOrigin(const nsStyleCoord& aCoord, |
10268 | | TransformReferenceBox& aRefBox, |
10269 | | TransformReferenceBox::DimensionGetter aGetter) |
10270 | 0 | { |
10271 | 0 | float result = 0.0; |
10272 | 0 | const float scale = mozilla::AppUnitsPerCSSPixel(); |
10273 | 0 | if (aCoord.GetUnit() == eStyleUnit_Calc) { |
10274 | 0 | const nsStyleCoord::Calc *calc = aCoord.GetCalcValue(); |
10275 | 0 | result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) * |
10276 | 0 | calc->mPercent + |
10277 | 0 | NSAppUnitsToFloatPixels(calc->mLength, scale); |
10278 | 0 | } else if (aCoord.GetUnit() == eStyleUnit_Percent) { |
10279 | 0 | result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) * |
10280 | 0 | aCoord.GetPercentValue(); |
10281 | 0 | } else { |
10282 | 0 | MOZ_ASSERT(aCoord.GetUnit() == eStyleUnit_Coord, "unexpected unit"); |
10283 | 0 | result = NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), scale); |
10284 | 0 | } |
10285 | 0 | return result; |
10286 | 0 | } |
10287 | | |
10288 | | /* static */ Maybe<MotionPathData> |
10289 | | nsLayoutUtils::ResolveMotionPath(const nsIFrame* aFrame) |
10290 | 0 | { |
10291 | 0 | MOZ_ASSERT(aFrame); |
10292 | 0 |
|
10293 | 0 | const nsStyleDisplay* display = aFrame->StyleDisplay(); |
10294 | 0 | if (!display->mMotion || !display->mMotion->HasPath()) { |
10295 | 0 | return Nothing(); |
10296 | 0 | } |
10297 | 0 | |
10298 | 0 | const UniquePtr<StyleMotion>& motion = display->mMotion; |
10299 | 0 | // Bug 1429299 - Implement offset-distance for motion path. For now, we use |
10300 | 0 | // the default value, i.e. 0%. |
10301 | 0 | float distance = 0.0; |
10302 | 0 | float angle = 0.0; |
10303 | 0 | Point point; |
10304 | 0 | if (motion->OffsetPath().GetType() == StyleShapeSourceType::Path) { |
10305 | 0 | // Build the path and compute the point and angle for creating the |
10306 | 0 | // equivalent translate and rotate. |
10307 | 0 | // Here we only need to build a valid path for motion path, so |
10308 | 0 | // using the default values of stroke-width, stoke-linecap, and fill-rule |
10309 | 0 | // is fine for now because what we want is get the point and its normal |
10310 | 0 | // vector along the path, instead of rendering it. |
10311 | 0 | // FIXME: Bug 1484780, we should cache the path to avoid rebuilding it here |
10312 | 0 | // at every restyle. (Caching the path avoids the cost of flattening it |
10313 | 0 | // again each time.) |
10314 | 0 | RefPtr<DrawTarget> drawTarget = |
10315 | 0 | gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); |
10316 | 0 | RefPtr<PathBuilder> builder = |
10317 | 0 | drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); |
10318 | 0 | RefPtr<gfx::Path> gfxPath = |
10319 | 0 | SVGPathData::BuildPath(motion->OffsetPath().GetPath()->Path(), |
10320 | 0 | builder, |
10321 | 0 | NS_STYLE_STROKE_LINECAP_BUTT, |
10322 | 0 | 0.0); |
10323 | 0 | if (!gfxPath) { |
10324 | 0 | return Nothing(); |
10325 | 0 | } |
10326 | 0 | float pathLength = gfxPath->ComputeLength(); |
10327 | 0 | float computedDistance = distance * pathLength; |
10328 | 0 | Point tangent; |
10329 | 0 | point = gfxPath->ComputePointAtLength(computedDistance, &tangent); |
10330 | 0 | // Bug 1429301 - Implement offset-rotate for motion path. |
10331 | 0 | // After implement offset-rotate, |angle| will be adjusted more. |
10332 | 0 | // For now, the default value of offset-rotate is "auto", so we use the |
10333 | 0 | // directional tangent vector. |
10334 | 0 | angle = atan2(tangent.y, tangent.x); |
10335 | 0 | } else { |
10336 | 0 | // Bug 1480665: Implement ray() function. |
10337 | 0 | NS_WARNING("Unsupported offset-path value"); |
10338 | 0 | } |
10339 | 0 |
|
10340 | 0 | // Compute the offset for motion path translate. |
10341 | 0 | // We need to resolve transform-origin here to calculate the correct path |
10342 | 0 | // translate. (i.e. Center transform-origin on the path.) |
10343 | 0 | TransformReferenceBox refBox(aFrame); |
10344 | 0 | Point origin( |
10345 | 0 | ResolveTransformOrigin(display->mTransformOrigin[0], |
10346 | 0 | refBox, |
10347 | 0 | &TransformReferenceBox::Width), |
10348 | 0 | ResolveTransformOrigin(display->mTransformOrigin[1], |
10349 | 0 | refBox, |
10350 | 0 | &TransformReferenceBox::Height) |
10351 | 0 | ); |
10352 | 0 | // Bug 1186329: the translate parameters will be adjusted more after we |
10353 | 0 | // implement offset-position and offset-anchor. |
10354 | 0 | return Some(MotionPathData { point - origin, angle }); |
10355 | 0 | } |