/src/mozilla-central/layout/generic/nsImageFrame.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 | | /* rendering object for replaced elements with image data */ |
8 | | |
9 | | #include "nsImageFrame.h" |
10 | | |
11 | | #include "gfx2DGlue.h" |
12 | | #include "gfxContext.h" |
13 | | #include "gfxUtils.h" |
14 | | #include "mozilla/ComputedStyle.h" |
15 | | #include "mozilla/DebugOnly.h" |
16 | | #include "mozilla/Encoding.h" |
17 | | #include "mozilla/EventStates.h" |
18 | | #include "mozilla/gfx/2D.h" |
19 | | #include "mozilla/gfx/Helpers.h" |
20 | | #include "mozilla/gfx/PathHelpers.h" |
21 | | #include "mozilla/dom/GeneratedImageContent.h" |
22 | | #include "mozilla/dom/HTMLImageElement.h" |
23 | | #include "mozilla/dom/ResponsiveImageSelector.h" |
24 | | #include "mozilla/layers/WebRenderLayerManager.h" |
25 | | #include "mozilla/MouseEvents.h" |
26 | | #include "mozilla/Unused.h" |
27 | | |
28 | | #include "nsCOMPtr.h" |
29 | | #include "nsFontMetrics.h" |
30 | | #include "nsIImageLoadingContent.h" |
31 | | #include "nsImageLoadingContent.h" |
32 | | #include "nsString.h" |
33 | | #include "nsPrintfCString.h" |
34 | | #include "nsPresContext.h" |
35 | | #include "nsIPresShell.h" |
36 | | #include "nsGkAtoms.h" |
37 | | #include "nsIDocument.h" |
38 | | #include "nsContentUtils.h" |
39 | | #include "nsCSSAnonBoxes.h" |
40 | | #include "nsStyleConsts.h" |
41 | | #include "nsStyleCoord.h" |
42 | | #include "nsStyleUtil.h" |
43 | | #include "nsTransform2D.h" |
44 | | #include "nsImageMap.h" |
45 | | #include "nsIIOService.h" |
46 | | #include "nsILoadGroup.h" |
47 | | #include "nsISupportsPriority.h" |
48 | | #include "nsNetUtil.h" |
49 | | #include "nsNetCID.h" |
50 | | #include "nsCSSRendering.h" |
51 | | #include "nsNameSpaceManager.h" |
52 | | #include <algorithm> |
53 | | #ifdef ACCESSIBILITY |
54 | | #include "nsAccessibilityService.h" |
55 | | #endif |
56 | | #include "nsLayoutUtils.h" |
57 | | #include "nsDisplayList.h" |
58 | | #include "nsIContent.h" |
59 | | #include "nsIDocument.h" |
60 | | #include "FrameLayerBuilder.h" |
61 | | #include "mozilla/dom/Selection.h" |
62 | | #include "nsIURIMutator.h" |
63 | | |
64 | | #include "imgIContainer.h" |
65 | | #include "imgLoader.h" |
66 | | #include "imgRequestProxy.h" |
67 | | |
68 | | #include "nsCSSFrameConstructor.h" |
69 | | #include "nsRange.h" |
70 | | |
71 | | #include "nsError.h" |
72 | | #include "nsBidiUtils.h" |
73 | | #include "nsBidiPresUtils.h" |
74 | | |
75 | | #include "gfxRect.h" |
76 | | #include "ImageLayers.h" |
77 | | #include "ImageContainer.h" |
78 | | #include "mozilla/ServoStyleSet.h" |
79 | | #include "nsBlockFrame.h" |
80 | | #include "nsStyleStructInlines.h" |
81 | | |
82 | | #include "mozilla/Preferences.h" |
83 | | |
84 | | #include "mozilla/dom/Link.h" |
85 | | #include "SVGImageContext.h" |
86 | | #include "mozilla/dom/HTMLAnchorElement.h" |
87 | | |
88 | | using namespace mozilla; |
89 | | using namespace mozilla::dom; |
90 | | using namespace mozilla::gfx; |
91 | | using namespace mozilla::image; |
92 | | using namespace mozilla::layers; |
93 | | |
94 | | // sizes (pixels) for image icon, padding and border frame |
95 | 0 | #define ICON_SIZE (16) |
96 | 0 | #define ICON_PADDING (3) |
97 | 0 | #define ALT_BORDER_WIDTH (1) |
98 | | |
99 | | // Default alignment value (so we can tell an unset value from a set value) |
100 | | #define ALIGN_UNSET uint8_t(-1) |
101 | | |
102 | | // static icon information |
103 | | StaticRefPtr<nsImageFrame::IconLoad> nsImageFrame::gIconLoad; |
104 | | |
105 | | // cached IO service for loading icons |
106 | | nsIIOService* nsImageFrame::sIOService; |
107 | | |
108 | | // test if the width and height are fixed, looking at the style data |
109 | | // This is used by nsImageFrame::ShouldCreateImageFrameFor and should |
110 | | // not be used for layout decisions. |
111 | | static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition) |
112 | 0 | { |
113 | 0 | // check the width and height values in the reflow state's style struct |
114 | 0 | // - if width and height are specified as either coord or percentage, then |
115 | 0 | // the size of the image frame is constrained |
116 | 0 | return aStylePosition->mWidth.IsCoordPercentCalcUnit() && |
117 | 0 | aStylePosition->mHeight.IsCoordPercentCalcUnit(); |
118 | 0 | } |
119 | | |
120 | | // Decide whether we can optimize away reflows that result from the |
121 | | // image's intrinsic size changing. |
122 | | inline bool HaveFixedSize(const ReflowInput& aReflowInput) |
123 | 0 | { |
124 | 0 | NS_ASSERTION(aReflowInput.mStylePosition, "crappy reflowInput - null stylePosition"); |
125 | 0 | // Don't try to make this optimization when an image has percentages |
126 | 0 | // in its 'width' or 'height'. The percentages might be treated like |
127 | 0 | // auto (especially for intrinsic width calculations and for heights). |
128 | 0 | return aReflowInput.mStylePosition->mHeight.ConvertsToLength() && |
129 | 0 | aReflowInput.mStylePosition->mWidth.ConvertsToLength(); |
130 | 0 | } |
131 | | |
132 | | nsIFrame* |
133 | | NS_NewImageFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
134 | 0 | { |
135 | 0 | return new (aPresShell) nsImageFrame(aStyle, nsImageFrame::Kind::ImageElement); |
136 | 0 | } |
137 | | |
138 | | nsIFrame* |
139 | | NS_NewImageFrameForContentProperty(nsIPresShell* aPresShell, |
140 | | ComputedStyle* aStyle) |
141 | 0 | { |
142 | 0 | return new (aPresShell) nsImageFrame( |
143 | 0 | aStyle, nsImageFrame::Kind::ContentProperty); |
144 | 0 | } |
145 | | |
146 | | nsIFrame* |
147 | | NS_NewImageFrameForGeneratedContentIndex(nsIPresShell* aPresShell, |
148 | | ComputedStyle* aStyle) |
149 | 0 | { |
150 | 0 | return new (aPresShell) nsImageFrame( |
151 | 0 | aStyle, nsImageFrame::Kind::ContentPropertyAtIndex); |
152 | 0 | } |
153 | | |
154 | | nsImageFrame* |
155 | | nsImageFrame::CreateContinuingFrame(nsIPresShell* aPresShell, |
156 | | ComputedStyle* aStyle) const |
157 | 0 | { |
158 | 0 | return new (aPresShell) nsImageFrame(aStyle, mKind); |
159 | 0 | } |
160 | | |
161 | | NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame) |
162 | | |
163 | | nsImageFrame::nsImageFrame(ComputedStyle* aStyle, ClassID aID, Kind aKind) |
164 | | : nsAtomicContainerFrame(aStyle, aID) |
165 | | , mComputedSize(0, 0) |
166 | | , mIntrinsicRatio(0, 0) |
167 | | , mKind(aKind) |
168 | | , mContentURLRequestRegistered(false) |
169 | | , mDisplayingIcon(false) |
170 | | , mFirstFrameComplete(false) |
171 | | , mReflowCallbackPosted(false) |
172 | | , mForceSyncDecoding(false) |
173 | 0 | { |
174 | 0 | EnableVisibilityTracking(); |
175 | 0 |
|
176 | 0 | // We assume our size is not constrained and we haven't gotten an |
177 | 0 | // initial reflow yet, so don't touch those flags. |
178 | 0 | mIntrinsicSize.width.SetCoordValue(0); |
179 | 0 | mIntrinsicSize.height.SetCoordValue(0); |
180 | 0 | } |
181 | | |
182 | | nsImageFrame::~nsImageFrame() |
183 | 0 | { |
184 | 0 | } |
185 | | |
186 | 0 | NS_QUERYFRAME_HEAD(nsImageFrame) |
187 | 0 | NS_QUERYFRAME_ENTRY(nsImageFrame) |
188 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame) |
189 | | |
190 | | #ifdef ACCESSIBILITY |
191 | | a11y::AccType |
192 | | nsImageFrame::AccessibleType() |
193 | 0 | { |
194 | 0 | // Don't use GetImageMap() to avoid reentrancy into accessibility. |
195 | 0 | if (HasImageMap()) { |
196 | 0 | return a11y::eHTMLImageMapType; |
197 | 0 | } |
198 | 0 | |
199 | 0 | return a11y::eImageType; |
200 | 0 | } |
201 | | #endif |
202 | | |
203 | | void |
204 | | nsImageFrame::DisconnectMap() |
205 | 0 | { |
206 | 0 | if (!mImageMap) { |
207 | 0 | return; |
208 | 0 | } |
209 | 0 | |
210 | 0 | mImageMap->Destroy(); |
211 | 0 | mImageMap = nullptr; |
212 | 0 |
|
213 | 0 | #ifdef ACCESSIBILITY |
214 | 0 | if (nsAccessibilityService* accService = GetAccService()) { |
215 | 0 | accService->RecreateAccessible(PresShell(), mContent); |
216 | 0 | } |
217 | 0 | #endif |
218 | 0 | } |
219 | | |
220 | | void |
221 | | nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) |
222 | 0 | { |
223 | 0 | if (mReflowCallbackPosted) { |
224 | 0 | PresShell()->CancelReflowCallback(this); |
225 | 0 | mReflowCallbackPosted = false; |
226 | 0 | } |
227 | 0 |
|
228 | 0 | // Tell our image map, if there is one, to clean up |
229 | 0 | // This causes the nsImageMap to unregister itself as |
230 | 0 | // a DOM listener. |
231 | 0 | DisconnectMap(); |
232 | 0 |
|
233 | 0 | MOZ_ASSERT(mListener); |
234 | 0 |
|
235 | 0 | if (mKind == Kind::ImageElement) { |
236 | 0 | MOZ_ASSERT(!mContentURLRequest); |
237 | 0 | MOZ_ASSERT(!mContentURLRequestRegistered); |
238 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); |
239 | 0 | MOZ_ASSERT(imageLoader); |
240 | 0 |
|
241 | 0 | // Notify our image loading content that we are going away so it can |
242 | 0 | // deregister with our refresh driver. |
243 | 0 | imageLoader->FrameDestroyed(this); |
244 | 0 | imageLoader->RemoveNativeObserver(mListener); |
245 | 0 | } else { |
246 | 0 | if (mContentURLRequest) { |
247 | 0 | nsLayoutUtils::DeregisterImageRequest( |
248 | 0 | PresContext(), mContentURLRequest, &mContentURLRequestRegistered); |
249 | 0 | mContentURLRequest->Cancel(NS_BINDING_ABORTED); |
250 | 0 | } |
251 | 0 | } |
252 | 0 |
|
253 | 0 | // set the frame to null so we don't send messages to a dead object. |
254 | 0 | mListener->SetFrame(nullptr); |
255 | 0 | mListener = nullptr; |
256 | 0 |
|
257 | 0 | // If we were displaying an icon, take ourselves off the list |
258 | 0 | if (mDisplayingIcon) |
259 | 0 | gIconLoad->RemoveIconObserver(this); |
260 | 0 |
|
261 | 0 | nsAtomicContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); |
262 | 0 | } |
263 | | |
264 | | void |
265 | | nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) |
266 | 0 | { |
267 | 0 | nsAtomicContainerFrame::DidSetComputedStyle(aOldComputedStyle); |
268 | 0 |
|
269 | 0 | if (!mImage) { |
270 | 0 | // We'll pick this change up whenever we do get an image. |
271 | 0 | return; |
272 | 0 | } |
273 | 0 | |
274 | 0 | auto newOrientation = StyleVisibility()->mImageOrientation; |
275 | 0 |
|
276 | 0 | // We need to update our orientation either if we had no ComputedStyle before |
277 | 0 | // because this is the first time it's been set, or if the image-orientation |
278 | 0 | // property changed from its previous value. |
279 | 0 | bool shouldUpdateOrientation = |
280 | 0 | !aOldComputedStyle || |
281 | 0 | aOldComputedStyle->StyleVisibility()->mImageOrientation != newOrientation; |
282 | 0 |
|
283 | 0 | if (shouldUpdateOrientation) { |
284 | 0 | nsCOMPtr<imgIContainer> image(mImage->Unwrap()); |
285 | 0 | mImage = nsLayoutUtils::OrientImage(image, newOrientation); |
286 | 0 |
|
287 | 0 | UpdateIntrinsicSize(mImage); |
288 | 0 | UpdateIntrinsicRatio(mImage); |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | | static bool |
293 | | SizeIsAvailable(imgIRequest* aRequest) |
294 | 0 | { |
295 | 0 | if (!aRequest) { |
296 | 0 | return false; |
297 | 0 | } |
298 | 0 | |
299 | 0 | uint32_t imageStatus = 0; |
300 | 0 | nsresult rv = aRequest->GetImageStatus(&imageStatus); |
301 | 0 | return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE); |
302 | 0 | } |
303 | | |
304 | | void |
305 | | nsImageFrame::Init(nsIContent* aContent, |
306 | | nsContainerFrame* aParent, |
307 | | nsIFrame* aPrevInFlow) |
308 | 0 | { |
309 | 0 | MOZ_ASSERT_IF(aPrevInFlow, |
310 | 0 | aPrevInFlow->Type() == Type() && |
311 | 0 | static_cast<nsImageFrame*>(aPrevInFlow)->mKind == mKind); |
312 | 0 |
|
313 | 0 | nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow); |
314 | 0 |
|
315 | 0 | mListener = new nsImageListener(this); |
316 | 0 |
|
317 | 0 | if (!gIconLoad) |
318 | 0 | LoadIcons(PresContext()); |
319 | 0 |
|
320 | 0 | if (mKind == Kind::ImageElement) { |
321 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent); |
322 | 0 | MOZ_ASSERT(imageLoader); |
323 | 0 | imageLoader->AddNativeObserver(mListener); |
324 | 0 | // We have a PresContext now, so we need to notify the image content node |
325 | 0 | // that it can register images. |
326 | 0 | imageLoader->FrameCreated(this); |
327 | 0 | } else { |
328 | 0 | uint32_t contentIndex = 0; |
329 | 0 | const nsStyleContent* styleContent = StyleContent(); |
330 | 0 | if (mKind == Kind::ContentPropertyAtIndex) { |
331 | 0 | MOZ_RELEASE_ASSERT( |
332 | 0 | aParent->GetContent()->IsGeneratedContentContainerForAfter() || |
333 | 0 | aParent->GetContent()->IsGeneratedContentContainerForBefore()); |
334 | 0 | MOZ_RELEASE_ASSERT(aContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)); |
335 | 0 | nsIFrame* nonAnonymousParent = aParent; |
336 | 0 | while (nonAnonymousParent->Style()->IsAnonBox()) { |
337 | 0 | nonAnonymousParent = nonAnonymousParent->GetParent(); |
338 | 0 | } |
339 | 0 | MOZ_RELEASE_ASSERT(aParent->GetContent() == nonAnonymousParent->GetContent()); |
340 | 0 | styleContent = nonAnonymousParent->StyleContent(); |
341 | 0 | contentIndex = static_cast<GeneratedImageContent*>(aContent)->Index(); |
342 | 0 | } |
343 | 0 | MOZ_RELEASE_ASSERT(contentIndex < styleContent->ContentCount()); |
344 | 0 | MOZ_RELEASE_ASSERT(styleContent->ContentAt(contentIndex).GetType() == |
345 | 0 | StyleContentType::Image); |
346 | 0 | if (auto* proxy = styleContent->ContentAt(contentIndex).GetImage()) { |
347 | 0 | proxy->Clone(mListener, |
348 | 0 | mContent->OwnerDoc(), |
349 | 0 | getter_AddRefs(mContentURLRequest)); |
350 | 0 | SetupForContentURLRequest(); |
351 | 0 | } |
352 | 0 | } |
353 | 0 |
|
354 | 0 | // Give image loads associated with an image frame a small priority boost. |
355 | 0 | if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { |
356 | 0 | uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT; |
357 | 0 |
|
358 | 0 | // Increase load priority further if intrinsic size might be important for layout. |
359 | 0 | if (!HaveSpecifiedSize(StylePosition())) { |
360 | 0 | categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY; |
361 | 0 | } |
362 | 0 |
|
363 | 0 | currentRequest->BoostPriority(categoryToBoostPriority); |
364 | 0 | } |
365 | 0 | } |
366 | | |
367 | | void |
368 | | nsImageFrame::SetupForContentURLRequest() |
369 | 0 | { |
370 | 0 | MOZ_ASSERT(mKind != Kind::ImageElement); |
371 | 0 | if (!mContentURLRequest) { |
372 | 0 | return; |
373 | 0 | } |
374 | 0 | |
375 | 0 | uint32_t status = 0; |
376 | 0 | nsresult rv = mContentURLRequest->GetImageStatus(&status); |
377 | 0 | if (NS_FAILED(rv)) { |
378 | 0 | return; |
379 | 0 | } |
380 | 0 | |
381 | 0 | if (status & imgIRequest::STATUS_SIZE_AVAILABLE) { |
382 | 0 | nsCOMPtr<imgIContainer> image; |
383 | 0 | mContentURLRequest->GetImage(getter_AddRefs(image)); |
384 | 0 | OnSizeAvailable(mContentURLRequest, image); |
385 | 0 | } |
386 | 0 |
|
387 | 0 | if (status & imgIRequest::STATUS_FRAME_COMPLETE) { |
388 | 0 | mFirstFrameComplete = true; |
389 | 0 | } |
390 | 0 |
|
391 | 0 | if (status & imgIRequest::STATUS_IS_ANIMATED) { |
392 | 0 | nsLayoutUtils::RegisterImageRequest( |
393 | 0 | PresContext(), mContentURLRequest, &mContentURLRequestRegistered); |
394 | 0 | } |
395 | 0 | } |
396 | | |
397 | | static void |
398 | | ScaleIntrinsicSizeForDensity(nsIContent& aContent, nsSize& aSize) |
399 | 0 | { |
400 | 0 | auto* image = HTMLImageElement::FromNode(aContent); |
401 | 0 | if (!image) { |
402 | 0 | return; |
403 | 0 | } |
404 | 0 | |
405 | 0 | ResponsiveImageSelector* selector = image->GetResponsiveImageSelector(); |
406 | 0 | if (!selector) { |
407 | 0 | return; |
408 | 0 | } |
409 | 0 | |
410 | 0 | double density = selector->GetSelectedImageDensity(); |
411 | 0 | MOZ_ASSERT(density >= 0.0); |
412 | 0 | if (density == 1.0) { |
413 | 0 | return; |
414 | 0 | } |
415 | 0 | |
416 | 0 | if (aSize.width != -1) { |
417 | 0 | aSize.width = NSToCoordRound(double(aSize.width) / density); |
418 | 0 | } |
419 | 0 | if (aSize.height != -1) { |
420 | 0 | aSize.height = NSToCoordRound(double(aSize.height) / density); |
421 | 0 | } |
422 | 0 | } |
423 | | |
424 | | bool |
425 | | nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage) |
426 | 0 | { |
427 | 0 | MOZ_ASSERT(aImage, "null image"); |
428 | 0 | if (!aImage) |
429 | 0 | return false; |
430 | 0 | |
431 | 0 | IntrinsicSize oldIntrinsicSize = mIntrinsicSize; |
432 | 0 | mIntrinsicSize = IntrinsicSize(); |
433 | 0 |
|
434 | 0 | // Set intrinsic size to match aImage's reported intrinsic width & height. |
435 | 0 | nsSize intrinsicSize; |
436 | 0 | if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) { |
437 | 0 | if (mKind == Kind::ImageElement) { |
438 | 0 | ScaleIntrinsicSizeForDensity(*mContent, intrinsicSize); |
439 | 0 | } |
440 | 0 | // If the image has no intrinsic width, intrinsicSize.width will be -1, and |
441 | 0 | // we can leave mIntrinsicSize.width at its default value of eStyleUnit_None. |
442 | 0 | // Otherwise we use intrinsicSize.width. Height works the same way. |
443 | 0 | if (intrinsicSize.width != -1) |
444 | 0 | mIntrinsicSize.width.SetCoordValue(intrinsicSize.width); |
445 | 0 | if (intrinsicSize.height != -1) |
446 | 0 | mIntrinsicSize.height.SetCoordValue(intrinsicSize.height); |
447 | 0 | } else { |
448 | 0 | // Failure means that the image hasn't loaded enough to report a result. We |
449 | 0 | // treat this case as if the image's intrinsic size was 0x0. |
450 | 0 | mIntrinsicSize.width.SetCoordValue(0); |
451 | 0 | mIntrinsicSize.height.SetCoordValue(0); |
452 | 0 | } |
453 | 0 |
|
454 | 0 | return mIntrinsicSize != oldIntrinsicSize; |
455 | 0 | } |
456 | | |
457 | | bool |
458 | | nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage) |
459 | 0 | { |
460 | 0 | MOZ_ASSERT(aImage, "null image"); |
461 | 0 |
|
462 | 0 | if (!aImage) |
463 | 0 | return false; |
464 | 0 | |
465 | 0 | nsSize oldIntrinsicRatio = mIntrinsicRatio; |
466 | 0 |
|
467 | 0 | // Set intrinsic ratio to match aImage's reported intrinsic ratio. |
468 | 0 | if (NS_FAILED(aImage->GetIntrinsicRatio(&mIntrinsicRatio))) |
469 | 0 | mIntrinsicRatio.SizeTo(0, 0); |
470 | 0 |
|
471 | 0 | return mIntrinsicRatio != oldIntrinsicRatio; |
472 | 0 | } |
473 | | |
474 | | bool |
475 | | nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) |
476 | 0 | { |
477 | 0 | // First, figure out destRect (the rect we're rendering into). |
478 | 0 | // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here, |
479 | 0 | // because GetInnerArea() might be smaller if we're fragmented, whereas |
480 | 0 | // mComputedSize has our full content-box size (which we need for |
481 | 0 | // ComputeObjectDestRect to work correctly). |
482 | 0 | nsRect constraintRect(GetInnerArea().TopLeft(), mComputedSize); |
483 | 0 | constraintRect.y -= GetContinuationOffset(); |
484 | 0 |
|
485 | 0 | nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(constraintRect, |
486 | 0 | mIntrinsicSize, |
487 | 0 | mIntrinsicRatio, |
488 | 0 | StylePosition()); |
489 | 0 | // Set the translation components, based on destRect |
490 | 0 | // XXXbz does this introduce rounding errors because of the cast to |
491 | 0 | // float? Should we just manually add that stuff in every time |
492 | 0 | // instead? |
493 | 0 | aTransform.SetToTranslate(float(destRect.x), float(destRect.y)); |
494 | 0 |
|
495 | 0 |
|
496 | 0 | // NOTE(emilio): This intrinsicSize is not the same as the layout intrinsic |
497 | 0 | // size (mIntrinsicSize), which can be scaled due to ResponsiveImageSelector, |
498 | 0 | // see ScaleIntrinsicSizeForDensity. |
499 | 0 | nsSize intrinsicSize; |
500 | 0 | if (!mImage || |
501 | 0 | !NS_SUCCEEDED(mImage->GetIntrinsicSize(&intrinsicSize)) || |
502 | 0 | intrinsicSize.IsEmpty()) { |
503 | 0 | return false; |
504 | 0 | } |
505 | 0 | |
506 | 0 | aTransform.SetScale(float(destRect.width) / float(intrinsicSize.width), |
507 | 0 | float(destRect.height) / float(intrinsicSize.height)); |
508 | 0 | return true; |
509 | 0 | } |
510 | | |
511 | | // This function checks whether the given request is the current request for our |
512 | | // mContent. |
513 | | bool |
514 | | nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const |
515 | 0 | { |
516 | 0 | // Default to pending load in case of errors |
517 | 0 | if (mKind != Kind::ImageElement) { |
518 | 0 | MOZ_ASSERT(aRequest == mContentURLRequest); |
519 | 0 | return false; |
520 | 0 | } |
521 | 0 |
|
522 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent)); |
523 | 0 | MOZ_ASSERT(imageLoader); |
524 | 0 |
|
525 | 0 | int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST; |
526 | 0 | imageLoader->GetRequestType(aRequest, &requestType); |
527 | 0 |
|
528 | 0 | return requestType != nsIImageLoadingContent::CURRENT_REQUEST; |
529 | 0 | } |
530 | | |
531 | | nsRect |
532 | | nsImageFrame::SourceRectToDest(const nsIntRect& aRect) |
533 | 0 | { |
534 | 0 | // When scaling the image, row N of the source image may (depending on |
535 | 0 | // the scaling function) be used to draw any row in the destination image |
536 | 0 | // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the |
537 | 0 | // floating-point scaling factor. The same holds true for columns. |
538 | 0 | // So, we start by computing that bound without the floor and ceiling. |
539 | 0 |
|
540 | 0 | nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1), |
541 | 0 | nsPresContext::CSSPixelsToAppUnits(aRect.y - 1), |
542 | 0 | nsPresContext::CSSPixelsToAppUnits(aRect.width + 2), |
543 | 0 | nsPresContext::CSSPixelsToAppUnits(aRect.height + 2)); |
544 | 0 |
|
545 | 0 | nsTransform2D sourceToDest; |
546 | 0 | if (!GetSourceToDestTransform(sourceToDest)) { |
547 | 0 | // Failed to generate transform matrix. Return our whole inner area, |
548 | 0 | // to be on the safe side (since this method is used for generating |
549 | 0 | // invalidation rects). |
550 | 0 | return GetInnerArea(); |
551 | 0 | } |
552 | 0 | |
553 | 0 | sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height); |
554 | 0 |
|
555 | 0 | // Now, round the edges out to the pixel boundary. |
556 | 0 | nscoord scale = nsPresContext::CSSPixelsToAppUnits(1); |
557 | 0 | nscoord right = r.x + r.width; |
558 | 0 | nscoord bottom = r.y + r.height; |
559 | 0 |
|
560 | 0 | r.x -= (scale + (r.x % scale)) % scale; |
561 | 0 | r.y -= (scale + (r.y % scale)) % scale; |
562 | 0 | r.width = right + ((scale - (right % scale)) % scale) - r.x; |
563 | 0 | r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y; |
564 | 0 |
|
565 | 0 | return r; |
566 | 0 | } |
567 | | |
568 | | // Note that we treat NS_EVENT_STATE_SUPPRESSED images as "OK". This means |
569 | | // that we'll construct image frames for them as needed if their display is |
570 | | // toggled from "none" (though we won't paint them, unless their visibility |
571 | | // is changed too). |
572 | 0 | #define BAD_STATES (NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED | \ |
573 | 0 | NS_EVENT_STATE_LOADING) |
574 | | |
575 | | // This is a macro so that we don't evaluate the boolean last arg |
576 | | // unless we have to; it can be expensive |
577 | | #define IMAGE_OK(_state, _loadingOK) \ |
578 | 0 | (!(_state).HasAtLeastOneOfStates(BAD_STATES) || \ |
579 | 0 | (!(_state).HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED) && \ |
580 | 0 | (_state).HasState(NS_EVENT_STATE_LOADING) && (_loadingOK))) |
581 | | |
582 | | /* static */ |
583 | | bool |
584 | | nsImageFrame::ShouldCreateImageFrameFor(const Element& aElement, |
585 | | ComputedStyle& aStyle) |
586 | 0 | { |
587 | 0 | EventStates state = aElement.State(); |
588 | 0 | if (IMAGE_OK(state, HaveSpecifiedSize(aStyle.StylePosition()))) { |
589 | 0 | // Image is fine; do the image frame thing |
590 | 0 | return true; |
591 | 0 | } |
592 | 0 | |
593 | 0 | // Check if we want to use a placeholder box with an icon or just |
594 | 0 | // let the presShell make us into inline text. Decide as follows: |
595 | 0 | // |
596 | 0 | // - if our special "force icons" style is set, show an icon |
597 | 0 | // - else if our "do not show placeholders" pref is set, skip the icon |
598 | 0 | // - else: |
599 | 0 | // - if there is a src attribute, there is no alt attribute, |
600 | 0 | // and this is not an <object> (which could not possibly have |
601 | 0 | // such an attribute), show an icon. |
602 | 0 | // - if QuirksMode, and the IMG has a size show an icon. |
603 | 0 | // - otherwise, skip the icon |
604 | 0 | bool useSizedBox; |
605 | 0 |
|
606 | 0 | if (aStyle.StyleUIReset()->mForceBrokenImageIcon) { |
607 | 0 | useSizedBox = true; |
608 | 0 | } |
609 | 0 | else if (gIconLoad && gIconLoad->mPrefForceInlineAltText) { |
610 | 0 | useSizedBox = false; |
611 | 0 | } |
612 | 0 | else if (aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::src) && |
613 | 0 | !aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::alt) && |
614 | 0 | !aElement.IsHTMLElement(nsGkAtoms::object) && |
615 | 0 | !aElement.IsHTMLElement(nsGkAtoms::input)) { |
616 | 0 | // Use a sized box if we have no alt text. This means no alt attribute |
617 | 0 | // and the node is not an object or an input (since those always have alt |
618 | 0 | // text). |
619 | 0 | useSizedBox = true; |
620 | 0 | } |
621 | 0 | else if (aElement.OwnerDoc()->GetCompatibilityMode() != |
622 | 0 | eCompatibility_NavQuirks) { |
623 | 0 | useSizedBox = false; |
624 | 0 | } |
625 | 0 | else { |
626 | 0 | // check whether we have specified size |
627 | 0 | useSizedBox = HaveSpecifiedSize(aStyle.StylePosition()); |
628 | 0 | } |
629 | 0 |
|
630 | 0 | return useSizedBox; |
631 | 0 | } |
632 | | |
633 | | nsresult |
634 | | nsImageFrame::Notify(imgIRequest* aRequest, |
635 | | int32_t aType, |
636 | | const nsIntRect* aRect) |
637 | 0 | { |
638 | 0 | if (aType == imgINotificationObserver::SIZE_AVAILABLE) { |
639 | 0 | nsCOMPtr<imgIContainer> image; |
640 | 0 | aRequest->GetImage(getter_AddRefs(image)); |
641 | 0 | return OnSizeAvailable(aRequest, image); |
642 | 0 | } |
643 | 0 | |
644 | 0 | if (aType == imgINotificationObserver::FRAME_UPDATE) { |
645 | 0 | return OnFrameUpdate(aRequest, aRect); |
646 | 0 | } |
647 | 0 | |
648 | 0 | if (aType == imgINotificationObserver::FRAME_COMPLETE) { |
649 | 0 | mFirstFrameComplete = true; |
650 | 0 | } |
651 | 0 |
|
652 | 0 | if (aType == imgINotificationObserver::IS_ANIMATED && |
653 | 0 | mKind != Kind::ImageElement) { |
654 | 0 | nsLayoutUtils::RegisterImageRequest( |
655 | 0 | PresContext(), mContentURLRequest, &mContentURLRequestRegistered); |
656 | 0 | } |
657 | 0 |
|
658 | 0 | if (aType == imgINotificationObserver::LOAD_COMPLETE) { |
659 | 0 | uint32_t imgStatus; |
660 | 0 | aRequest->GetImageStatus(&imgStatus); |
661 | 0 | nsresult status = |
662 | 0 | imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; |
663 | 0 | return OnLoadComplete(aRequest, status); |
664 | 0 | } |
665 | 0 |
|
666 | 0 | return NS_OK; |
667 | 0 | } |
668 | | |
669 | | nsresult |
670 | | nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage) |
671 | 0 | { |
672 | 0 | if (!aImage) { |
673 | 0 | return NS_ERROR_INVALID_ARG; |
674 | 0 | } |
675 | 0 | |
676 | 0 | /* Get requested animation policy from the pres context: |
677 | 0 | * normal = 0 |
678 | 0 | * one frame = 1 |
679 | 0 | * one loop = 2 |
680 | 0 | */ |
681 | 0 | aImage->SetAnimationMode(PresContext()->ImageAnimationMode()); |
682 | 0 |
|
683 | 0 | if (IsPendingLoad(aRequest)) { |
684 | 0 | // We don't care |
685 | 0 | return NS_OK; |
686 | 0 | } |
687 | 0 | |
688 | 0 | bool intrinsicSizeChanged = false; |
689 | 0 | if (SizeIsAvailable(aRequest)) { |
690 | 0 | // This is valid and for the current request, so update our stored image |
691 | 0 | // container, orienting according to our style. |
692 | 0 | mImage = nsLayoutUtils::OrientImage(aImage, StyleVisibility()->mImageOrientation); |
693 | 0 |
|
694 | 0 | intrinsicSizeChanged = UpdateIntrinsicSize(mImage); |
695 | 0 | intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged; |
696 | 0 | } else { |
697 | 0 | // We no longer have a valid image, so release our stored image container. |
698 | 0 | mImage = mPrevImage = nullptr; |
699 | 0 |
|
700 | 0 | // Have to size to 0,0 so that GetDesiredSize recalculates the size. |
701 | 0 | mIntrinsicSize.width.SetCoordValue(0); |
702 | 0 | mIntrinsicSize.height.SetCoordValue(0); |
703 | 0 | mIntrinsicRatio.SizeTo(0, 0); |
704 | 0 | intrinsicSizeChanged = true; |
705 | 0 | } |
706 | 0 |
|
707 | 0 | if (!GotInitialReflow()) { |
708 | 0 | return NS_OK; |
709 | 0 | } |
710 | 0 | |
711 | 0 | MarkNeedsDisplayItemRebuild(); |
712 | 0 |
|
713 | 0 | if (intrinsicSizeChanged) { |
714 | 0 | // Now we need to reflow if we have an unconstrained size and have |
715 | 0 | // already gotten the initial reflow |
716 | 0 | if (!(mState & IMAGE_SIZECONSTRAINED)) { |
717 | 0 | PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange, |
718 | 0 | NS_FRAME_IS_DIRTY); |
719 | 0 | } else { |
720 | 0 | // We've already gotten the initial reflow, and our size hasn't changed, |
721 | 0 | // so we're ready to request a decode. |
722 | 0 | MaybeDecodeForPredictedSize(); |
723 | 0 | } |
724 | 0 |
|
725 | 0 | mPrevImage = nullptr; |
726 | 0 | } |
727 | 0 |
|
728 | 0 | return NS_OK; |
729 | 0 | } |
730 | | |
731 | | nsresult |
732 | | nsImageFrame::OnFrameUpdate(imgIRequest* aRequest, const nsIntRect* aRect) |
733 | 0 | { |
734 | 0 | NS_ENSURE_ARG_POINTER(aRect); |
735 | 0 |
|
736 | 0 | if (!GotInitialReflow()) { |
737 | 0 | // Don't bother to do anything; we have a reflow coming up! |
738 | 0 | return NS_OK; |
739 | 0 | } |
740 | 0 | |
741 | 0 | if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) { |
742 | 0 | return NS_OK; |
743 | 0 | } |
744 | 0 | |
745 | 0 | if (IsPendingLoad(aRequest)) { |
746 | 0 | // We don't care |
747 | 0 | return NS_OK; |
748 | 0 | } |
749 | 0 | |
750 | 0 | nsIntRect layerInvalidRect = mImage |
751 | 0 | ? mImage->GetImageSpaceInvalidationRect(*aRect) |
752 | 0 | : *aRect; |
753 | 0 |
|
754 | 0 | if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) { |
755 | 0 | // Invalidate our entire area. |
756 | 0 | InvalidateSelf(nullptr, nullptr); |
757 | 0 | return NS_OK; |
758 | 0 | } |
759 | 0 | |
760 | 0 | nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect); |
761 | 0 | InvalidateSelf(&layerInvalidRect, &frameInvalidRect); |
762 | 0 | return NS_OK; |
763 | 0 | } |
764 | | |
765 | | void |
766 | | nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect, |
767 | | const nsRect* aFrameInvalidRect) |
768 | 0 | { |
769 | 0 | // Check if WebRender has interacted with this frame. If it has |
770 | 0 | // we need to let it know that things have changed. |
771 | 0 | if (HasProperty(WebRenderUserDataProperty::Key())) { |
772 | 0 | RefPtr<WebRenderFallbackData> data = GetWebRenderUserData<WebRenderFallbackData>(this, static_cast<uint32_t>(DisplayItemType::TYPE_IMAGE)); |
773 | 0 | if (data) { |
774 | 0 | data->SetInvalid(true); |
775 | 0 | } |
776 | 0 | SchedulePaint(); |
777 | 0 | return; |
778 | 0 | } |
779 | 0 |
|
780 | 0 | InvalidateLayer(DisplayItemType::TYPE_IMAGE, |
781 | 0 | aLayerInvalidRect, |
782 | 0 | aFrameInvalidRect); |
783 | 0 |
|
784 | 0 | if (!mFirstFrameComplete) { |
785 | 0 | InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, |
786 | 0 | aLayerInvalidRect, |
787 | 0 | aFrameInvalidRect); |
788 | 0 | } |
789 | 0 | } |
790 | | |
791 | | nsresult |
792 | | nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) |
793 | 0 | { |
794 | 0 | NotifyNewCurrentRequest(aRequest, aStatus); |
795 | 0 | return NS_OK; |
796 | 0 | } |
797 | | |
798 | | void |
799 | | nsImageFrame::ResponsiveContentDensityChanged() |
800 | 0 | { |
801 | 0 | if (!GotInitialReflow()) { |
802 | 0 | return; |
803 | 0 | } |
804 | 0 | |
805 | 0 | if (!mImage) { |
806 | 0 | return; |
807 | 0 | } |
808 | 0 | |
809 | 0 | if (!UpdateIntrinsicSize(mImage) && !UpdateIntrinsicRatio(mImage)) { |
810 | 0 | return; |
811 | 0 | } |
812 | 0 | |
813 | 0 | PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange, |
814 | 0 | NS_FRAME_IS_DIRTY); |
815 | 0 | } |
816 | | |
817 | | void |
818 | | nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest, nsresult aStatus) |
819 | 0 | { |
820 | 0 | nsCOMPtr<imgIContainer> image; |
821 | 0 | aRequest->GetImage(getter_AddRefs(image)); |
822 | 0 | NS_ASSERTION(image || NS_FAILED(aStatus), "Successful load with no container?"); |
823 | 0 |
|
824 | 0 | // May have to switch sizes here! |
825 | 0 | bool intrinsicSizeChanged = true; |
826 | 0 | if (NS_SUCCEEDED(aStatus) && image && SizeIsAvailable(aRequest)) { |
827 | 0 | // Update our stored image container, orienting according to our style. |
828 | 0 | mImage = nsLayoutUtils::OrientImage(image, StyleVisibility()->mImageOrientation); |
829 | 0 |
|
830 | 0 | intrinsicSizeChanged = UpdateIntrinsicSize(mImage); |
831 | 0 | intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged; |
832 | 0 | } else { |
833 | 0 | // We no longer have a valid image, so release our stored image container. |
834 | 0 | mImage = mPrevImage = nullptr; |
835 | 0 |
|
836 | 0 | // Have to size to 0,0 so that GetDesiredSize recalculates the size |
837 | 0 | mIntrinsicSize.width.SetCoordValue(0); |
838 | 0 | mIntrinsicSize.height.SetCoordValue(0); |
839 | 0 | mIntrinsicRatio.SizeTo(0, 0); |
840 | 0 | } |
841 | 0 |
|
842 | 0 | if (GotInitialReflow()) { |
843 | 0 | if (intrinsicSizeChanged) { |
844 | 0 | if (!(mState & IMAGE_SIZECONSTRAINED)) { |
845 | 0 | PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange, |
846 | 0 | NS_FRAME_IS_DIRTY); |
847 | 0 | } else { |
848 | 0 | // We've already gotten the initial reflow, and our size hasn't changed, |
849 | 0 | // so we're ready to request a decode. |
850 | 0 | MaybeDecodeForPredictedSize(); |
851 | 0 | } |
852 | 0 |
|
853 | 0 | mPrevImage = nullptr; |
854 | 0 | } |
855 | 0 | // Update border+content to account for image change |
856 | 0 | InvalidateFrame(); |
857 | 0 | } |
858 | 0 | } |
859 | | |
860 | | void |
861 | | nsImageFrame::MaybeDecodeForPredictedSize() |
862 | 0 | { |
863 | 0 | // Check that we're ready to decode. |
864 | 0 | if (!mImage) { |
865 | 0 | return; // Nothing to do yet. |
866 | 0 | } |
867 | 0 | |
868 | 0 | if (mComputedSize.IsEmpty()) { |
869 | 0 | return; // We won't draw anything, so no point in decoding. |
870 | 0 | } |
871 | 0 | |
872 | 0 | if (GetVisibility() != Visibility::APPROXIMATELY_VISIBLE) { |
873 | 0 | return; // We're not visible, so don't decode. |
874 | 0 | } |
875 | 0 | |
876 | 0 | // OK, we're ready to decode. Compute the scale to the screen... |
877 | 0 | nsIPresShell* presShell = PresContext()->GetPresShell(); |
878 | 0 | LayoutDeviceToScreenScale2D resolutionToScreen( |
879 | 0 | presShell->GetCumulativeResolution() |
880 | 0 | * nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this)); |
881 | 0 |
|
882 | 0 | // ...and this frame's content box... |
883 | 0 | const nsPoint offset = |
884 | 0 | GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this)); |
885 | 0 | const nsRect frameContentBox = GetInnerArea() + offset; |
886 | 0 |
|
887 | 0 | // ...and our predicted dest rect... |
888 | 0 | const int32_t factor = PresContext()->AppUnitsPerDevPixel(); |
889 | 0 | const LayoutDeviceRect destRect = |
890 | 0 | LayoutDeviceRect::FromAppUnits(PredictedDestRect(frameContentBox), factor); |
891 | 0 |
|
892 | 0 | // ...and use them to compute our predicted size in screen pixels. |
893 | 0 | const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen; |
894 | 0 | const ScreenIntSize predictedScreenIntSize = RoundedToInt(predictedScreenSize); |
895 | 0 | if (predictedScreenIntSize.IsEmpty()) { |
896 | 0 | return; |
897 | 0 | } |
898 | 0 | |
899 | 0 | // Determine the optimal image size to use. |
900 | 0 | uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
901 | 0 | | imgIContainer::FLAG_ASYNC_NOTIFY; |
902 | 0 | SamplingFilter samplingFilter = |
903 | 0 | nsLayoutUtils::GetSamplingFilterForFrame(this); |
904 | 0 | gfxSize gfxPredictedScreenSize = gfxSize(predictedScreenIntSize.width, |
905 | 0 | predictedScreenIntSize.height); |
906 | 0 | nsIntSize predictedImageSize = |
907 | 0 | mImage->OptimalImageSizeForDest(gfxPredictedScreenSize, |
908 | 0 | imgIContainer::FRAME_CURRENT, |
909 | 0 | samplingFilter, flags); |
910 | 0 |
|
911 | 0 | // Request a decode. |
912 | 0 | mImage->RequestDecodeForSize(predictedImageSize, flags); |
913 | 0 | } |
914 | | |
915 | | nsRect |
916 | | nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) |
917 | 0 | { |
918 | 0 | // Note: To get the "dest rect", we have to provide the "constraint rect" |
919 | 0 | // (which is the content-box, with the effects of fragmentation undone). |
920 | 0 | nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize); |
921 | 0 | constraintRect.y -= GetContinuationOffset(); |
922 | 0 |
|
923 | 0 | return nsLayoutUtils::ComputeObjectDestRect(constraintRect, |
924 | 0 | mIntrinsicSize, |
925 | 0 | mIntrinsicRatio, |
926 | 0 | StylePosition()); |
927 | 0 | } |
928 | | |
929 | | void |
930 | | nsImageFrame::EnsureIntrinsicSizeAndRatio() |
931 | 0 | { |
932 | 0 | // If mIntrinsicSize.width and height are 0, then we need to update from the |
933 | 0 | // image container. |
934 | 0 | if (mIntrinsicSize.width.GetUnit() != eStyleUnit_Coord || |
935 | 0 | mIntrinsicSize.width.GetCoordValue() != 0 || |
936 | 0 | mIntrinsicSize.height.GetUnit() != eStyleUnit_Coord || |
937 | 0 | mIntrinsicSize.height.GetCoordValue() != 0) { |
938 | 0 | return; |
939 | 0 | } |
940 | 0 | |
941 | 0 | if (mImage) { |
942 | 0 | UpdateIntrinsicSize(mImage); |
943 | 0 | UpdateIntrinsicRatio(mImage); |
944 | 0 | return; |
945 | 0 | } |
946 | 0 | |
947 | 0 | // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit |
948 | 0 | // and Blink behave differently here for content: url(..), for now adapt to |
949 | 0 | // Blink's behavior. |
950 | 0 | const bool mayDisplayBrokenIcon = mKind == Kind::ImageElement; |
951 | 0 | if (!mayDisplayBrokenIcon) { |
952 | 0 | return; |
953 | 0 | } |
954 | 0 | // image request is null or image size not known, probably an |
955 | 0 | // invalid image specified |
956 | 0 | bool imageInvalid = false; |
957 | 0 |
|
958 | 0 | // check for broken images. valid null images (eg. img src="") are |
959 | 0 | // not considered broken because they have no image requests |
960 | 0 | if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { |
961 | 0 | uint32_t imageStatus; |
962 | 0 | imageInvalid = |
963 | 0 | NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) && |
964 | 0 | (imageStatus & imgIRequest::STATUS_ERROR); |
965 | 0 | } else { |
966 | 0 | MOZ_ASSERT(mKind == Kind::ImageElement); |
967 | 0 |
|
968 | 0 | nsCOMPtr<nsIImageLoadingContent> loader = do_QueryInterface(mContent); |
969 | 0 | MOZ_ASSERT(loader); |
970 | 0 | // check if images are user-disabled (or blocked for other reasons) |
971 | 0 | int16_t imageBlockingStatus; |
972 | 0 | loader->GetImageBlockingStatus(&imageBlockingStatus); |
973 | 0 | imageInvalid = imageBlockingStatus != nsIContentPolicy::ACCEPT; |
974 | 0 | } |
975 | 0 |
|
976 | 0 | // invalid image specified. make the image big enough for the "broken" icon |
977 | 0 | if (imageInvalid) { |
978 | 0 | nscoord edgeLengthToUse = |
979 | 0 | nsPresContext::CSSPixelsToAppUnits( |
980 | 0 | ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); |
981 | 0 | mIntrinsicSize.width.SetCoordValue(edgeLengthToUse); |
982 | 0 | mIntrinsicSize.height.SetCoordValue(edgeLengthToUse); |
983 | 0 | mIntrinsicRatio.SizeTo(1, 1); |
984 | 0 | } |
985 | 0 | } |
986 | | |
987 | | /* virtual */ |
988 | | LogicalSize |
989 | | nsImageFrame::ComputeSize(gfxContext *aRenderingContext, |
990 | | WritingMode aWM, |
991 | | const LogicalSize& aCBSize, |
992 | | nscoord aAvailableISize, |
993 | | const LogicalSize& aMargin, |
994 | | const LogicalSize& aBorder, |
995 | | const LogicalSize& aPadding, |
996 | | ComputeSizeFlags aFlags) |
997 | 0 | { |
998 | 0 | EnsureIntrinsicSizeAndRatio(); |
999 | 0 | return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM, |
1000 | 0 | mIntrinsicSize, mIntrinsicRatio, |
1001 | 0 | aCBSize, aMargin, aBorder, aPadding, |
1002 | 0 | aFlags); |
1003 | 0 | } |
1004 | | |
1005 | | // XXXdholbert This function's clients should probably just be calling |
1006 | | // GetContentRectRelativeToSelf() directly. |
1007 | | nsRect |
1008 | | nsImageFrame::GetInnerArea() const |
1009 | 0 | { |
1010 | 0 | return GetContentRectRelativeToSelf(); |
1011 | 0 | } |
1012 | | |
1013 | | Element* |
1014 | | nsImageFrame::GetMapElement() const |
1015 | 0 | { |
1016 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); |
1017 | 0 | return imageLoader ? |
1018 | 0 | static_cast<nsImageLoadingContent*>(imageLoader.get())->FindImageMap() : |
1019 | 0 | nullptr; |
1020 | 0 | } |
1021 | | |
1022 | | // get the offset into the content area of the image where aImg starts if it is a continuation. |
1023 | | nscoord |
1024 | | nsImageFrame::GetContinuationOffset() const |
1025 | 0 | { |
1026 | 0 | nscoord offset = 0; |
1027 | 0 | for (nsIFrame *f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) { |
1028 | 0 | offset += f->GetContentRect().height; |
1029 | 0 | } |
1030 | 0 | NS_ASSERTION(offset >= 0, "bogus GetContentRect"); |
1031 | 0 | return offset; |
1032 | 0 | } |
1033 | | |
1034 | | /* virtual */ nscoord |
1035 | | nsImageFrame::GetMinISize(gfxContext *aRenderingContext) |
1036 | 0 | { |
1037 | 0 | // XXX The caller doesn't account for constraints of the block-size, |
1038 | 0 | // min-block-size, and max-block-size properties. |
1039 | 0 | DebugOnly<nscoord> result; |
1040 | 0 | DISPLAY_MIN_INLINE_SIZE(this, result); |
1041 | 0 | EnsureIntrinsicSizeAndRatio(); |
1042 | 0 | const nsStyleCoord& iSize = GetWritingMode().IsVertical() ? |
1043 | 0 | mIntrinsicSize.height : mIntrinsicSize.width; |
1044 | 0 | return iSize.GetUnit() == eStyleUnit_Coord ? iSize.GetCoordValue() : 0; |
1045 | 0 | } |
1046 | | |
1047 | | /* virtual */ nscoord |
1048 | | nsImageFrame::GetPrefISize(gfxContext *aRenderingContext) |
1049 | 0 | { |
1050 | 0 | // XXX The caller doesn't account for constraints of the block-size, |
1051 | 0 | // min-block-size, and max-block-size properties. |
1052 | 0 | DebugOnly<nscoord> result; |
1053 | 0 | DISPLAY_PREF_INLINE_SIZE(this, result); |
1054 | 0 | EnsureIntrinsicSizeAndRatio(); |
1055 | 0 | const nsStyleCoord& iSize = GetWritingMode().IsVertical() ? |
1056 | 0 | mIntrinsicSize.height : mIntrinsicSize.width; |
1057 | 0 | // convert from normal twips to scaled twips (printing...) |
1058 | 0 | return iSize.GetUnit() == eStyleUnit_Coord ? iSize.GetCoordValue() : 0; |
1059 | 0 | } |
1060 | | |
1061 | | /* virtual */ IntrinsicSize |
1062 | | nsImageFrame::GetIntrinsicSize() |
1063 | 0 | { |
1064 | 0 | return mIntrinsicSize; |
1065 | 0 | } |
1066 | | |
1067 | | /* virtual */ nsSize |
1068 | | nsImageFrame::GetIntrinsicRatio() |
1069 | 0 | { |
1070 | 0 | return mIntrinsicRatio; |
1071 | 0 | } |
1072 | | |
1073 | | void |
1074 | | nsImageFrame::Reflow(nsPresContext* aPresContext, |
1075 | | ReflowOutput& aMetrics, |
1076 | | const ReflowInput& aReflowInput, |
1077 | | nsReflowStatus& aStatus) |
1078 | 0 | { |
1079 | 0 | MarkInReflow(); |
1080 | 0 | DO_GLOBAL_REFLOW_COUNT("nsImageFrame"); |
1081 | 0 | DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); |
1082 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
1083 | 0 | NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, |
1084 | 0 | ("enter nsImageFrame::Reflow: availSize=%d,%d", |
1085 | 0 | aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); |
1086 | 0 |
|
1087 | 0 | MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); |
1088 | 0 |
|
1089 | 0 | // see if we have a frozen size (i.e. a fixed width and height) |
1090 | 0 | if (HaveFixedSize(aReflowInput)) { |
1091 | 0 | AddStateBits(IMAGE_SIZECONSTRAINED); |
1092 | 0 | } else { |
1093 | 0 | RemoveStateBits(IMAGE_SIZECONSTRAINED); |
1094 | 0 | } |
1095 | 0 |
|
1096 | 0 | mComputedSize = |
1097 | 0 | nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight()); |
1098 | 0 |
|
1099 | 0 | aMetrics.Width() = mComputedSize.width; |
1100 | 0 | aMetrics.Height() = mComputedSize.height; |
1101 | 0 |
|
1102 | 0 | // add borders and padding |
1103 | 0 | aMetrics.Width() += aReflowInput.ComputedPhysicalBorderPadding().LeftRight(); |
1104 | 0 | aMetrics.Height() += aReflowInput.ComputedPhysicalBorderPadding().TopBottom(); |
1105 | 0 |
|
1106 | 0 | if (GetPrevInFlow()) { |
1107 | 0 | aMetrics.Width() = GetPrevInFlow()->GetSize().width; |
1108 | 0 | nscoord y = GetContinuationOffset(); |
1109 | 0 | aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top; |
1110 | 0 | aMetrics.Height() = std::max(0, aMetrics.Height()); |
1111 | 0 | } |
1112 | 0 |
|
1113 | 0 |
|
1114 | 0 | // we have to split images if we are: |
1115 | 0 | // in Paginated mode, we need to have a constrained height, and have a height larger than our available height |
1116 | 0 | uint32_t loadStatus = imgIRequest::STATUS_NONE; |
1117 | 0 | if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { |
1118 | 0 | currentRequest->GetImageStatus(&loadStatus); |
1119 | 0 | } |
1120 | 0 |
|
1121 | 0 | if (aPresContext->IsPaginated() && |
1122 | 0 | ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) || (mState & IMAGE_SIZECONSTRAINED)) && |
1123 | 0 | NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() && |
1124 | 0 | aMetrics.Height() > aReflowInput.AvailableHeight()) { |
1125 | 0 | // our desired height was greater than 0, so to avoid infinite |
1126 | 0 | // splitting, use 1 pixel as the min |
1127 | 0 | aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1), aReflowInput.AvailableHeight()); |
1128 | 0 | aStatus.SetIncomplete(); |
1129 | 0 | } |
1130 | 0 |
|
1131 | 0 | aMetrics.SetOverflowAreasToDesiredBounds(); |
1132 | 0 | EventStates contentState = mContent->AsElement()->State(); |
1133 | 0 | bool imageOK = IMAGE_OK(contentState, true); |
1134 | 0 |
|
1135 | 0 | // Determine if the size is available |
1136 | 0 | bool haveSize = false; |
1137 | 0 | if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) { |
1138 | 0 | haveSize = true; |
1139 | 0 | } |
1140 | 0 |
|
1141 | 0 | if (!imageOK || !haveSize) { |
1142 | 0 | nsRect altFeedbackSize(0, 0, |
1143 | 0 | nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH)), |
1144 | 0 | nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH))); |
1145 | 0 | // We include the altFeedbackSize in our visual overflow, but not in our |
1146 | 0 | // scrollable overflow, since it doesn't really need to be scrolled to |
1147 | 0 | // outside the image. |
1148 | 0 | static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?"); |
1149 | 0 | nsRect& visualOverflow = aMetrics.VisualOverflow(); |
1150 | 0 | visualOverflow.UnionRect(visualOverflow, altFeedbackSize); |
1151 | 0 | } else { |
1152 | 0 | // We've just reflowed and we should have an accurate size, so we're ready |
1153 | 0 | // to request a decode. |
1154 | 0 | MaybeDecodeForPredictedSize(); |
1155 | 0 | } |
1156 | 0 | FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); |
1157 | 0 |
|
1158 | 0 | if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) { |
1159 | 0 | nsIPresShell* shell = PresShell(); |
1160 | 0 | mReflowCallbackPosted = true; |
1161 | 0 | shell->PostReflowCallback(this); |
1162 | 0 | } |
1163 | 0 |
|
1164 | 0 | NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, |
1165 | 0 | ("exit nsImageFrame::Reflow: size=%d,%d", |
1166 | 0 | aMetrics.Width(), aMetrics.Height())); |
1167 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics); |
1168 | 0 | } |
1169 | | |
1170 | | bool |
1171 | | nsImageFrame::ReflowFinished() |
1172 | 0 | { |
1173 | 0 | mReflowCallbackPosted = false; |
1174 | 0 |
|
1175 | 0 | // XXX(seth): We don't need this. The purpose of updating visibility |
1176 | 0 | // synchronously is to ensure that animated images start animating |
1177 | 0 | // immediately. In the short term, however, |
1178 | 0 | // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that |
1179 | 0 | // animations start as soon as the image is painted for the first time, and in |
1180 | 0 | // the long term we want to update visibility information from the display |
1181 | 0 | // list whenever we paint, so we don't actually need to do this. However, to |
1182 | 0 | // avoid behavior changes during the transition from the old image visibility |
1183 | 0 | // code, we'll leave it in for now. |
1184 | 0 | UpdateVisibilitySynchronously(); |
1185 | 0 |
|
1186 | 0 | return false; |
1187 | 0 | } |
1188 | | |
1189 | | void |
1190 | | nsImageFrame::ReflowCallbackCanceled() |
1191 | 0 | { |
1192 | 0 | mReflowCallbackPosted = false; |
1193 | 0 | } |
1194 | | |
1195 | | // Computes the width of the specified string. aMaxWidth specifies the maximum |
1196 | | // width available. Once this limit is reached no more characters are measured. |
1197 | | // The number of characters that fit within the maximum width are returned in |
1198 | | // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected |
1199 | | // into the rendering context before this is called (for performance). MMP |
1200 | | nscoord |
1201 | | nsImageFrame::MeasureString(const char16_t* aString, |
1202 | | int32_t aLength, |
1203 | | nscoord aMaxWidth, |
1204 | | uint32_t& aMaxFit, |
1205 | | gfxContext& aContext, |
1206 | | nsFontMetrics& aFontMetrics) |
1207 | 0 | { |
1208 | 0 | nscoord totalWidth = 0; |
1209 | 0 | aFontMetrics.SetTextRunRTL(false); |
1210 | 0 | nscoord spaceWidth = aFontMetrics.SpaceWidth(); |
1211 | 0 |
|
1212 | 0 | aMaxFit = 0; |
1213 | 0 | while (aLength > 0) { |
1214 | 0 | // Find the next place we can line break |
1215 | 0 | uint32_t len = aLength; |
1216 | 0 | bool trailingSpace = false; |
1217 | 0 | for (int32_t i = 0; i < aLength; i++) { |
1218 | 0 | if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) { |
1219 | 0 | len = i; // don't include the space when measuring |
1220 | 0 | trailingSpace = true; |
1221 | 0 | break; |
1222 | 0 | } |
1223 | 0 | } |
1224 | 0 |
|
1225 | 0 | // Measure this chunk of text, and see if it fits |
1226 | 0 | nscoord width = |
1227 | 0 | nsLayoutUtils::AppUnitWidthOfStringBidi(aString, len, this, aFontMetrics, |
1228 | 0 | aContext); |
1229 | 0 | bool fits = (totalWidth + width) <= aMaxWidth; |
1230 | 0 |
|
1231 | 0 | // If it fits on the line, or it's the first word we've processed then |
1232 | 0 | // include it |
1233 | 0 | if (fits || (0 == totalWidth)) { |
1234 | 0 | // New piece fits |
1235 | 0 | totalWidth += width; |
1236 | 0 |
|
1237 | 0 | // If there's a trailing space then see if it fits as well |
1238 | 0 | if (trailingSpace) { |
1239 | 0 | if ((totalWidth + spaceWidth) <= aMaxWidth) { |
1240 | 0 | totalWidth += spaceWidth; |
1241 | 0 | } else { |
1242 | 0 | // Space won't fit. Leave it at the end but don't include it in |
1243 | 0 | // the width |
1244 | 0 | fits = false; |
1245 | 0 | } |
1246 | 0 |
|
1247 | 0 | len++; |
1248 | 0 | } |
1249 | 0 |
|
1250 | 0 | aMaxFit += len; |
1251 | 0 | aString += len; |
1252 | 0 | aLength -= len; |
1253 | 0 | } |
1254 | 0 |
|
1255 | 0 | if (!fits) { |
1256 | 0 | break; |
1257 | 0 | } |
1258 | 0 | } |
1259 | 0 | return totalWidth; |
1260 | 0 | } |
1261 | | |
1262 | | // Formats the alt-text to fit within the specified rectangle. Breaks lines |
1263 | | // between words if a word would extend past the edge of the rectangle |
1264 | | void |
1265 | | nsImageFrame::DisplayAltText(nsPresContext* aPresContext, |
1266 | | gfxContext& aRenderingContext, |
1267 | | const nsString& aAltText, |
1268 | | const nsRect& aRect) |
1269 | 0 | { |
1270 | 0 | // Set font and color |
1271 | 0 | aRenderingContext.SetColor(Color::FromABGR(StyleColor()->mColor)); |
1272 | 0 | RefPtr<nsFontMetrics> fm = |
1273 | 0 | nsLayoutUtils::GetInflatedFontMetricsForFrame(this); |
1274 | 0 |
|
1275 | 0 | // Format the text to display within the formatting rect |
1276 | 0 |
|
1277 | 0 | nscoord maxAscent = fm->MaxAscent(); |
1278 | 0 | nscoord maxDescent = fm->MaxDescent(); |
1279 | 0 | nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate |
1280 | 0 | // length if writing mode is vertical |
1281 | 0 |
|
1282 | 0 | WritingMode wm = GetWritingMode(); |
1283 | 0 | bool isVertical = wm.IsVertical(); |
1284 | 0 |
|
1285 | 0 | fm->SetVertical(isVertical); |
1286 | 0 | fm->SetTextOrientation(StyleVisibility()->mTextOrientation); |
1287 | 0 |
|
1288 | 0 | // XXX It would be nice if there was a way to have the font metrics tell |
1289 | 0 | // use where to break the text given a maximum width. At a minimum we need |
1290 | 0 | // to be able to get the break character... |
1291 | 0 | const char16_t* str = aAltText.get(); |
1292 | 0 | int32_t strLen = aAltText.Length(); |
1293 | 0 | nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0) |
1294 | 0 | : aRect.TopLeft(); |
1295 | 0 | nscoord iSize = isVertical ? aRect.height : aRect.width; |
1296 | 0 |
|
1297 | 0 | if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) { |
1298 | 0 | aPresContext->SetBidiEnabled(); |
1299 | 0 | } |
1300 | 0 |
|
1301 | 0 | // Always show the first line, even if we have to clip it below |
1302 | 0 | bool firstLine = true; |
1303 | 0 | while (strLen > 0) { |
1304 | 0 | if (!firstLine) { |
1305 | 0 | // If we've run out of space, break out of the loop |
1306 | 0 | if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) || |
1307 | 0 | (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) || |
1308 | 0 | (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) { |
1309 | 0 | break; |
1310 | 0 | } |
1311 | 0 | } |
1312 | 0 | |
1313 | 0 | // Determine how much of the text to display on this line |
1314 | 0 | uint32_t maxFit; // number of characters that fit |
1315 | 0 | nscoord strWidth = MeasureString(str, strLen, iSize, maxFit, |
1316 | 0 | aRenderingContext, *fm); |
1317 | 0 |
|
1318 | 0 | // Display the text |
1319 | 0 | nsresult rv = NS_ERROR_FAILURE; |
1320 | 0 |
|
1321 | 0 | if (aPresContext->BidiEnabled()) { |
1322 | 0 | nsBidiDirection dir; |
1323 | 0 | nscoord x, y; |
1324 | 0 |
|
1325 | 0 | if (isVertical) { |
1326 | 0 | x = pt.x + maxDescent; |
1327 | 0 | if (wm.IsBidiLTR()) { |
1328 | 0 | y = aRect.y; |
1329 | 0 | dir = NSBIDI_LTR; |
1330 | 0 | } else { |
1331 | 0 | y = aRect.YMost() - strWidth; |
1332 | 0 | dir = NSBIDI_RTL; |
1333 | 0 | } |
1334 | 0 | } else { |
1335 | 0 | y = pt.y + maxAscent; |
1336 | 0 | if (wm.IsBidiLTR()) { |
1337 | 0 | x = aRect.x; |
1338 | 0 | dir = NSBIDI_LTR; |
1339 | 0 | } else { |
1340 | 0 | x = aRect.XMost() - strWidth; |
1341 | 0 | dir = NSBIDI_RTL; |
1342 | 0 | } |
1343 | 0 | } |
1344 | 0 |
|
1345 | 0 | rv = nsBidiPresUtils::RenderText(str, maxFit, dir, |
1346 | 0 | aPresContext, aRenderingContext, |
1347 | 0 | aRenderingContext.GetDrawTarget(), |
1348 | 0 | *fm, x, y); |
1349 | 0 | } |
1350 | 0 | if (NS_FAILED(rv)) { |
1351 | 0 | nsLayoutUtils::DrawUniDirString(str, maxFit, |
1352 | 0 | isVertical |
1353 | 0 | ? nsPoint(pt.x + maxDescent, pt.y) |
1354 | 0 | : nsPoint(pt.x, pt.y + maxAscent), |
1355 | 0 | *fm, aRenderingContext); |
1356 | 0 | } |
1357 | 0 |
|
1358 | 0 | // Move to the next line |
1359 | 0 | str += maxFit; |
1360 | 0 | strLen -= maxFit; |
1361 | 0 | if (wm.IsVerticalRL()) { |
1362 | 0 | pt.x -= lineHeight; |
1363 | 0 | } else if (wm.IsVerticalLR()) { |
1364 | 0 | pt.x += lineHeight; |
1365 | 0 | } else { |
1366 | 0 | pt.y += lineHeight; |
1367 | 0 | } |
1368 | 0 |
|
1369 | 0 | firstLine = false; |
1370 | 0 | } |
1371 | 0 | } |
1372 | | |
1373 | | struct nsRecessedBorder : public nsStyleBorder { |
1374 | | nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext) |
1375 | | : nsStyleBorder(aPresContext) |
1376 | 0 | { |
1377 | 0 | NS_FOR_CSS_SIDES(side) { |
1378 | 0 | BorderColorFor(side) = StyleComplexColor::Black(); |
1379 | 0 | mBorder.Side(side) = aBorderWidth; |
1380 | 0 | // Note: use SetBorderStyle here because we want to affect |
1381 | 0 | // mComputedBorder |
1382 | 0 | SetBorderStyle(side, NS_STYLE_BORDER_STYLE_INSET); |
1383 | 0 | } |
1384 | 0 | } |
1385 | | }; |
1386 | | |
1387 | | class nsDisplayAltFeedback final : public nsDisplayItem |
1388 | | { |
1389 | | public: |
1390 | | nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) |
1391 | 0 | : nsDisplayItem(aBuilder, aFrame) {} |
1392 | | |
1393 | | virtual nsDisplayItemGeometry* |
1394 | | AllocateGeometry(nsDisplayListBuilder* aBuilder) override |
1395 | 0 | { |
1396 | 0 | return new nsDisplayItemGenericImageGeometry(this, aBuilder); |
1397 | 0 | } |
1398 | | |
1399 | | virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, |
1400 | | const nsDisplayItemGeometry* aGeometry, |
1401 | | nsRegion* aInvalidRegion) const override |
1402 | 0 | { |
1403 | 0 | auto geometry = |
1404 | 0 | static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); |
1405 | 0 |
|
1406 | 0 | if (aBuilder->ShouldSyncDecodeImages() && |
1407 | 0 | geometry->ShouldInvalidateToSyncDecodeImages()) { |
1408 | 0 | bool snap; |
1409 | 0 | aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); |
1410 | 0 | } |
1411 | 0 |
|
1412 | 0 | nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); |
1413 | 0 | } |
1414 | | |
1415 | | virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, |
1416 | | bool* aSnap) const override |
1417 | 0 | { |
1418 | 0 | *aSnap = false; |
1419 | 0 | return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); |
1420 | 0 | } |
1421 | | |
1422 | | virtual void Paint(nsDisplayListBuilder* aBuilder, |
1423 | | gfxContext* aCtx) override |
1424 | 0 | { |
1425 | 0 | // Always sync decode, because these icons are UI, and since they're not |
1426 | 0 | // discardable we'll pay the price of sync decoding at most once. |
1427 | 0 | uint32_t flags = imgIContainer::FLAG_SYNC_DECODE; |
1428 | 0 |
|
1429 | 0 | nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); |
1430 | 0 | ImgDrawResult result = |
1431 | 0 | f->DisplayAltFeedback(*aCtx, |
1432 | 0 | GetPaintRect(), |
1433 | 0 | ToReferenceFrame(), |
1434 | 0 | flags); |
1435 | 0 |
|
1436 | 0 | nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); |
1437 | 0 | } |
1438 | | |
1439 | | NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK) |
1440 | | }; |
1441 | | |
1442 | | ImgDrawResult |
1443 | | nsImageFrame::DisplayAltFeedback(gfxContext& aRenderingContext, |
1444 | | const nsRect& aDirtyRect, |
1445 | | nsPoint aPt, |
1446 | | uint32_t aFlags) |
1447 | 0 | { |
1448 | 0 | // We should definitely have a gIconLoad here. |
1449 | 0 | MOZ_ASSERT(gIconLoad, "How did we succeed in Init then?"); |
1450 | 0 |
|
1451 | 0 | // Whether we draw the broken or loading icon. |
1452 | 0 | bool isLoading = IMAGE_OK(GetContent()->AsElement()->State(), true); |
1453 | 0 |
|
1454 | 0 | // Calculate the inner area |
1455 | 0 | nsRect inner = GetInnerArea() + aPt; |
1456 | 0 |
|
1457 | 0 | // Display a recessed one pixel border |
1458 | 0 | nscoord borderEdgeWidth = nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH); |
1459 | 0 |
|
1460 | 0 | // if inner area is empty, then make it big enough for at least the icon |
1461 | 0 | if (inner.IsEmpty()){ |
1462 | 0 | inner.SizeTo(2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH)), |
1463 | 0 | 2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH))); |
1464 | 0 | } |
1465 | 0 |
|
1466 | 0 | // Make sure we have enough room to actually render the border within |
1467 | 0 | // our frame bounds |
1468 | 0 | if ((inner.width < 2 * borderEdgeWidth) || (inner.height < 2 * borderEdgeWidth)) { |
1469 | 0 | return ImgDrawResult::SUCCESS; |
1470 | 0 | } |
1471 | 0 | |
1472 | 0 | // Paint the border |
1473 | 0 | if (!isLoading || gIconLoad->mPrefShowLoadingPlaceholder) { |
1474 | 0 | nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext()); |
1475 | 0 |
|
1476 | 0 | // Assert that we're not drawing a border-image here; if we were, we |
1477 | 0 | // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder returns. |
1478 | 0 | MOZ_ASSERT(recessedBorder.mBorderImageSource.GetType() == eStyleImageType_Null); |
1479 | 0 |
|
1480 | 0 | Unused << |
1481 | 0 | nsCSSRendering::PaintBorderWithStyleBorder(PresContext(), aRenderingContext, |
1482 | 0 | this, inner, inner, |
1483 | 0 | recessedBorder, mComputedStyle, |
1484 | 0 | PaintBorderFlags::SYNC_DECODE_IMAGES); |
1485 | 0 | } |
1486 | 0 |
|
1487 | 0 | // Adjust the inner rect to account for the one pixel recessed border, |
1488 | 0 | // and a six pixel padding on each edge |
1489 | 0 | inner.Deflate(nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH), |
1490 | 0 | nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH)); |
1491 | 0 | if (inner.IsEmpty()) { |
1492 | 0 | return ImgDrawResult::SUCCESS; |
1493 | 0 | } |
1494 | 0 | |
1495 | 0 | DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); |
1496 | 0 |
|
1497 | 0 | // Clip so we don't render outside the inner rect |
1498 | 0 | aRenderingContext.Save(); |
1499 | 0 | aRenderingContext.Clip( |
1500 | 0 | NSRectToSnappedRect(inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget)); |
1501 | 0 |
|
1502 | 0 | ImgDrawResult result = ImgDrawResult::NOT_READY; |
1503 | 0 |
|
1504 | 0 | // Check if we should display image placeholders |
1505 | 0 | if (!gIconLoad->mPrefShowPlaceholders || |
1506 | 0 | (isLoading && !gIconLoad->mPrefShowLoadingPlaceholder)) { |
1507 | 0 | result = ImgDrawResult::SUCCESS; |
1508 | 0 | } else { |
1509 | 0 | nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE); |
1510 | 0 |
|
1511 | 0 | imgIRequest* request = isLoading |
1512 | 0 | ? nsImageFrame::gIconLoad->mLoadingImage |
1513 | 0 | : nsImageFrame::gIconLoad->mBrokenImage; |
1514 | 0 |
|
1515 | 0 | // If we weren't previously displaying an icon, register ourselves |
1516 | 0 | // as an observer for load and animation updates and flag that we're |
1517 | 0 | // doing so now. |
1518 | 0 | if (request && !mDisplayingIcon) { |
1519 | 0 | gIconLoad->AddIconObserver(this); |
1520 | 0 | mDisplayingIcon = true; |
1521 | 0 | } |
1522 | 0 |
|
1523 | 0 | WritingMode wm = GetWritingMode(); |
1524 | 0 | bool flushRight = |
1525 | 0 | (!wm.IsVertical() && !wm.IsBidiLTR()) || wm.IsVerticalRL(); |
1526 | 0 |
|
1527 | 0 | // If the icon in question is loaded, draw it. |
1528 | 0 | uint32_t imageStatus = 0; |
1529 | 0 | if (request) |
1530 | 0 | request->GetImageStatus(&imageStatus); |
1531 | 0 | if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE && |
1532 | 0 | !(imageStatus & imgIRequest::STATUS_ERROR)) { |
1533 | 0 | nsCOMPtr<imgIContainer> imgCon; |
1534 | 0 | request->GetImage(getter_AddRefs(imgCon)); |
1535 | 0 | MOZ_ASSERT(imgCon, "Load complete, but no image container?"); |
1536 | 0 | nsRect dest(flushRight ? inner.XMost() - size : inner.x, |
1537 | 0 | inner.y, size, size); |
1538 | 0 | result = nsLayoutUtils::DrawSingleImage(aRenderingContext, PresContext(), imgCon, |
1539 | 0 | nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect, |
1540 | 0 | /* no SVGImageContext */ Nothing(), aFlags); |
1541 | 0 | } |
1542 | 0 |
|
1543 | 0 | // If we could not draw the icon, just draw some graffiti in the mean time. |
1544 | 0 | if (result == ImgDrawResult::NOT_READY) { |
1545 | 0 | ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 1.f))); |
1546 | 0 |
|
1547 | 0 | nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x; |
1548 | 0 |
|
1549 | 0 | // stroked rect: |
1550 | 0 | nsRect rect(iconXPos, inner.y, size, size); |
1551 | 0 | Rect devPxRect = |
1552 | 0 | ToRect(nsLayoutUtils::RectToGfxRect(rect, PresContext()->AppUnitsPerDevPixel())); |
1553 | 0 | drawTarget->StrokeRect(devPxRect, color); |
1554 | 0 |
|
1555 | 0 | // filled circle in bottom right quadrant of stroked rect: |
1556 | 0 | nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2); |
1557 | 0 | rect = nsRect(iconXPos + size/2, inner.y + size/2, |
1558 | 0 | size/2 - twoPX, size/2 - twoPX); |
1559 | 0 | devPxRect = |
1560 | 0 | ToRect(nsLayoutUtils::RectToGfxRect(rect, PresContext()->AppUnitsPerDevPixel())); |
1561 | 0 | RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder(); |
1562 | 0 | AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size()); |
1563 | 0 | RefPtr<Path> ellipse = builder->Finish(); |
1564 | 0 | drawTarget->Fill(ellipse, color); |
1565 | 0 | } |
1566 | 0 |
|
1567 | 0 | // Reduce the inner rect by the width of the icon, and leave an |
1568 | 0 | // additional ICON_PADDING pixels for padding |
1569 | 0 | int32_t paddedIconSize = |
1570 | 0 | nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING); |
1571 | 0 | if (wm.IsVertical()) { |
1572 | 0 | inner.y += paddedIconSize; |
1573 | 0 | inner.height -= paddedIconSize; |
1574 | 0 | } else { |
1575 | 0 | if (!flushRight) { |
1576 | 0 | inner.x += paddedIconSize; |
1577 | 0 | } |
1578 | 0 | inner.width -= paddedIconSize; |
1579 | 0 | } |
1580 | 0 | } |
1581 | 0 |
|
1582 | 0 | // If there's still room, display the alt-text |
1583 | 0 | if (!inner.IsEmpty()) { |
1584 | 0 | nsAutoString altText; |
1585 | 0 | nsCSSFrameConstructor::GetAlternateTextFor(mContent->AsElement(), |
1586 | 0 | mContent->NodeInfo()->NameAtom(), |
1587 | 0 | altText); |
1588 | 0 | DisplayAltText(PresContext(), aRenderingContext, altText, inner); |
1589 | 0 | } |
1590 | 0 |
|
1591 | 0 | aRenderingContext.Restore(); |
1592 | 0 |
|
1593 | 0 | return result; |
1594 | 0 | } |
1595 | | |
1596 | | #ifdef DEBUG |
1597 | | static void PaintDebugImageMap(nsIFrame* aFrame, DrawTarget* aDrawTarget, |
1598 | | const nsRect& aDirtyRect, nsPoint aPt) |
1599 | | { |
1600 | | nsImageFrame* f = static_cast<nsImageFrame*>(aFrame); |
1601 | | nsRect inner = f->GetInnerArea() + aPt; |
1602 | | gfxPoint devPixelOffset = |
1603 | | nsLayoutUtils::PointToGfxPoint(inner.TopLeft(), |
1604 | | aFrame->PresContext()->AppUnitsPerDevPixel()); |
1605 | | AutoRestoreTransform autoRestoreTransform(aDrawTarget); |
1606 | | aDrawTarget->SetTransform( |
1607 | | aDrawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); |
1608 | | f->GetImageMap()->Draw(aFrame, *aDrawTarget, |
1609 | | ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)))); |
1610 | | } |
1611 | | #endif |
1612 | | |
1613 | | void |
1614 | | nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, |
1615 | | gfxContext* aCtx) |
1616 | 0 | { |
1617 | 0 | uint32_t flags = imgIContainer::FLAG_NONE; |
1618 | 0 | if (aBuilder->ShouldSyncDecodeImages()) { |
1619 | 0 | flags |= imgIContainer::FLAG_SYNC_DECODE; |
1620 | 0 | } |
1621 | 0 | if (aBuilder->IsPaintingToWindow()) { |
1622 | 0 | flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; |
1623 | 0 | } |
1624 | 0 |
|
1625 | 0 | ImgDrawResult result = static_cast<nsImageFrame*>(mFrame)-> |
1626 | 0 | PaintImage(*aCtx, ToReferenceFrame(), GetPaintRect(), mImage, flags); |
1627 | 0 |
|
1628 | 0 | if (result == ImgDrawResult::NOT_READY || |
1629 | 0 | result == ImgDrawResult::INCOMPLETE || |
1630 | 0 | result == ImgDrawResult::TEMPORARY_ERROR) { |
1631 | 0 | // If the current image failed to paint because it's still loading or |
1632 | 0 | // decoding, try painting the previous image. |
1633 | 0 | if (mPrevImage) { |
1634 | 0 | result = static_cast<nsImageFrame*>(mFrame)-> |
1635 | 0 | PaintImage(*aCtx, ToReferenceFrame(), GetPaintRect(), mPrevImage, flags); |
1636 | 0 | } |
1637 | 0 | } |
1638 | 0 |
|
1639 | 0 | nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); |
1640 | 0 | } |
1641 | | |
1642 | | nsDisplayItemGeometry* |
1643 | | nsDisplayImage::AllocateGeometry(nsDisplayListBuilder* aBuilder) |
1644 | 0 | { |
1645 | 0 | return new nsDisplayItemGenericImageGeometry(this, aBuilder); |
1646 | 0 | } |
1647 | | |
1648 | | void |
1649 | | nsDisplayImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, |
1650 | | const nsDisplayItemGeometry* aGeometry, |
1651 | | nsRegion* aInvalidRegion) const |
1652 | 0 | { |
1653 | 0 | auto geometry = |
1654 | 0 | static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); |
1655 | 0 |
|
1656 | 0 | if (aBuilder->ShouldSyncDecodeImages() && |
1657 | 0 | geometry->ShouldInvalidateToSyncDecodeImages()) { |
1658 | 0 | bool snap; |
1659 | 0 | aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); |
1660 | 0 | } |
1661 | 0 |
|
1662 | 0 | nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); |
1663 | 0 | } |
1664 | | |
1665 | | already_AddRefed<imgIContainer> |
1666 | | nsDisplayImage::GetImage() |
1667 | 0 | { |
1668 | 0 | nsCOMPtr<imgIContainer> image = mImage; |
1669 | 0 | return image.forget(); |
1670 | 0 | } |
1671 | | |
1672 | | nsRect |
1673 | | nsDisplayImage::GetDestRect() const |
1674 | 0 | { |
1675 | 0 | bool snap = true; |
1676 | 0 | const nsRect frameContentBox = GetBounds(&snap); |
1677 | 0 |
|
1678 | 0 | nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame); |
1679 | 0 | return imageFrame->PredictedDestRect(frameContentBox); |
1680 | 0 | } |
1681 | | |
1682 | | LayerState |
1683 | | nsDisplayImage::GetLayerState(nsDisplayListBuilder* aBuilder, |
1684 | | LayerManager* aManager, |
1685 | | const ContainerLayerParameters& aParameters) |
1686 | 0 | { |
1687 | 0 | if (!nsDisplayItem::ForceActiveLayers()) { |
1688 | 0 | bool animated = false; |
1689 | 0 | if (!nsLayoutUtils::AnimatedImageLayersEnabled() || |
1690 | 0 | mImage->GetType() != imgIContainer::TYPE_RASTER || |
1691 | 0 | NS_FAILED(mImage->GetAnimated(&animated)) || |
1692 | 0 | !animated) { |
1693 | 0 | if (!aManager->IsCompositingCheap() || |
1694 | 0 | !nsLayoutUtils::GPUImageScalingEnabled()) { |
1695 | 0 | return LAYER_NONE; |
1696 | 0 | } |
1697 | 0 | } |
1698 | 0 | |
1699 | 0 | if (!animated) { |
1700 | 0 | int32_t imageWidth; |
1701 | 0 | int32_t imageHeight; |
1702 | 0 | mImage->GetWidth(&imageWidth); |
1703 | 0 | mImage->GetHeight(&imageHeight); |
1704 | 0 |
|
1705 | 0 | NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); |
1706 | 0 |
|
1707 | 0 | const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); |
1708 | 0 | const LayoutDeviceRect destRect = |
1709 | 0 | LayoutDeviceRect::FromAppUnits(GetDestRect(), factor); |
1710 | 0 | const LayerRect destLayerRect = destRect * aParameters.Scale(); |
1711 | 0 |
|
1712 | 0 | // Calculate the scaling factor for the frame. |
1713 | 0 | const gfxSize scale = gfxSize(destLayerRect.width / imageWidth, |
1714 | 0 | destLayerRect.height / imageHeight); |
1715 | 0 |
|
1716 | 0 | // If we are not scaling at all, no point in separating this into a layer. |
1717 | 0 | if (scale.width == 1.0f && scale.height == 1.0f) { |
1718 | 0 | return LAYER_NONE; |
1719 | 0 | } |
1720 | 0 | |
1721 | 0 | // If the target size is pretty small, no point in using a layer. |
1722 | 0 | if (destLayerRect.width * destLayerRect.height < 64 * 64) { |
1723 | 0 | return LAYER_NONE; |
1724 | 0 | } |
1725 | 0 | } |
1726 | 0 | } |
1727 | 0 | |
1728 | 0 | |
1729 | 0 | if (!CanOptimizeToImageLayer(aManager, aBuilder)) { |
1730 | 0 | return LAYER_NONE; |
1731 | 0 | } |
1732 | 0 | |
1733 | 0 | // Image layer doesn't support draw focus ring for image map. |
1734 | 0 | nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); |
1735 | 0 | if (f->HasImageMap()) { |
1736 | 0 | return LAYER_NONE; |
1737 | 0 | } |
1738 | 0 | |
1739 | 0 | return LAYER_ACTIVE; |
1740 | 0 | } |
1741 | | |
1742 | | |
1743 | | /* virtual */ nsRegion |
1744 | | nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, |
1745 | | bool* aSnap) const |
1746 | 0 | { |
1747 | 0 | *aSnap = false; |
1748 | 0 | if (mImage && mImage->WillDrawOpaqueNow()) { |
1749 | 0 | const nsRect frameContentBox = GetBounds(aSnap); |
1750 | 0 | return GetDestRect().Intersect(frameContentBox); |
1751 | 0 | } |
1752 | 0 | return nsRegion(); |
1753 | 0 | } |
1754 | | |
1755 | | already_AddRefed<Layer> |
1756 | | nsDisplayImage::BuildLayer(nsDisplayListBuilder* aBuilder, |
1757 | | LayerManager* aManager, |
1758 | | const ContainerLayerParameters& aParameters) |
1759 | 0 | { |
1760 | 0 | uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY; |
1761 | 0 | if (aBuilder->ShouldSyncDecodeImages()) { |
1762 | 0 | flags |= imgIContainer::FLAG_SYNC_DECODE; |
1763 | 0 | } |
1764 | 0 |
|
1765 | 0 | RefPtr<ImageContainer> container = |
1766 | 0 | mImage->GetImageContainer(aManager, flags); |
1767 | 0 | if (!container || !container->HasCurrentImage()) { |
1768 | 0 | return nullptr; |
1769 | 0 | } |
1770 | 0 | |
1771 | 0 | RefPtr<ImageLayer> layer = static_cast<ImageLayer*> |
1772 | 0 | (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); |
1773 | 0 | if (!layer) { |
1774 | 0 | layer = aManager->CreateImageLayer(); |
1775 | 0 | if (!layer) |
1776 | 0 | return nullptr; |
1777 | 0 | } |
1778 | 0 | layer->SetContainer(container); |
1779 | 0 | ConfigureLayer(layer, aParameters); |
1780 | 0 | return layer.forget(); |
1781 | 0 | } |
1782 | | |
1783 | | bool |
1784 | | nsDisplayImage::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder, |
1785 | | mozilla::wr::IpcResourceUpdateQueue& aResources, |
1786 | | const StackingContextHelper& aSc, |
1787 | | WebRenderLayerManager* aManager, |
1788 | | nsDisplayListBuilder* aDisplayListBuilder) |
1789 | 0 | { |
1790 | 0 | if (!mImage) { |
1791 | 0 | return false; |
1792 | 0 | } |
1793 | 0 | |
1794 | 0 | if (mFrame->IsImageFrame()) { |
1795 | 0 | // Image layer doesn't support draw focus ring for image map. |
1796 | 0 | nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); |
1797 | 0 | if (f->HasImageMap()) { |
1798 | 0 | return false; |
1799 | 0 | } |
1800 | 0 | } |
1801 | 0 | |
1802 | 0 | uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY; |
1803 | 0 | if (aDisplayListBuilder->IsPaintingToWindow()) { |
1804 | 0 | flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; |
1805 | 0 | } |
1806 | 0 | if (aDisplayListBuilder->ShouldSyncDecodeImages()) { |
1807 | 0 | flags |= imgIContainer::FLAG_SYNC_DECODE; |
1808 | 0 | } |
1809 | 0 |
|
1810 | 0 | const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); |
1811 | 0 | const LayoutDeviceRect destRect( |
1812 | 0 | LayoutDeviceRect::FromAppUnits(GetDestRect(), factor)); |
1813 | 0 | Maybe<SVGImageContext> svgContext; |
1814 | 0 | IntSize decodeSize = |
1815 | 0 | nsLayoutUtils::ComputeImageContainerDrawingParameters(mImage, mFrame, destRect, |
1816 | 0 | aSc, flags, svgContext); |
1817 | 0 |
|
1818 | 0 | RefPtr<layers::ImageContainer> container; |
1819 | 0 | ImgDrawResult drawResult = |
1820 | 0 | mImage->GetImageContainerAtSize(aManager, decodeSize, svgContext, |
1821 | 0 | flags, getter_AddRefs(container)); |
1822 | 0 |
|
1823 | 0 | // While we got a container, it may not contain a fully decoded surface. If |
1824 | 0 | // that is the case, and we have an image we were previously displaying which |
1825 | 0 | // has a fully decoded surface, then we should prefer the previous image. |
1826 | 0 | bool updatePrevImage = false; |
1827 | 0 | switch (drawResult) { |
1828 | 0 | case ImgDrawResult::NOT_READY: |
1829 | 0 | case ImgDrawResult::INCOMPLETE: |
1830 | 0 | case ImgDrawResult::TEMPORARY_ERROR: |
1831 | 0 | if (mPrevImage && mPrevImage != mImage) { |
1832 | 0 | RefPtr<ImageContainer> prevContainer; |
1833 | 0 | drawResult = mPrevImage->GetImageContainerAtSize(aManager, decodeSize, |
1834 | 0 | svgContext, flags, |
1835 | 0 | getter_AddRefs(prevContainer)); |
1836 | 0 | if (prevContainer && drawResult == ImgDrawResult::SUCCESS) { |
1837 | 0 | container = std::move(prevContainer); |
1838 | 0 | break; |
1839 | 0 | } |
1840 | 0 | |
1841 | 0 | // Previous image was unusable; we can forget about it. |
1842 | 0 | updatePrevImage = true; |
1843 | 0 | } |
1844 | 0 | break; |
1845 | 0 | case ImgDrawResult::NOT_SUPPORTED: |
1846 | 0 | return false; |
1847 | 0 | default: |
1848 | 0 | updatePrevImage = mPrevImage != mImage; |
1849 | 0 | break; |
1850 | 0 | } |
1851 | 0 | |
1852 | 0 | // The previous image was not used, and is different from the current image. |
1853 | 0 | // We should forget about it. We need to update the frame as well because the |
1854 | 0 | // display item may get recreated. |
1855 | 0 | if (updatePrevImage) { |
1856 | 0 | mPrevImage = mImage; |
1857 | 0 | if (mFrame->IsImageFrame()) { |
1858 | 0 | nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); |
1859 | 0 | f->mPrevImage = f->mImage; |
1860 | 0 | } |
1861 | 0 | } |
1862 | 0 |
|
1863 | 0 | // If the image container is empty, we don't want to fallback. Any other |
1864 | 0 | // failure will be due to resource constraints and fallback is unlikely to |
1865 | 0 | // help us. Hence we can ignore the return value from PushImage. |
1866 | 0 | if (container) { |
1867 | 0 | aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources, aSc, destRect); |
1868 | 0 | } |
1869 | 0 |
|
1870 | 0 | nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, drawResult); |
1871 | 0 | return true; |
1872 | 0 | } |
1873 | | |
1874 | | ImgDrawResult |
1875 | | nsImageFrame::PaintImage(gfxContext& aRenderingContext, nsPoint aPt, |
1876 | | const nsRect& aDirtyRect, imgIContainer* aImage, |
1877 | | uint32_t aFlags) |
1878 | 0 | { |
1879 | 0 | DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); |
1880 | 0 |
|
1881 | 0 | // Render the image into our content area (the area inside |
1882 | 0 | // the borders and padding) |
1883 | 0 | NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width"); |
1884 | 0 |
|
1885 | 0 | // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here, |
1886 | 0 | // because GetInnerArea() might be smaller if we're fragmented, whereas |
1887 | 0 | // mComputedSize has our full content-box size (which we need for |
1888 | 0 | // ComputeObjectDestRect to work correctly). |
1889 | 0 | nsRect constraintRect(aPt + GetInnerArea().TopLeft(), mComputedSize); |
1890 | 0 | constraintRect.y -= GetContinuationOffset(); |
1891 | 0 |
|
1892 | 0 | nsPoint anchorPoint; |
1893 | 0 | nsRect dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect, |
1894 | 0 | mIntrinsicSize, |
1895 | 0 | mIntrinsicRatio, |
1896 | 0 | StylePosition(), |
1897 | 0 | &anchorPoint); |
1898 | 0 |
|
1899 | 0 | uint32_t flags = aFlags; |
1900 | 0 | if (mForceSyncDecoding) { |
1901 | 0 | flags |= imgIContainer::FLAG_SYNC_DECODE; |
1902 | 0 | } |
1903 | 0 |
|
1904 | 0 | Maybe<SVGImageContext> svgContext; |
1905 | 0 | SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage); |
1906 | 0 |
|
1907 | 0 | ImgDrawResult result = |
1908 | 0 | nsLayoutUtils::DrawSingleImage(aRenderingContext, |
1909 | 0 | PresContext(), aImage, |
1910 | 0 | nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect, |
1911 | 0 | svgContext, flags, &anchorPoint); |
1912 | 0 |
|
1913 | 0 | if (nsImageMap* map = GetImageMap()) { |
1914 | 0 | gfxPoint devPixelOffset = |
1915 | 0 | nsLayoutUtils::PointToGfxPoint(dest.TopLeft(), |
1916 | 0 | PresContext()->AppUnitsPerDevPixel()); |
1917 | 0 | AutoRestoreTransform autoRestoreTransform(drawTarget); |
1918 | 0 | drawTarget->SetTransform( |
1919 | 0 | drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); |
1920 | 0 |
|
1921 | 0 | // solid white stroke: |
1922 | 0 | ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f))); |
1923 | 0 | map->Draw(this, *drawTarget, white); |
1924 | 0 |
|
1925 | 0 | // then dashed black stroke over the top: |
1926 | 0 | ColorPattern black(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f))); |
1927 | 0 | StrokeOptions strokeOptions; |
1928 | 0 | nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED); |
1929 | 0 | map->Draw(this, *drawTarget, black, strokeOptions); |
1930 | 0 | } |
1931 | 0 |
|
1932 | 0 | if (result == ImgDrawResult::SUCCESS) { |
1933 | 0 | mPrevImage = aImage; |
1934 | 0 | } else if (result == ImgDrawResult::BAD_IMAGE) { |
1935 | 0 | mPrevImage = nullptr; |
1936 | 0 | } |
1937 | 0 |
|
1938 | 0 | return result; |
1939 | 0 | } |
1940 | | |
1941 | | already_AddRefed<imgIRequest> |
1942 | | nsImageFrame::GetCurrentRequest() const |
1943 | 0 | { |
1944 | 0 | if (mKind != Kind::ImageElement) { |
1945 | 0 | return do_AddRef(mContentURLRequest); |
1946 | 0 | } |
1947 | 0 | |
1948 | 0 | MOZ_ASSERT(!mContentURLRequest); |
1949 | 0 |
|
1950 | 0 | nsCOMPtr<imgIRequest> request; |
1951 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); |
1952 | 0 | MOZ_ASSERT(imageLoader); |
1953 | 0 | imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, |
1954 | 0 | getter_AddRefs(request)); |
1955 | 0 | return request.forget(); |
1956 | 0 | } |
1957 | | |
1958 | | void |
1959 | | nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
1960 | | const nsDisplayListSet& aLists) |
1961 | 0 | { |
1962 | 0 | if (!IsVisibleForPainting(aBuilder)) |
1963 | 0 | return; |
1964 | 0 | |
1965 | 0 | DisplayBorderBackgroundOutline(aBuilder, aLists); |
1966 | 0 |
|
1967 | 0 | uint32_t clipFlags = |
1968 | 0 | nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ? |
1969 | 0 | 0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT; |
1970 | 0 |
|
1971 | 0 | DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox |
1972 | 0 | clip(aBuilder, this, clipFlags); |
1973 | 0 |
|
1974 | 0 | if (mComputedSize.width != 0 && mComputedSize.height != 0) { |
1975 | 0 | EventStates contentState = mContent->AsElement()->State(); |
1976 | 0 | bool imageOK = IMAGE_OK(contentState, true); |
1977 | 0 |
|
1978 | 0 | nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest(); |
1979 | 0 |
|
1980 | 0 | // XXX(seth): The SizeIsAvailable check here should not be necessary - the |
1981 | 0 | // intention is that a non-null mImage means we have a size, but there is |
1982 | 0 | // currently some code that violates this invariant. |
1983 | 0 | if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) { |
1984 | 0 | // No image yet, or image load failed. Draw the alt-text and an icon |
1985 | 0 | // indicating the status |
1986 | 0 | aLists.Content()->AppendToTop( |
1987 | 0 | MakeDisplayItem<nsDisplayAltFeedback>(aBuilder, this)); |
1988 | 0 |
|
1989 | 0 | // This image is visible (we are being asked to paint it) but it's not |
1990 | 0 | // decoded yet. And we are not going to ask the image to draw, so this |
1991 | 0 | // may be the only chance to tell it that it should decode. |
1992 | 0 | if (currentRequest) { |
1993 | 0 | uint32_t status = 0; |
1994 | 0 | currentRequest->GetImageStatus(&status); |
1995 | 0 | if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) { |
1996 | 0 | MaybeDecodeForPredictedSize(); |
1997 | 0 | } |
1998 | 0 | // Increase loading priority if the image is ready to be displayed. |
1999 | 0 | if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)){ |
2000 | 0 | currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY); |
2001 | 0 | } |
2002 | 0 | } |
2003 | 0 | } else { |
2004 | 0 | aLists.Content()->AppendToTop( |
2005 | 0 | MakeDisplayItem<nsDisplayImage>(aBuilder, this, mImage, mPrevImage)); |
2006 | 0 |
|
2007 | 0 | // If we were previously displaying an icon, we're not anymore |
2008 | 0 | if (mDisplayingIcon) { |
2009 | 0 | gIconLoad->RemoveIconObserver(this); |
2010 | 0 | mDisplayingIcon = false; |
2011 | 0 | } |
2012 | 0 |
|
2013 | | #ifdef DEBUG |
2014 | | if (GetShowFrameBorders() && GetImageMap()) { |
2015 | | aLists.Outlines()->AppendToTop( |
2016 | | MakeDisplayItem<nsDisplayGeneric>(aBuilder, this, PaintDebugImageMap, "DebugImageMap", |
2017 | | DisplayItemType::TYPE_DEBUG_IMAGE_MAP)); |
2018 | | } |
2019 | | #endif |
2020 | | } |
2021 | 0 | } |
2022 | 0 |
|
2023 | 0 | if (ShouldDisplaySelection()) { |
2024 | 0 | DisplaySelectionOverlay(aBuilder, aLists.Content(), |
2025 | 0 | nsISelectionDisplay::DISPLAY_IMAGES); |
2026 | 0 | } |
2027 | 0 | } |
2028 | | |
2029 | | bool |
2030 | | nsImageFrame::ShouldDisplaySelection() |
2031 | 0 | { |
2032 | 0 | int16_t displaySelection = PresShell()->GetSelectionFlags(); |
2033 | 0 | if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES)) |
2034 | 0 | return false;//no need to check the blue border, we cannot be drawn selected |
2035 | 0 | |
2036 | 0 | // If the image is the only selected node, don't draw the selection overlay. |
2037 | 0 | // This can happen when selecting an image in contenteditable context. |
2038 | 0 | if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) { |
2039 | 0 | if (const nsFrameSelection* frameSelection = GetConstFrameSelection()) { |
2040 | 0 | const Selection* selection = frameSelection->GetSelection(SelectionType::eNormal); |
2041 | 0 | if (selection && selection->RangeCount() == 1) { |
2042 | 0 | nsINode* parent = mContent->GetParent(); |
2043 | 0 | int32_t thisOffset = parent->ComputeIndexOf(mContent); |
2044 | 0 | nsRange* range = selection->GetRangeAt(0); |
2045 | 0 | if (range->GetStartContainer() == parent && |
2046 | 0 | range->GetEndContainer() == parent && |
2047 | 0 | static_cast<int32_t>(range->StartOffset()) == thisOffset && |
2048 | 0 | static_cast<int32_t>(range->EndOffset()) == thisOffset + 1) { |
2049 | 0 | return false; |
2050 | 0 | } |
2051 | 0 | } |
2052 | 0 | } |
2053 | 0 | } |
2054 | 0 | return true; |
2055 | 0 | } |
2056 | | |
2057 | | nsImageMap* |
2058 | | nsImageFrame::GetImageMap() |
2059 | 0 | { |
2060 | 0 | if (!mImageMap) { |
2061 | 0 | if (nsIContent* map = GetMapElement()) { |
2062 | 0 | mImageMap = new nsImageMap(); |
2063 | 0 | mImageMap->Init(this, map); |
2064 | 0 | } |
2065 | 0 | } |
2066 | 0 |
|
2067 | 0 | return mImageMap; |
2068 | 0 | } |
2069 | | |
2070 | | bool |
2071 | | nsImageFrame::IsServerImageMap() |
2072 | 0 | { |
2073 | 0 | return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ismap); |
2074 | 0 | } |
2075 | | |
2076 | | // Translate an point that is relative to our frame |
2077 | | // into a localized pixel coordinate that is relative to the |
2078 | | // content area of this frame (inside the border+padding). |
2079 | | void |
2080 | | nsImageFrame::TranslateEventCoords(const nsPoint& aPoint, |
2081 | | nsIntPoint& aResult) |
2082 | 0 | { |
2083 | 0 | nscoord x = aPoint.x; |
2084 | 0 | nscoord y = aPoint.y; |
2085 | 0 |
|
2086 | 0 | // Subtract out border and padding here so that the coordinates are |
2087 | 0 | // now relative to the content area of this frame. |
2088 | 0 | nsRect inner = GetInnerArea(); |
2089 | 0 | x -= inner.x; |
2090 | 0 | y -= inner.y; |
2091 | 0 |
|
2092 | 0 | aResult.x = nsPresContext::AppUnitsToIntCSSPixels(x); |
2093 | 0 | aResult.y = nsPresContext::AppUnitsToIntCSSPixels(y); |
2094 | 0 | } |
2095 | | |
2096 | | bool |
2097 | | nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget, |
2098 | | nsIContent** aNode) |
2099 | 0 | { |
2100 | 0 | bool status = false; |
2101 | 0 | aTarget.Truncate(); |
2102 | 0 | *aHref = nullptr; |
2103 | 0 | *aNode = nullptr; |
2104 | 0 |
|
2105 | 0 | // Walk up the content tree, looking for an nsIDOMAnchorElement |
2106 | 0 | for (nsIContent* content = mContent->GetParent(); |
2107 | 0 | content; content = content->GetParent()) { |
2108 | 0 | nsCOMPtr<dom::Link> link(do_QueryInterface(content)); |
2109 | 0 | if (link) { |
2110 | 0 | nsCOMPtr<nsIURI> href = content->GetHrefURI(); |
2111 | 0 | if (href) { |
2112 | 0 | href.forget(aHref); |
2113 | 0 | } |
2114 | 0 | status = (*aHref != nullptr); |
2115 | 0 |
|
2116 | 0 | RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(content); |
2117 | 0 | if (anchor) { |
2118 | 0 | anchor->GetTarget(aTarget); |
2119 | 0 | } |
2120 | 0 | NS_ADDREF(*aNode = content); |
2121 | 0 | break; |
2122 | 0 | } |
2123 | 0 | } |
2124 | 0 | return status; |
2125 | 0 | } |
2126 | | |
2127 | | nsresult |
2128 | | nsImageFrame::GetContentForEvent(WidgetEvent* aEvent, |
2129 | | nsIContent** aContent) |
2130 | 0 | { |
2131 | 0 | NS_ENSURE_ARG_POINTER(aContent); |
2132 | 0 |
|
2133 | 0 | nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this); |
2134 | 0 | if (f != this) { |
2135 | 0 | return f->GetContentForEvent(aEvent, aContent); |
2136 | 0 | } |
2137 | 0 | |
2138 | 0 | // XXX We need to make this special check for area element's capturing the |
2139 | 0 | // mouse due to bug 135040. Remove it once that's fixed. |
2140 | 0 | nsIContent* capturingContent = |
2141 | 0 | aEvent->HasMouseEventMessage() ? nsIPresShell::GetCapturingContent() : |
2142 | 0 | nullptr; |
2143 | 0 | if (capturingContent && capturingContent->GetPrimaryFrame() == this) { |
2144 | 0 | *aContent = capturingContent; |
2145 | 0 | NS_IF_ADDREF(*aContent); |
2146 | 0 | return NS_OK; |
2147 | 0 | } |
2148 | 0 |
|
2149 | 0 | if (nsImageMap* map = GetImageMap()) { |
2150 | 0 | nsIntPoint p; |
2151 | 0 | TranslateEventCoords( |
2152 | 0 | nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p); |
2153 | 0 | nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y); |
2154 | 0 | if (area) { |
2155 | 0 | area.forget(aContent); |
2156 | 0 | return NS_OK; |
2157 | 0 | } |
2158 | 0 | } |
2159 | 0 | |
2160 | 0 | *aContent = GetContent(); |
2161 | 0 | NS_IF_ADDREF(*aContent); |
2162 | 0 | return NS_OK; |
2163 | 0 | } |
2164 | | |
2165 | | // XXX what should clicks on transparent pixels do? |
2166 | | nsresult |
2167 | | nsImageFrame::HandleEvent(nsPresContext* aPresContext, |
2168 | | WidgetGUIEvent* aEvent, |
2169 | | nsEventStatus* aEventStatus) |
2170 | 0 | { |
2171 | 0 | NS_ENSURE_ARG_POINTER(aEventStatus); |
2172 | 0 |
|
2173 | 0 | if ((aEvent->mMessage == eMouseClick && |
2174 | 0 | aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) || |
2175 | 0 | aEvent->mMessage == eMouseMove) { |
2176 | 0 | nsImageMap* map = GetImageMap(); |
2177 | 0 | bool isServerMap = IsServerImageMap(); |
2178 | 0 | if (map || isServerMap) { |
2179 | 0 | nsIntPoint p; |
2180 | 0 | TranslateEventCoords( |
2181 | 0 | nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p); |
2182 | 0 | bool inside = false; |
2183 | 0 | // Even though client-side image map triggering happens |
2184 | 0 | // through content, we need to make sure we're not inside |
2185 | 0 | // (in case we deal with a case of both client-side and |
2186 | 0 | // sever-side on the same image - it happens!) |
2187 | 0 | if (nullptr != map) { |
2188 | 0 | inside = !!map->GetArea(p.x, p.y); |
2189 | 0 | } |
2190 | 0 |
|
2191 | 0 | if (!inside && isServerMap) { |
2192 | 0 |
|
2193 | 0 | // Server side image maps use the href in a containing anchor |
2194 | 0 | // element to provide the basis for the destination url. |
2195 | 0 | nsCOMPtr<nsIURI> uri; |
2196 | 0 | nsAutoString target; |
2197 | 0 | nsCOMPtr<nsIContent> anchorNode; |
2198 | 0 | if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target, |
2199 | 0 | getter_AddRefs(anchorNode))) { |
2200 | 0 | // XXX if the mouse is over/clicked in the border/padding area |
2201 | 0 | // we should probably just pretend nothing happened. Nav4 |
2202 | 0 | // keeps the x,y coordinates positive as we do; IE doesn't |
2203 | 0 | // bother. Both of them send the click through even when the |
2204 | 0 | // mouse is over the border. |
2205 | 0 | if (p.x < 0) p.x = 0; |
2206 | 0 | if (p.y < 0) p.y = 0; |
2207 | 0 |
|
2208 | 0 | nsAutoCString spec; |
2209 | 0 | nsresult rv = uri->GetSpec(spec); |
2210 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2211 | 0 |
|
2212 | 0 | spec += nsPrintfCString("?%d,%d", p.x, p.y); |
2213 | 0 | rv = NS_MutateURI(uri) |
2214 | 0 | .SetSpec(spec) |
2215 | 0 | .Finalize(uri); |
2216 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2217 | 0 |
|
2218 | 0 | bool clicked = false; |
2219 | 0 | if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) { |
2220 | 0 | *aEventStatus = nsEventStatus_eConsumeDoDefault; |
2221 | 0 | clicked = true; |
2222 | 0 | } |
2223 | 0 | nsContentUtils::TriggerLink(anchorNode, aPresContext, uri, target, |
2224 | 0 | clicked, /* isTrusted */ true); |
2225 | 0 | } |
2226 | 0 | } |
2227 | 0 | } |
2228 | 0 | } |
2229 | 0 |
|
2230 | 0 | return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus); |
2231 | 0 | } |
2232 | | |
2233 | | nsresult |
2234 | | nsImageFrame::GetCursor(const nsPoint& aPoint, |
2235 | | nsIFrame::Cursor& aCursor) |
2236 | 0 | { |
2237 | 0 | if (nsImageMap* map = GetImageMap()) { |
2238 | 0 | nsIntPoint p; |
2239 | 0 | TranslateEventCoords(aPoint, p); |
2240 | 0 | nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y); |
2241 | 0 | if (area) { |
2242 | 0 | // Use the cursor from the style of the *area* element. |
2243 | 0 | // XXX Using the image as the parent ComputedStyle isn't |
2244 | 0 | // technically correct, but it's probably the right thing to do |
2245 | 0 | // here, since it means that areas on which the cursor isn't |
2246 | 0 | // specified will inherit the style from the image. |
2247 | 0 | RefPtr<ComputedStyle> areaStyle = |
2248 | 0 | PresShell()->StyleSet()-> |
2249 | 0 | ResolveStyleFor(area->AsElement(), |
2250 | 0 | LazyComputeBehavior::Allow); |
2251 | 0 | FillCursorInformationFromStyle(areaStyle->StyleUI(), aCursor); |
2252 | 0 | if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { |
2253 | 0 | aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; |
2254 | 0 | } |
2255 | 0 | return NS_OK; |
2256 | 0 | } |
2257 | 0 | } |
2258 | 0 | return nsFrame::GetCursor(aPoint, aCursor); |
2259 | 0 | } |
2260 | | |
2261 | | nsresult |
2262 | | nsImageFrame::AttributeChanged(int32_t aNameSpaceID, |
2263 | | nsAtom* aAttribute, |
2264 | | int32_t aModType) |
2265 | 0 | { |
2266 | 0 | nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID, |
2267 | 0 | aAttribute, aModType); |
2268 | 0 | if (NS_FAILED(rv)) { |
2269 | 0 | return rv; |
2270 | 0 | } |
2271 | 0 | if (nsGkAtoms::alt == aAttribute) |
2272 | 0 | { |
2273 | 0 | PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange, |
2274 | 0 | NS_FRAME_IS_DIRTY); |
2275 | 0 | } |
2276 | 0 |
|
2277 | 0 | return NS_OK; |
2278 | 0 | } |
2279 | | |
2280 | | void |
2281 | | nsImageFrame::OnVisibilityChange(Visibility aNewVisibility, |
2282 | | const Maybe<OnNonvisible>& aNonvisibleAction) |
2283 | 0 | { |
2284 | 0 | if (mKind == Kind::ImageElement) { |
2285 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); |
2286 | 0 | imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); |
2287 | 0 | } |
2288 | 0 |
|
2289 | 0 | if (aNewVisibility == Visibility::APPROXIMATELY_VISIBLE) { |
2290 | 0 | MaybeDecodeForPredictedSize(); |
2291 | 0 | } |
2292 | 0 |
|
2293 | 0 | nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); |
2294 | 0 | } |
2295 | | |
2296 | | #ifdef DEBUG_FRAME_DUMP |
2297 | | nsresult |
2298 | | nsImageFrame::GetFrameName(nsAString& aResult) const |
2299 | | { |
2300 | | return MakeFrameName(NS_LITERAL_STRING("ImageFrame"), aResult); |
2301 | | } |
2302 | | |
2303 | | void |
2304 | | nsImageFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const |
2305 | | { |
2306 | | nsCString str; |
2307 | | ListGeneric(str, aPrefix, aFlags); |
2308 | | |
2309 | | // output the img src url |
2310 | | if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { |
2311 | | nsCOMPtr<nsIURI> uri; |
2312 | | currentRequest->GetURI(getter_AddRefs(uri)); |
2313 | | nsAutoCString uristr; |
2314 | | uri->GetAsciiSpec(uristr); |
2315 | | str += nsPrintfCString(" [src=%s]", uristr.get()); |
2316 | | } |
2317 | | fprintf_stderr(out, "%s\n", str.get()); |
2318 | | } |
2319 | | #endif |
2320 | | |
2321 | | nsIFrame::LogicalSides |
2322 | | nsImageFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const |
2323 | 0 | { |
2324 | 0 | if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == |
2325 | 0 | StyleBoxDecorationBreak::Clone)) { |
2326 | 0 | return LogicalSides(); |
2327 | 0 | } |
2328 | 0 | LogicalSides skip; |
2329 | 0 | if (nullptr != GetPrevInFlow()) { |
2330 | 0 | skip |= eLogicalSideBitsBStart; |
2331 | 0 | } |
2332 | 0 | if (nullptr != GetNextInFlow()) { |
2333 | 0 | skip |= eLogicalSideBitsBEnd; |
2334 | 0 | } |
2335 | 0 | return skip; |
2336 | 0 | } |
2337 | | |
2338 | | nsresult |
2339 | | nsImageFrame::GetIntrinsicImageSize(nsSize& aSize) |
2340 | 0 | { |
2341 | 0 | if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && |
2342 | 0 | mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) { |
2343 | 0 | aSize.SizeTo(mIntrinsicSize.width.GetCoordValue(), |
2344 | 0 | mIntrinsicSize.height.GetCoordValue()); |
2345 | 0 | return NS_OK; |
2346 | 0 | } |
2347 | 0 | |
2348 | 0 | return NS_ERROR_FAILURE; |
2349 | 0 | } |
2350 | | |
2351 | | nsresult |
2352 | | nsImageFrame::LoadIcon(const nsAString& aSpec, |
2353 | | nsPresContext *aPresContext, |
2354 | | imgRequestProxy** aRequest) |
2355 | 0 | { |
2356 | 0 | nsresult rv = NS_OK; |
2357 | 0 | MOZ_ASSERT(!aSpec.IsEmpty(), "What happened??"); |
2358 | 0 |
|
2359 | 0 | if (!sIOService) { |
2360 | 0 | rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); |
2361 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2362 | 0 | } |
2363 | 0 |
|
2364 | 0 | nsCOMPtr<nsIURI> realURI; |
2365 | 0 | SpecToURI(aSpec, sIOService, getter_AddRefs(realURI)); |
2366 | 0 |
|
2367 | 0 | RefPtr<imgLoader> il = |
2368 | 0 | nsContentUtils::GetImgLoaderForDocument(aPresContext->Document()); |
2369 | 0 |
|
2370 | 0 | nsCOMPtr<nsILoadGroup> loadGroup; |
2371 | 0 | GetLoadGroup(aPresContext, getter_AddRefs(loadGroup)); |
2372 | 0 |
|
2373 | 0 | // For icon loads, we don't need to merge with the loadgroup flags |
2374 | 0 | nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; |
2375 | 0 | nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE; |
2376 | 0 |
|
2377 | 0 | return il->LoadImage(realURI, /* icon URI */ |
2378 | 0 | nullptr, /* initial document URI; this is only |
2379 | 0 | relevant for cookies, so does not |
2380 | 0 | apply to icons. */ |
2381 | 0 | nullptr, /* referrer (not relevant for icons) */ |
2382 | 0 | mozilla::net::RP_Unset, |
2383 | 0 | nullptr, /* principal (not relevant for icons) */ |
2384 | 0 | 0, |
2385 | 0 | loadGroup, |
2386 | 0 | gIconLoad, |
2387 | 0 | nullptr, /* No context */ |
2388 | 0 | nullptr, /* Not associated with any particular document */ |
2389 | 0 | loadFlags, |
2390 | 0 | nullptr, |
2391 | 0 | contentPolicyType, |
2392 | 0 | EmptyString(), |
2393 | 0 | false, /* aUseUrgentStartForChannel */ |
2394 | 0 | aRequest); |
2395 | 0 | } |
2396 | | |
2397 | | void |
2398 | | nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const |
2399 | 0 | { |
2400 | 0 | if (mContent) { |
2401 | 0 | NS_ASSERTION(mContent->GetComposedDoc(), |
2402 | 0 | "Frame still alive after content removed from document!"); |
2403 | 0 | mContent->GetComposedDoc()->GetDocumentCharacterSet()->Name(aCharset); |
2404 | 0 | } |
2405 | 0 | } |
2406 | | |
2407 | | void |
2408 | | nsImageFrame::SpecToURI(const nsAString& aSpec, nsIIOService *aIOService, |
2409 | | nsIURI **aURI) |
2410 | 0 | { |
2411 | 0 | nsCOMPtr<nsIURI> baseURI; |
2412 | 0 | if (mContent) { |
2413 | 0 | baseURI = mContent->GetBaseURI(); |
2414 | 0 | } |
2415 | 0 | nsAutoCString charset; |
2416 | 0 | GetDocumentCharacterSet(charset); |
2417 | 0 | NS_NewURI(aURI, aSpec, |
2418 | 0 | charset.IsEmpty() ? nullptr : charset.get(), |
2419 | 0 | baseURI, aIOService); |
2420 | 0 | } |
2421 | | |
2422 | | void |
2423 | | nsImageFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup) |
2424 | 0 | { |
2425 | 0 | if (!aPresContext) |
2426 | 0 | return; |
2427 | 0 | |
2428 | 0 | MOZ_ASSERT(nullptr != aLoadGroup, "null OUT parameter pointer"); |
2429 | 0 |
|
2430 | 0 | nsIPresShell *shell = aPresContext->GetPresShell(); |
2431 | 0 |
|
2432 | 0 | if (!shell) |
2433 | 0 | return; |
2434 | 0 | |
2435 | 0 | nsIDocument *doc = shell->GetDocument(); |
2436 | 0 | if (!doc) |
2437 | 0 | return; |
2438 | 0 | |
2439 | 0 | *aLoadGroup = doc->GetDocumentLoadGroup().take(); |
2440 | 0 | } |
2441 | | |
2442 | | nsresult nsImageFrame::LoadIcons(nsPresContext *aPresContext) |
2443 | 0 | { |
2444 | 0 | NS_ASSERTION(!gIconLoad, "called LoadIcons twice"); |
2445 | 0 |
|
2446 | 0 | NS_NAMED_LITERAL_STRING(loadingSrc,"resource://gre-resources/loading-image.png"); |
2447 | 0 | NS_NAMED_LITERAL_STRING(brokenSrc,"resource://gre-resources/broken-image.png"); |
2448 | 0 |
|
2449 | 0 | gIconLoad = new IconLoad(); |
2450 | 0 |
|
2451 | 0 | nsresult rv; |
2452 | 0 | // create a loader and load the images |
2453 | 0 | rv = LoadIcon(loadingSrc, |
2454 | 0 | aPresContext, |
2455 | 0 | getter_AddRefs(gIconLoad->mLoadingImage)); |
2456 | 0 | if (NS_FAILED(rv)) { |
2457 | 0 | return rv; |
2458 | 0 | } |
2459 | 0 | |
2460 | 0 | rv = LoadIcon(brokenSrc, |
2461 | 0 | aPresContext, |
2462 | 0 | getter_AddRefs(gIconLoad->mBrokenImage)); |
2463 | 0 | if (NS_FAILED(rv)) { |
2464 | 0 | return rv; |
2465 | 0 | } |
2466 | 0 | |
2467 | 0 | return rv; |
2468 | 0 | } |
2469 | | |
2470 | | NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver, |
2471 | | imgINotificationObserver) |
2472 | | |
2473 | | static const char* kIconLoadPrefs[] = { |
2474 | | "browser.display.force_inline_alttext", |
2475 | | "browser.display.show_image_placeholders", |
2476 | | "browser.display.show_loading_image_placeholder", |
2477 | | nullptr |
2478 | | }; |
2479 | | |
2480 | | nsImageFrame::IconLoad::IconLoad() |
2481 | 0 | { |
2482 | 0 | // register observers |
2483 | 0 | Preferences::AddStrongObservers(this, kIconLoadPrefs); |
2484 | 0 | GetPrefs(); |
2485 | 0 | } |
2486 | | |
2487 | | void |
2488 | | nsImageFrame::IconLoad::Shutdown() |
2489 | 0 | { |
2490 | 0 | Preferences::RemoveObservers(this, kIconLoadPrefs); |
2491 | 0 | // in case the pref service releases us later |
2492 | 0 | if (mLoadingImage) { |
2493 | 0 | mLoadingImage->CancelAndForgetObserver(NS_ERROR_FAILURE); |
2494 | 0 | mLoadingImage = nullptr; |
2495 | 0 | } |
2496 | 0 | if (mBrokenImage) { |
2497 | 0 | mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE); |
2498 | 0 | mBrokenImage = nullptr; |
2499 | 0 | } |
2500 | 0 | } |
2501 | | |
2502 | | NS_IMETHODIMP |
2503 | | nsImageFrame::IconLoad::Observe(nsISupports *aSubject, const char* aTopic, |
2504 | | const char16_t* aData) |
2505 | 0 | { |
2506 | 0 | NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), |
2507 | 0 | "wrong topic"); |
2508 | | #ifdef DEBUG |
2509 | | // assert |aData| is one of our prefs. |
2510 | | uint32_t i = 0; |
2511 | | for (; i < ArrayLength(kIconLoadPrefs); ++i) { |
2512 | | if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData)) |
2513 | | break; |
2514 | | } |
2515 | | MOZ_ASSERT(i < ArrayLength(kIconLoadPrefs)); |
2516 | | #endif |
2517 | |
|
2518 | 0 | GetPrefs(); |
2519 | 0 | return NS_OK; |
2520 | 0 | } |
2521 | | |
2522 | | void nsImageFrame::IconLoad::GetPrefs() |
2523 | 0 | { |
2524 | 0 | mPrefForceInlineAltText = |
2525 | 0 | Preferences::GetBool("browser.display.force_inline_alttext"); |
2526 | 0 |
|
2527 | 0 | mPrefShowPlaceholders = |
2528 | 0 | Preferences::GetBool("browser.display.show_image_placeholders", true); |
2529 | 0 |
|
2530 | 0 | mPrefShowLoadingPlaceholder = |
2531 | 0 | Preferences::GetBool("browser.display.show_loading_image_placeholder", true); |
2532 | 0 | } |
2533 | | |
2534 | | NS_IMETHODIMP |
2535 | | nsImageFrame::IconLoad::Notify(imgIRequest* aRequest, |
2536 | | int32_t aType, |
2537 | | const nsIntRect* aData) |
2538 | 0 | { |
2539 | 0 | MOZ_ASSERT(aRequest); |
2540 | 0 |
|
2541 | 0 | if (aType != imgINotificationObserver::LOAD_COMPLETE && |
2542 | 0 | aType != imgINotificationObserver::FRAME_UPDATE) { |
2543 | 0 | return NS_OK; |
2544 | 0 | } |
2545 | 0 | |
2546 | 0 | if (aType == imgINotificationObserver::LOAD_COMPLETE) { |
2547 | 0 | nsCOMPtr<imgIContainer> image; |
2548 | 0 | aRequest->GetImage(getter_AddRefs(image)); |
2549 | 0 | if (!image) { |
2550 | 0 | return NS_ERROR_FAILURE; |
2551 | 0 | } |
2552 | 0 | |
2553 | 0 | // Retrieve the image's intrinsic size. |
2554 | 0 | int32_t width = 0; |
2555 | 0 | int32_t height = 0; |
2556 | 0 | image->GetWidth(&width); |
2557 | 0 | image->GetHeight(&height); |
2558 | 0 |
|
2559 | 0 | // Request a decode at that size. |
2560 | 0 | image->RequestDecodeForSize(IntSize(width, height), |
2561 | 0 | imgIContainer::DECODE_FLAGS_DEFAULT); |
2562 | 0 | } |
2563 | 0 |
|
2564 | 0 | nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers); |
2565 | 0 | nsImageFrame *frame; |
2566 | 0 | while (iter.HasMore()) { |
2567 | 0 | frame = iter.GetNext(); |
2568 | 0 | frame->InvalidateFrame(); |
2569 | 0 | } |
2570 | 0 |
|
2571 | 0 | return NS_OK; |
2572 | 0 | } |
2573 | | |
2574 | | NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver) |
2575 | | |
2576 | | nsImageListener::nsImageListener(nsImageFrame* aFrame) |
2577 | | : mFrame(aFrame) |
2578 | 0 | { |
2579 | 0 | } |
2580 | | |
2581 | 0 | nsImageListener::~nsImageListener() = default; |
2582 | | |
2583 | | NS_IMETHODIMP |
2584 | | nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) |
2585 | 0 | { |
2586 | 0 | if (!mFrame) |
2587 | 0 | return NS_ERROR_FAILURE; |
2588 | 0 | |
2589 | 0 | return mFrame->Notify(aRequest, aType, aData); |
2590 | 0 | } |
2591 | | |
2592 | | static bool |
2593 | | IsInAutoWidthTableCellForQuirk(nsIFrame *aFrame) |
2594 | 0 | { |
2595 | 0 | if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode()) |
2596 | 0 | return false; |
2597 | 0 | // Check if the parent of the closest nsBlockFrame has auto width. |
2598 | 0 | nsBlockFrame *ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame); |
2599 | 0 | if (ancestor->Style()->GetPseudo() == nsCSSAnonBoxes::cellContent()) { |
2600 | 0 | // Assume direct parent is a table cell frame. |
2601 | 0 | nsFrame *grandAncestor = static_cast<nsFrame*>(ancestor->GetParent()); |
2602 | 0 | return grandAncestor && |
2603 | 0 | grandAncestor->StylePosition()->mWidth.GetUnit() == eStyleUnit_Auto; |
2604 | 0 | } |
2605 | 0 | return false; |
2606 | 0 | } |
2607 | | |
2608 | | /* virtual */ void |
2609 | | nsImageFrame::AddInlineMinISize(gfxContext* aRenderingContext, |
2610 | | nsIFrame::InlineMinISizeData* aData) |
2611 | 0 | { |
2612 | 0 | nscoord isize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, |
2613 | 0 | this, nsLayoutUtils::MIN_ISIZE); |
2614 | 0 | bool canBreak = !IsInAutoWidthTableCellForQuirk(this); |
2615 | 0 | aData->DefaultAddInlineMinISize(this, isize, canBreak); |
2616 | 0 | } |