Coverage Report

Created: 2018-09-25 14:53

/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
}