Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/html/ImageDocument.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "ImageDocument.h"
8
#include "mozilla/ComputedStyle.h"
9
#include "mozilla/dom/Element.h"
10
#include "mozilla/dom/Event.h"
11
#include "mozilla/dom/ImageDocumentBinding.h"
12
#include "mozilla/dom/HTMLImageElement.h"
13
#include "mozilla/dom/MouseEvent.h"
14
#include "mozilla/StaticPrefs.h"
15
#include "nsRect.h"
16
#include "nsIImageLoadingContent.h"
17
#include "nsGenericHTMLElement.h"
18
#include "nsDocShell.h"
19
#include "nsIDocumentInlines.h"
20
#include "nsDOMTokenList.h"
21
#include "nsIDOMEventListener.h"
22
#include "nsIFrame.h"
23
#include "nsGkAtoms.h"
24
#include "imgIRequest.h"
25
#include "imgILoader.h"
26
#include "imgIContainer.h"
27
#include "imgINotificationObserver.h"
28
#include "nsIPresShell.h"
29
#include "nsPresContext.h"
30
#include "nsIChannel.h"
31
#include "nsIContentPolicy.h"
32
#include "nsContentPolicyUtils.h"
33
#include "nsPIDOMWindow.h"
34
#include "nsError.h"
35
#include "nsURILoader.h"
36
#include "nsIDocShell.h"
37
#include "nsIContentViewer.h"
38
#include "nsThreadUtils.h"
39
#include "nsIScrollableFrame.h"
40
#include "nsContentUtils.h"
41
#include "mozilla/Preferences.h"
42
#include <algorithm>
43
44
0
#define AUTOMATIC_IMAGE_RESIZING_PREF "browser.enable_automatic_image_resizing"
45
0
#define CLICK_IMAGE_RESIZING_PREF "browser.enable_click_image_resizing"
46
47
//XXX A hack needed for Firefox's site specific zoom.
48
static bool IsSiteSpecific()
49
0
{
50
0
  return !mozilla::StaticPrefs::privacy_resistFingerprinting() &&
51
0
         mozilla::Preferences::GetBool("browser.zoom.siteSpecific", false);
52
0
}
53
54
namespace mozilla {
55
namespace dom {
56
57
class ImageListener : public MediaDocumentStreamListener
58
{
59
public:
60
  NS_DECL_NSIREQUESTOBSERVER
61
62
  explicit ImageListener(ImageDocument* aDocument);
63
  virtual ~ImageListener();
64
};
65
66
ImageListener::ImageListener(ImageDocument* aDocument)
67
  : MediaDocumentStreamListener(aDocument)
68
0
{
69
0
}
70
71
ImageListener::~ImageListener()
72
0
{
73
0
}
74
75
NS_IMETHODIMP
76
ImageListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
77
0
{
78
0
  NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
79
0
80
0
  ImageDocument *imgDoc = static_cast<ImageDocument*>(mDocument.get());
81
0
  nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
82
0
  if (!channel) {
83
0
    return NS_ERROR_FAILURE;
84
0
  }
85
0
86
0
  nsCOMPtr<nsPIDOMWindowOuter> domWindow = imgDoc->GetWindow();
87
0
  NS_ENSURE_TRUE(domWindow, NS_ERROR_UNEXPECTED);
88
0
89
0
  // Do a ShouldProcess check to see whether to keep loading the image.
90
0
  nsCOMPtr<nsIURI> channelURI;
91
0
  channel->GetURI(getter_AddRefs(channelURI));
92
0
93
0
  nsAutoCString mimeType;
94
0
  channel->GetContentType(mimeType);
95
0
96
0
  nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
97
0
  // query the corresponding arguments for the channel loadinfo and pass
98
0
  // it on to the temporary loadinfo used for content policy checks.
99
0
  nsCOMPtr<nsINode> requestingNode = domWindow->GetFrameElementInternal();
100
0
  nsCOMPtr<nsIPrincipal> loadingPrincipal;
101
0
  if (requestingNode) {
102
0
    loadingPrincipal = requestingNode->NodePrincipal();
103
0
  }
104
0
  else {
105
0
    nsContentUtils::GetSecurityManager()->
106
0
      GetChannelResultPrincipal(channel, getter_AddRefs(loadingPrincipal));
107
0
  }
108
0
109
0
  nsCOMPtr<nsILoadInfo> secCheckLoadInfo =
110
0
    new net::LoadInfo(loadingPrincipal,
111
0
                      loadInfo ? loadInfo->TriggeringPrincipal() : nullptr,
112
0
                      requestingNode,
113
0
                      nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
114
0
                      nsIContentPolicy::TYPE_INTERNAL_IMAGE);
115
0
116
0
  int16_t decision = nsIContentPolicy::ACCEPT;
117
0
  nsresult rv = NS_CheckContentProcessPolicy(channelURI,
118
0
                                             secCheckLoadInfo,
119
0
                                             mimeType,
120
0
                                             &decision,
121
0
                                             nsContentUtils::GetContentPolicy());
122
0
123
0
  if (NS_FAILED(rv) || NS_CP_REJECTED(decision)) {
124
0
    request->Cancel(NS_ERROR_CONTENT_BLOCKED);
125
0
    return NS_OK;
126
0
  }
127
0
128
0
  if (!imgDoc->mObservingImageLoader) {
129
0
    nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(imgDoc->mImageContent);
130
0
    NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
131
0
132
0
    imageLoader->AddNativeObserver(imgDoc);
133
0
    imgDoc->mObservingImageLoader = true;
134
0
    imageLoader->LoadImageWithChannel(channel, getter_AddRefs(mNextStream));
135
0
  }
136
0
137
0
  return MediaDocumentStreamListener::OnStartRequest(request, ctxt);
138
0
}
139
140
NS_IMETHODIMP
141
ImageListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt, nsresult aStatus)
142
0
{
143
0
  ImageDocument* imgDoc = static_cast<ImageDocument*>(mDocument.get());
144
0
  nsContentUtils::DispatchChromeEvent(imgDoc, static_cast<nsIDocument*>(imgDoc),
145
0
                                      NS_LITERAL_STRING("ImageContentLoaded"),
146
0
                                      CanBubble::eYes, Cancelable::eYes);
147
0
  return MediaDocumentStreamListener::OnStopRequest(aRequest, aCtxt, aStatus);
148
0
}
149
150
ImageDocument::ImageDocument()
151
  : MediaDocument()
152
  , mVisibleWidth(0.0)
153
  , mVisibleHeight(0.0)
154
  , mImageWidth(0)
155
  , mImageHeight(0)
156
  , mResizeImageByDefault(false)
157
  , mClickResizingEnabled(false)
158
  , mImageIsOverflowingHorizontally(false)
159
  , mImageIsOverflowingVertically(false)
160
  , mImageIsResized(false)
161
  , mShouldResize(false)
162
  , mFirstResize(false)
163
  , mObservingImageLoader(false)
164
  , mOriginalZoomLevel(1.0)
165
#if defined(MOZ_WIDGET_ANDROID)
166
  , mOriginalResolution(1.0)
167
#endif
168
0
{
169
0
}
170
171
ImageDocument::~ImageDocument()
172
0
{
173
0
}
174
175
176
NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageDocument, MediaDocument,
177
                                   mImageContent)
178
179
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(ImageDocument,
180
                                             MediaDocument,
181
                                             nsIImageDocument,
182
                                             imgINotificationObserver,
183
                                             nsIDOMEventListener)
184
185
186
nsresult
187
ImageDocument::Init()
188
0
{
189
0
  nsresult rv = MediaDocument::Init();
190
0
  NS_ENSURE_SUCCESS(rv, rv);
191
0
192
0
  mResizeImageByDefault = Preferences::GetBool(AUTOMATIC_IMAGE_RESIZING_PREF);
193
0
  mClickResizingEnabled = Preferences::GetBool(CLICK_IMAGE_RESIZING_PREF);
194
0
  mShouldResize = mResizeImageByDefault;
195
0
  mFirstResize = true;
196
0
197
0
  return NS_OK;
198
0
}
199
200
JSObject*
201
ImageDocument::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
202
0
{
203
0
  return ImageDocument_Binding::Wrap(aCx, this, aGivenProto);
204
0
}
205
206
nsresult
207
ImageDocument::StartDocumentLoad(const char*         aCommand,
208
                                 nsIChannel*         aChannel,
209
                                 nsILoadGroup*       aLoadGroup,
210
                                 nsISupports*        aContainer,
211
                                 nsIStreamListener** aDocListener,
212
                                 bool                aReset,
213
                                 nsIContentSink*     aSink)
214
0
{
215
0
  nsresult rv =
216
0
    MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer,
217
0
                                     aDocListener, aReset, aSink);
218
0
  if (NS_FAILED(rv)) {
219
0
    return rv;
220
0
  }
221
0
222
0
  mOriginalZoomLevel = IsSiteSpecific() ? 1.0 : GetZoomLevel();
223
#if defined(MOZ_WIDGET_ANDROID)
224
  mOriginalResolution = GetResolution();
225
#endif
226
227
0
  NS_ASSERTION(aDocListener, "null aDocListener");
228
0
  *aDocListener = new ImageListener(this);
229
0
  NS_ADDREF(*aDocListener);
230
0
231
0
  return NS_OK;
232
0
}
233
234
void
235
ImageDocument::Destroy()
236
0
{
237
0
  if (mImageContent) {
238
0
    // Remove our event listener from the image content.
239
0
    nsCOMPtr<EventTarget> target = do_QueryInterface(mImageContent);
240
0
    target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
241
0
    target->RemoveEventListener(NS_LITERAL_STRING("click"), this, false);
242
0
243
0
    // Break reference cycle with mImageContent, if we have one
244
0
    if (mObservingImageLoader) {
245
0
      nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
246
0
      if (imageLoader) {
247
0
        imageLoader->RemoveNativeObserver(this);
248
0
      }
249
0
    }
250
0
251
0
    mImageContent = nullptr;
252
0
  }
253
0
254
0
  MediaDocument::Destroy();
255
0
}
256
257
void
258
ImageDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject)
259
0
{
260
0
  // If the script global object is changing, we need to unhook our event
261
0
  // listeners on the window.
262
0
  nsCOMPtr<EventTarget> target;
263
0
  if (mScriptGlobalObject &&
264
0
      aScriptGlobalObject != mScriptGlobalObject) {
265
0
    target = do_QueryInterface(mScriptGlobalObject);
266
0
    target->RemoveEventListener(NS_LITERAL_STRING("resize"), this, false);
267
0
    target->RemoveEventListener(NS_LITERAL_STRING("keypress"), this,
268
0
                                false);
269
0
  }
270
0
271
0
  // Set the script global object on the superclass before doing
272
0
  // anything that might require it....
273
0
  MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
274
0
275
0
  if (aScriptGlobalObject) {
276
0
    if (!InitialSetupHasBeenDone()) {
277
0
      MOZ_ASSERT(!GetRootElement(), "Where did the root element come from?");
278
0
      // Create synthetic document
279
#ifdef DEBUG
280
      nsresult rv =
281
#endif
282
        CreateSyntheticDocument();
283
0
      NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document");
284
0
285
0
      target = do_QueryInterface(mImageContent);
286
0
      target->AddEventListener(NS_LITERAL_STRING("load"), this, false);
287
0
      target->AddEventListener(NS_LITERAL_STRING("click"), this, false);
288
0
    }
289
0
290
0
    target = do_QueryInterface(aScriptGlobalObject);
291
0
    target->AddEventListener(NS_LITERAL_STRING("resize"), this, false);
292
0
    target->AddEventListener(NS_LITERAL_STRING("keypress"), this, false);
293
0
294
0
    if (!InitialSetupHasBeenDone()) {
295
0
      LinkStylesheet(NS_LITERAL_STRING("resource://content-accessible/ImageDocument.css"));
296
0
      if (!nsContentUtils::IsChildOfSameType(this)) {
297
0
        LinkStylesheet(NS_LITERAL_STRING("resource://content-accessible/TopLevelImageDocument.css"));
298
0
        LinkStylesheet(NS_LITERAL_STRING("chrome://global/skin/media/TopLevelImageDocument.css"));
299
0
      }
300
0
      InitialSetupDone();
301
0
    }
302
0
  }
303
0
}
304
305
void
306
ImageDocument::OnPageShow(bool aPersisted,
307
                          EventTarget* aDispatchStartTarget,
308
                          bool aOnlySystemGroup)
309
0
{
310
0
  if (aPersisted) {
311
0
    mOriginalZoomLevel = IsSiteSpecific() ? 1.0 : GetZoomLevel();
312
#if defined(MOZ_WIDGET_ANDROID)
313
    mOriginalResolution = GetResolution();
314
#endif
315
  }
316
0
  RefPtr<ImageDocument> kungFuDeathGrip(this);
317
0
  UpdateSizeFromLayout();
318
0
319
0
  MediaDocument::OnPageShow(aPersisted, aDispatchStartTarget,
320
0
                            aOnlySystemGroup);
321
0
}
322
323
NS_IMETHODIMP
324
ImageDocument::GetImageIsOverflowing(bool* aImageIsOverflowing)
325
0
{
326
0
  *aImageIsOverflowing = ImageIsOverflowing();
327
0
  return NS_OK;
328
0
}
329
330
NS_IMETHODIMP
331
ImageDocument::GetImageIsResized(bool* aImageIsResized)
332
0
{
333
0
  *aImageIsResized = ImageIsResized();
334
0
  return NS_OK;
335
0
}
336
337
already_AddRefed<imgIRequest>
338
ImageDocument::GetImageRequest(ErrorResult& aRv)
339
0
{
340
0
  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
341
0
  nsCOMPtr<imgIRequest> imageRequest;
342
0
  if (imageLoader) {
343
0
    aRv = imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
344
0
                                  getter_AddRefs(imageRequest));
345
0
  }
346
0
  return imageRequest.forget();
347
0
}
348
349
NS_IMETHODIMP
350
ImageDocument::GetImageRequest(imgIRequest** aImageRequest)
351
0
{
352
0
  ErrorResult rv;
353
0
  *aImageRequest = GetImageRequest(rv).take();
354
0
  return rv.StealNSResult();
355
0
}
356
357
void
358
ImageDocument::ShrinkToFit()
359
0
{
360
0
  if (!mImageContent) {
361
0
    return;
362
0
  }
363
0
  if (GetZoomLevel() != mOriginalZoomLevel && mImageIsResized &&
364
0
      !nsContentUtils::IsChildOfSameType(this)) {
365
0
    // If we're zoomed, so that we don't maintain the invariant that
366
0
    // mImageIsResized if and only if its displayed width/height fit in
367
0
    // mVisibleWidth/mVisibleHeight, then we may need to switch to/from the
368
0
    // overflowingVertical class here, because our viewport size may have
369
0
    // changed and we don't plan to adjust the image size to compensate.  Since
370
0
    // mImageIsResized it has a "height" attribute set, and we can just get the
371
0
    // displayed image height by getting .height on the HTMLImageElement.
372
0
    //
373
0
    // Hold strong ref, because Height() can run script.
374
0
    RefPtr<HTMLImageElement> img = HTMLImageElement::FromNode(mImageContent);
375
0
    uint32_t imageHeight = img->Height();
376
0
    nsDOMTokenList* classList = img->ClassList();
377
0
    ErrorResult ignored;
378
0
    if (imageHeight > mVisibleHeight) {
379
0
      classList->Add(NS_LITERAL_STRING("overflowingVertical"), ignored);
380
0
    } else {
381
0
      classList->Remove(NS_LITERAL_STRING("overflowingVertical"), ignored);
382
0
    }
383
0
    ignored.SuppressException();
384
0
    return;
385
0
  }
386
#if defined(MOZ_WIDGET_ANDROID)
387
  if (GetResolution() != mOriginalResolution && mImageIsResized) {
388
    // Don't resize if resolution has changed, e.g., through pinch-zooming on
389
    // Android.
390
    return;
391
  }
392
#endif
393
394
0
  // Keep image content alive while changing the attributes.
395
0
  RefPtr<HTMLImageElement> image = HTMLImageElement::FromNode(mImageContent);
396
0
397
0
  uint32_t newWidth = std::max(1, NSToCoordFloor(GetRatio() * mImageWidth));
398
0
  uint32_t newHeight = std::max(1, NSToCoordFloor(GetRatio() * mImageHeight));
399
0
  image->SetWidth(newWidth, IgnoreErrors());
400
0
  image->SetHeight(newHeight, IgnoreErrors());
401
0
402
0
  // The view might have been scrolled when zooming in, scroll back to the
403
0
  // origin now that we're showing a shrunk-to-window version.
404
0
  ScrollImageTo(0, 0, false);
405
0
406
0
  if (!mImageContent) {
407
0
    // ScrollImageTo flush destroyed our content.
408
0
    return;
409
0
  }
410
0
411
0
  SetModeClass(eShrinkToFit);
412
0
413
0
  mImageIsResized = true;
414
0
415
0
  UpdateTitleAndCharset();
416
0
}
417
418
NS_IMETHODIMP
419
ImageDocument::DOMShrinkToFit()
420
0
{
421
0
  ShrinkToFit();
422
0
  return NS_OK;
423
0
}
424
425
NS_IMETHODIMP
426
ImageDocument::DOMRestoreImageTo(int32_t aX, int32_t aY)
427
0
{
428
0
  RestoreImageTo(aX, aY);
429
0
  return NS_OK;
430
0
}
431
432
void
433
ImageDocument::ScrollImageTo(int32_t aX, int32_t aY, bool restoreImage)
434
0
{
435
0
  if (restoreImage) {
436
0
    RestoreImage();
437
0
    FlushPendingNotifications(FlushType::Layout);
438
0
  }
439
0
440
0
  nsCOMPtr<nsIPresShell> shell = GetShell();
441
0
  if (!shell) {
442
0
    return;
443
0
  }
444
0
445
0
  nsIScrollableFrame* sf = shell->GetRootScrollFrameAsScrollable();
446
0
  if (!sf) {
447
0
    return;
448
0
  }
449
0
450
0
  float ratio = GetRatio();
451
0
  // Don't try to scroll image if the document is not visible (mVisibleWidth or
452
0
  // mVisibleHeight is zero).
453
0
  if (ratio <= 0.0) {
454
0
    return;
455
0
  }
456
0
  nsRect portRect = sf->GetScrollPortRect();
457
0
  sf->ScrollTo(nsPoint(nsPresContext::CSSPixelsToAppUnits(aX/ratio) - portRect.width/2,
458
0
                       nsPresContext::CSSPixelsToAppUnits(aY/ratio) - portRect.height/2),
459
0
               nsIScrollableFrame::INSTANT);
460
0
}
461
462
void
463
ImageDocument::RestoreImage()
464
0
{
465
0
  if (!mImageContent) {
466
0
    return;
467
0
  }
468
0
  // Keep image content alive while changing the attributes.
469
0
  nsCOMPtr<Element> imageContent = mImageContent;
470
0
  imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::width, true);
471
0
  imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::height, true);
472
0
473
0
  if (ImageIsOverflowing()) {
474
0
    if (!mImageIsOverflowingVertically) {
475
0
      SetModeClass(eOverflowingHorizontalOnly);
476
0
    } else {
477
0
      SetModeClass(eOverflowingVertical);
478
0
    }
479
0
  }
480
0
  else {
481
0
    SetModeClass(eNone);
482
0
  }
483
0
484
0
  mImageIsResized = false;
485
0
486
0
  UpdateTitleAndCharset();
487
0
}
488
489
NS_IMETHODIMP
490
ImageDocument::DOMRestoreImage()
491
0
{
492
0
  RestoreImage();
493
0
  return NS_OK;
494
0
}
495
496
void
497
ImageDocument::ToggleImageSize()
498
0
{
499
0
  mShouldResize = true;
500
0
  if (mImageIsResized) {
501
0
    mShouldResize = false;
502
0
    ResetZoomLevel();
503
0
    RestoreImage();
504
0
  }
505
0
  else if (ImageIsOverflowing()) {
506
0
    ResetZoomLevel();
507
0
    ShrinkToFit();
508
0
  }
509
0
}
510
511
NS_IMETHODIMP
512
ImageDocument::DOMToggleImageSize()
513
0
{
514
0
  ToggleImageSize();
515
0
  return NS_OK;
516
0
}
517
518
NS_IMETHODIMP
519
ImageDocument::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
520
0
{
521
0
  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
522
0
    nsCOMPtr<imgIContainer> image;
523
0
    aRequest->GetImage(getter_AddRefs(image));
524
0
    return OnSizeAvailable(aRequest, image);
525
0
  }
526
0
527
0
  // Run this using a script runner because HAS_TRANSPARENCY notifications can
528
0
  // come during painting and this will trigger invalidation.
529
0
  if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
530
0
    nsCOMPtr<nsIRunnable> runnable =
531
0
      NewRunnableMethod("dom::ImageDocument::OnHasTransparency",
532
0
                        this,
533
0
                        &ImageDocument::OnHasTransparency);
534
0
    nsContentUtils::AddScriptRunner(runnable);
535
0
  }
536
0
537
0
  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
538
0
    uint32_t reqStatus;
539
0
    aRequest->GetImageStatus(&reqStatus);
540
0
    nsresult status =
541
0
        reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
542
0
    return OnLoadComplete(aRequest, status);
543
0
  }
544
0
545
0
  return NS_OK;
546
0
}
547
548
void
549
ImageDocument::OnHasTransparency()
550
0
{
551
0
  if (!mImageContent || nsContentUtils::IsChildOfSameType(this)) {
552
0
    return;
553
0
  }
554
0
555
0
  nsDOMTokenList* classList = mImageContent->ClassList();
556
0
  mozilla::ErrorResult rv;
557
0
  classList->Add(NS_LITERAL_STRING("transparent"), rv);
558
0
}
559
560
void
561
ImageDocument::SetModeClass(eModeClasses mode)
562
0
{
563
0
  nsDOMTokenList* classList = mImageContent->ClassList();
564
0
  ErrorResult rv;
565
0
566
0
  if (mode == eShrinkToFit) {
567
0
    classList->Add(NS_LITERAL_STRING("shrinkToFit"), rv);
568
0
  } else {
569
0
    classList->Remove(NS_LITERAL_STRING("shrinkToFit"), rv);
570
0
  }
571
0
572
0
  if (mode == eOverflowingVertical) {
573
0
    classList->Add(NS_LITERAL_STRING("overflowingVertical"), rv);
574
0
  } else {
575
0
    classList->Remove(NS_LITERAL_STRING("overflowingVertical"), rv);
576
0
  }
577
0
578
0
  if (mode == eOverflowingHorizontalOnly) {
579
0
    classList->Add(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
580
0
  } else {
581
0
    classList->Remove(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
582
0
  }
583
0
584
0
  rv.SuppressException();
585
0
}
586
587
nsresult
588
ImageDocument::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
589
0
{
590
0
  int32_t oldWidth = mImageWidth;
591
0
  int32_t oldHeight = mImageHeight;
592
0
593
0
  // Styles have not yet been applied, so we don't know the final size. For now,
594
0
  // default to the image's intrinsic size.
595
0
  aImage->GetWidth(&mImageWidth);
596
0
  aImage->GetHeight(&mImageHeight);
597
0
598
0
  // Multipart images send size available for each part; ignore them if it
599
0
  // doesn't change our size. (We may not even support changing size in
600
0
  // multipart images in the future.)
601
0
  if (oldWidth == mImageWidth && oldHeight == mImageHeight) {
602
0
    return NS_OK;
603
0
  }
604
0
605
0
  nsCOMPtr<nsIRunnable> runnable =
606
0
    NewRunnableMethod("dom::ImageDocument::DefaultCheckOverflowing",
607
0
                      this,
608
0
                      &ImageDocument::DefaultCheckOverflowing);
609
0
  nsContentUtils::AddScriptRunner(runnable);
610
0
  UpdateTitleAndCharset();
611
0
612
0
  return NS_OK;
613
0
}
614
615
nsresult
616
ImageDocument::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
617
0
{
618
0
  UpdateTitleAndCharset();
619
0
620
0
  // mImageContent can be null if the document is already destroyed
621
0
  if (NS_FAILED(aStatus) && mStringBundle && mImageContent) {
622
0
    nsAutoCString src;
623
0
    mDocumentURI->GetSpec(src);
624
0
    NS_ConvertUTF8toUTF16 srcString(src);
625
0
    const char16_t* formatString[] = { srcString.get() };
626
0
    nsAutoString errorMsg;
627
0
    mStringBundle->FormatStringFromName("InvalidImage", formatString, 1,
628
0
                                        errorMsg);
629
0
630
0
    mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false);
631
0
  }
632
0
633
0
  return NS_OK;
634
0
}
635
636
NS_IMETHODIMP
637
ImageDocument::HandleEvent(Event* aEvent)
638
0
{
639
0
  nsAutoString eventType;
640
0
  aEvent->GetType(eventType);
641
0
  if (eventType.EqualsLiteral("resize")) {
642
0
    CheckOverflowing(false);
643
0
  }
644
0
  else if (eventType.EqualsLiteral("click") && mClickResizingEnabled) {
645
0
    ResetZoomLevel();
646
0
    mShouldResize = true;
647
0
    if (mImageIsResized) {
648
0
      int32_t x = 0, y = 0;
649
0
      MouseEvent* event = aEvent->AsMouseEvent();
650
0
      if (event) {
651
0
        RefPtr<HTMLImageElement> img =
652
0
          HTMLImageElement::FromNode(mImageContent);
653
0
        x = event->ClientX() - img->OffsetLeft();
654
0
        y = event->ClientY() - img->OffsetTop();
655
0
      }
656
0
      mShouldResize = false;
657
0
      RestoreImageTo(x, y);
658
0
    }
659
0
    else if (ImageIsOverflowing()) {
660
0
      ShrinkToFit();
661
0
    }
662
0
  } else if (eventType.EqualsLiteral("load")) {
663
0
    UpdateSizeFromLayout();
664
0
  }
665
0
666
0
  return NS_OK;
667
0
}
668
669
void
670
ImageDocument::UpdateSizeFromLayout()
671
0
{
672
0
  // Pull an updated size from the content frame to account for any size
673
0
  // change due to CSS properties like |image-orientation|.
674
0
  if (!mImageContent) {
675
0
    return;
676
0
  }
677
0
678
0
  // Need strong ref, because GetPrimaryFrame can run script.
679
0
  nsCOMPtr<Element> imageContent = mImageContent;
680
0
  nsIFrame* contentFrame = imageContent->GetPrimaryFrame(FlushType::Frames);
681
0
  if (!contentFrame) {
682
0
    return;
683
0
  }
684
0
685
0
  nsIntSize oldSize(mImageWidth, mImageHeight);
686
0
  IntrinsicSize newSize = contentFrame->GetIntrinsicSize();
687
0
688
0
  if (newSize.width.GetUnit() == eStyleUnit_Coord) {
689
0
    mImageWidth = nsPresContext::AppUnitsToFloatCSSPixels(newSize.width.GetCoordValue());
690
0
  }
691
0
  if (newSize.height.GetUnit() == eStyleUnit_Coord) {
692
0
    mImageHeight = nsPresContext::AppUnitsToFloatCSSPixels(newSize.height.GetCoordValue());
693
0
  }
694
0
695
0
  // Ensure that our information about overflow is up-to-date if needed.
696
0
  if (mImageWidth != oldSize.width || mImageHeight != oldSize.height) {
697
0
    CheckOverflowing(false);
698
0
  }
699
0
}
700
701
nsresult
702
ImageDocument::CreateSyntheticDocument()
703
0
{
704
0
  // Synthesize an html document that refers to the image
705
0
  nsresult rv = MediaDocument::CreateSyntheticDocument();
706
0
  NS_ENSURE_SUCCESS(rv, rv);
707
0
708
0
  // Add the image element
709
0
  Element* body = GetBodyElement();
710
0
  if (!body) {
711
0
    NS_WARNING("no body on image document!");
712
0
    return NS_ERROR_FAILURE;
713
0
  }
714
0
715
0
  RefPtr<mozilla::dom::NodeInfo> nodeInfo;
716
0
  nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::img, nullptr,
717
0
                                           kNameSpaceID_XHTML,
718
0
                                           nsINode::ELEMENT_NODE);
719
0
720
0
  mImageContent = NS_NewHTMLImageElement(nodeInfo.forget());
721
0
  if (!mImageContent) {
722
0
    return NS_ERROR_OUT_OF_MEMORY;
723
0
  }
724
0
  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
725
0
  NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
726
0
727
0
  nsAutoCString src;
728
0
  mDocumentURI->GetSpec(src);
729
0
730
0
  NS_ConvertUTF8toUTF16 srcString(src);
731
0
  // Make sure not to start the image load from here...
732
0
  imageLoader->SetLoadingEnabled(false);
733
0
  mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::src, srcString, false);
734
0
  mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, srcString, false);
735
0
736
0
  body->AppendChildTo(mImageContent, false);
737
0
  imageLoader->SetLoadingEnabled(true);
738
0
739
0
  return NS_OK;
740
0
}
741
742
nsresult
743
ImageDocument::CheckOverflowing(bool changeState)
744
0
{
745
0
  /* Create a scope so that the ComputedStyle gets destroyed before we might
746
0
   * call RebuildStyleData.  Also, holding onto pointers to the
747
0
   * presentation through style resolution is potentially dangerous.
748
0
   */
749
0
  {
750
0
    nsPresContext* context = GetPresContext();
751
0
    if (!context) {
752
0
      return NS_OK;
753
0
    }
754
0
755
0
    nsRect visibleArea = context->GetVisibleArea();
756
0
757
0
    mVisibleWidth = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.width);
758
0
    mVisibleHeight = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.height);
759
0
  }
760
0
761
0
  bool imageWasOverflowing = ImageIsOverflowing();
762
0
  bool imageWasOverflowingVertically = mImageIsOverflowingVertically;
763
0
  mImageIsOverflowingHorizontally = mImageWidth > mVisibleWidth;
764
0
  mImageIsOverflowingVertically = mImageHeight > mVisibleHeight;
765
0
  bool windowBecameBigEnough = imageWasOverflowing && !ImageIsOverflowing();
766
0
  bool verticalOverflowChanged =
767
0
    mImageIsOverflowingVertically != imageWasOverflowingVertically;
768
0
769
0
  if (changeState || mShouldResize || mFirstResize ||
770
0
      windowBecameBigEnough || verticalOverflowChanged) {
771
0
    if (ImageIsOverflowing() && (changeState || mShouldResize)) {
772
0
      ShrinkToFit();
773
0
    }
774
0
    else if (mImageIsResized || mFirstResize || windowBecameBigEnough) {
775
0
      RestoreImage();
776
0
    } else if (!mImageIsResized && verticalOverflowChanged) {
777
0
      if (mImageIsOverflowingVertically) {
778
0
        SetModeClass(eOverflowingVertical);
779
0
      } else {
780
0
        SetModeClass(eOverflowingHorizontalOnly);
781
0
      }
782
0
    }
783
0
  }
784
0
  mFirstResize = false;
785
0
786
0
  return NS_OK;
787
0
}
788
789
void
790
ImageDocument::UpdateTitleAndCharset()
791
0
{
792
0
  nsAutoCString typeStr;
793
0
  nsCOMPtr<imgIRequest> imageRequest;
794
0
  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
795
0
  if (imageLoader) {
796
0
    imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
797
0
                            getter_AddRefs(imageRequest));
798
0
  }
799
0
800
0
  if (imageRequest) {
801
0
    nsCString mimeType;
802
0
    imageRequest->GetMimeType(getter_Copies(mimeType));
803
0
    ToUpperCase(mimeType);
804
0
    nsCString::const_iterator start, end;
805
0
    mimeType.BeginReading(start);
806
0
    mimeType.EndReading(end);
807
0
    nsCString::const_iterator iter = end;
808
0
    if (FindInReadable(NS_LITERAL_CSTRING("IMAGE/"), start, iter) &&
809
0
        iter != end) {
810
0
      // strip out "X-" if any
811
0
      if (*iter == 'X') {
812
0
        ++iter;
813
0
        if (iter != end && *iter == '-') {
814
0
          ++iter;
815
0
          if (iter == end) {
816
0
            // looks like "IMAGE/X-" is the type??  Bail out of here.
817
0
            mimeType.BeginReading(iter);
818
0
          }
819
0
        } else {
820
0
          --iter;
821
0
        }
822
0
      }
823
0
      typeStr = Substring(iter, end);
824
0
    } else {
825
0
      typeStr = mimeType;
826
0
    }
827
0
  }
828
0
829
0
  nsAutoString status;
830
0
  if (mImageIsResized) {
831
0
    nsAutoString ratioStr;
832
0
    ratioStr.AppendInt(NSToCoordFloor(GetRatio() * 100));
833
0
834
0
    const char16_t* formatString[1] = { ratioStr.get() };
835
0
    mStringBundle->FormatStringFromName("ScaledImage", formatString, 1, status);
836
0
  }
837
0
838
0
  static const char* const formatNames[4] =
839
0
  {
840
0
    "ImageTitleWithNeitherDimensionsNorFile",
841
0
    "ImageTitleWithoutDimensions",
842
0
    "ImageTitleWithDimensions2",
843
0
    "ImageTitleWithDimensions2AndFile",
844
0
  };
845
0
846
0
  MediaDocument::UpdateTitleAndCharset(typeStr, mChannel, formatNames,
847
0
                                       mImageWidth, mImageHeight, status);
848
0
}
849
850
void
851
ImageDocument::ResetZoomLevel()
852
0
{
853
0
  nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
854
0
  if (docShell) {
855
0
    if (nsContentUtils::IsChildOfSameType(this)) {
856
0
      return;
857
0
    }
858
0
859
0
    nsCOMPtr<nsIContentViewer> cv;
860
0
    docShell->GetContentViewer(getter_AddRefs(cv));
861
0
    if (cv) {
862
0
      cv->SetFullZoom(mOriginalZoomLevel);
863
0
    }
864
0
  }
865
0
}
866
867
float
868
ImageDocument::GetZoomLevel()
869
0
{
870
0
  float zoomLevel = mOriginalZoomLevel;
871
0
  nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
872
0
  if (docShell) {
873
0
    nsCOMPtr<nsIContentViewer> cv;
874
0
    docShell->GetContentViewer(getter_AddRefs(cv));
875
0
    if (cv) {
876
0
      cv->GetFullZoom(&zoomLevel);
877
0
    }
878
0
  }
879
0
  return zoomLevel;
880
0
}
881
882
#if defined(MOZ_WIDGET_ANDROID)
883
float
884
ImageDocument::GetResolution()
885
{
886
  float resolution = mOriginalResolution;
887
  nsCOMPtr<nsIPresShell> shell = GetShell();
888
  if (shell) {
889
    resolution = shell->GetResolution();
890
  }
891
  return resolution;
892
}
893
#endif
894
895
} // namespace dom
896
} // namespace mozilla
897
898
nsresult
899
NS_NewImageDocument(nsIDocument** aResult)
900
0
{
901
0
  mozilla::dom::ImageDocument* doc = new mozilla::dom::ImageDocument();
902
0
  NS_ADDREF(doc);
903
0
904
0
  nsresult rv = doc->Init();
905
0
  if (NS_FAILED(rv)) {
906
0
    NS_RELEASE(doc);
907
0
  }
908
0
909
0
  *aResult = doc;
910
0
911
0
  return rv;
912
0
}