tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ImageDocument.cpp (25378B)


      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 
      9 #include <algorithm>
     10 
     11 #include "DocumentInlines.h"
     12 #include "ImageBlocker.h"
     13 #include "imgIContainer.h"
     14 #include "imgINotificationObserver.h"
     15 #include "imgIRequest.h"
     16 #include "mozilla/AutoRestore.h"
     17 #include "mozilla/ComputedStyle.h"
     18 #include "mozilla/LoadInfo.h"
     19 #include "mozilla/Preferences.h"
     20 #include "mozilla/PresShell.h"
     21 #include "mozilla/ScrollContainerFrame.h"
     22 #include "mozilla/StaticPrefs_browser.h"
     23 #include "mozilla/dom/BrowserChild.h"
     24 #include "mozilla/dom/Element.h"
     25 #include "mozilla/dom/Event.h"
     26 #include "mozilla/dom/HTMLImageElement.h"
     27 #include "mozilla/dom/ImageDocumentBinding.h"
     28 #include "mozilla/dom/MouseEvent.h"
     29 #include "nsContentPolicyUtils.h"
     30 #include "nsContentUtils.h"
     31 #include "nsDOMCSSDeclaration.h"
     32 #include "nsDOMTokenList.h"
     33 #include "nsDocShell.h"
     34 #include "nsError.h"
     35 #include "nsGenericHTMLElement.h"
     36 #include "nsGkAtoms.h"
     37 #include "nsIChannel.h"
     38 #include "nsIContentPolicy.h"
     39 #include "nsIDOMEventListener.h"
     40 #include "nsIDocShell.h"
     41 #include "nsIDocumentViewer.h"
     42 #include "nsIFrame.h"
     43 #include "nsIImageLoadingContent.h"
     44 #include "nsObjectLoadingContent.h"
     45 #include "nsPIDOMWindow.h"
     46 #include "nsPresContext.h"
     47 #include "nsRect.h"
     48 #include "nsThreadUtils.h"
     49 #include "nsURILoader.h"
     50 
     51 namespace mozilla::dom {
     52 
     53 class ImageListener : public MediaDocumentStreamListener {
     54 public:
     55  // NS_DECL_NSIREQUESTOBSERVER
     56  // We only implement OnStartRequest; OnStopRequest is
     57  // implemented by MediaDocumentStreamListener
     58  NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override;
     59 
     60  explicit ImageListener(ImageDocument* aDocument);
     61  virtual ~ImageListener();
     62 };
     63 
     64 ImageListener::ImageListener(ImageDocument* aDocument)
     65    : MediaDocumentStreamListener(aDocument) {}
     66 
     67 ImageListener::~ImageListener() = default;
     68 
     69 NS_IMETHODIMP
     70 ImageListener::OnStartRequest(nsIRequest* request) {
     71  NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
     72 
     73  ImageDocument* imgDoc = static_cast<ImageDocument*>(mDocument.get());
     74  nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
     75  if (!channel) {
     76    return NS_ERROR_FAILURE;
     77  }
     78 
     79  nsCOMPtr<nsPIDOMWindowOuter> domWindow = imgDoc->GetWindow();
     80  NS_ENSURE_TRUE(domWindow, NS_ERROR_UNEXPECTED);
     81 
     82  // This is an image being loaded as a document, so it's not going to be
     83  // detected by the ImageBlocker. However we don't want to call
     84  // NS_CheckContentLoadPolicy (with an TYPE_INTERNAL_IMAGE) here, as it would
     85  // e.g. make this image load be detectable by CSP.
     86  nsCOMPtr<nsIURI> channelURI;
     87  channel->GetURI(getter_AddRefs(channelURI));
     88  if (image::ImageBlocker::ShouldBlock(channelURI)) {
     89    request->Cancel(NS_ERROR_CONTENT_BLOCKED);
     90    return NS_OK;
     91  }
     92 
     93  if (!imgDoc->mObservingImageLoader) {
     94    NS_ENSURE_TRUE(imgDoc->mImageContent, NS_ERROR_UNEXPECTED);
     95    imgDoc->mImageContent->AddNativeObserver(imgDoc);
     96    imgDoc->mObservingImageLoader = true;
     97    imgDoc->mImageContent->LoadImageWithChannel(channel,
     98                                                getter_AddRefs(mNextStream));
     99  }
    100 
    101  return MediaDocumentStreamListener::OnStartRequest(request);
    102 }
    103 
    104 ImageDocument::ImageDocument()
    105    : mVisibleWidth(0.0),
    106      mVisibleHeight(0.0),
    107      mImageWidth(0),
    108      mImageHeight(0),
    109      mImageIsResized(false),
    110      mShouldResize(false),
    111      mFirstResize(false),
    112      mObservingImageLoader(false),
    113      mTitleUpdateInProgress(false),
    114      mHasCustomTitle(false),
    115      mIsInObjectOrEmbed(false),
    116      mOriginalZoomLevel(1.0),
    117      mOriginalResolution(1.0) {}
    118 
    119 ImageDocument::~ImageDocument() = default;
    120 
    121 NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageDocument, MediaDocument, mImageContent)
    122 
    123 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(ImageDocument, MediaDocument,
    124                                             imgINotificationObserver,
    125                                             nsIDOMEventListener)
    126 
    127 nsresult ImageDocument::Init(nsIPrincipal* aPrincipal,
    128                             nsIPrincipal* aPartitionedPrincipal) {
    129  nsresult rv = MediaDocument::Init(aPrincipal, aPartitionedPrincipal);
    130  NS_ENSURE_SUCCESS(rv, rv);
    131 
    132  mShouldResize = StaticPrefs::browser_enable_automatic_image_resizing();
    133  mFirstResize = true;
    134 
    135  return NS_OK;
    136 }
    137 
    138 JSObject* ImageDocument::WrapNode(JSContext* aCx,
    139                                  JS::Handle<JSObject*> aGivenProto) {
    140  return ImageDocument_Binding::Wrap(aCx, this, aGivenProto);
    141 }
    142 
    143 nsresult ImageDocument::StartDocumentLoad(
    144    const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup,
    145    nsISupports* aContainer, nsIStreamListener** aDocListener, bool aReset) {
    146  nsresult rv = MediaDocument::StartDocumentLoad(
    147      aCommand, aChannel, aLoadGroup, aContainer, aDocListener, aReset);
    148  if (NS_FAILED(rv)) {
    149    return rv;
    150  }
    151 
    152  mOriginalZoomLevel = IsSiteSpecific() ? 1.0 : GetZoomLevel();
    153  CheckFullZoom();
    154  mOriginalResolution = GetResolution();
    155 
    156  if (BrowsingContext* context = GetBrowsingContext()) {
    157    mIsInObjectOrEmbed = context->IsEmbedderTypeObjectOrEmbed();
    158  }
    159 
    160  NS_ASSERTION(aDocListener, "null aDocListener");
    161  *aDocListener = new ImageListener(this);
    162  NS_ADDREF(*aDocListener);
    163 
    164  return NS_OK;
    165 }
    166 
    167 void ImageDocument::Destroy() {
    168  if (RefPtr<HTMLImageElement> img = std::move(mImageContent)) {
    169    // Remove our event listener from the image content.
    170    img->RemoveEventListener(u"load"_ns, this, false);
    171    img->RemoveEventListener(u"click"_ns, this, false);
    172 
    173    // Break reference cycle with mImageContent, if we have one
    174    if (mObservingImageLoader) {
    175      img->RemoveNativeObserver(this);
    176    }
    177  }
    178 
    179  MediaDocument::Destroy();
    180 }
    181 
    182 void ImageDocument::SetScriptGlobalObject(
    183    nsIScriptGlobalObject* aScriptGlobalObject) {
    184  // If the script global object is changing, we need to unhook our event
    185  // listeners on the window.
    186  nsCOMPtr<EventTarget> target;
    187  if (mScriptGlobalObject && aScriptGlobalObject != mScriptGlobalObject) {
    188    target = do_QueryInterface(mScriptGlobalObject);
    189    target->RemoveEventListener(u"resize"_ns, this, false);
    190    target->RemoveEventListener(u"keypress"_ns, this, false);
    191  }
    192 
    193  // Set the script global object on the superclass before doing
    194  // anything that might require it....
    195  MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
    196 
    197  if (aScriptGlobalObject) {
    198    if (!InitialSetupHasBeenDone()) {
    199      MOZ_ASSERT(!GetRootElement(), "Where did the root element come from?");
    200      // Create synthetic document
    201 #ifdef DEBUG
    202      nsresult rv =
    203 #endif
    204          CreateSyntheticDocument();
    205      NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document");
    206 
    207      target = mImageContent;
    208      target->AddEventListener(u"load"_ns, this, false);
    209      target->AddEventListener(u"click"_ns, this, false);
    210    }
    211 
    212    target = do_QueryInterface(aScriptGlobalObject);
    213    target->AddEventListener(u"resize"_ns, this, false);
    214    target->AddEventListener(u"keypress"_ns, this, false);
    215 
    216    if (!InitialSetupHasBeenDone()) {
    217      LinkStylesheet(u"resource://content-accessible/ImageDocument.css"_ns);
    218      if (!nsContentUtils::IsChildOfSameType(this)) {
    219        LinkStylesheet(nsLiteralString(
    220            u"resource://content-accessible/TopLevelImageDocument.css"));
    221      }
    222      InitialSetupDone();
    223    }
    224  }
    225 }
    226 
    227 void ImageDocument::OnPageShow(bool aPersisted,
    228                               EventTarget* aDispatchStartTarget,
    229                               bool aOnlySystemGroup) {
    230  if (aPersisted) {
    231    mOriginalZoomLevel = IsSiteSpecific() ? 1.0 : GetZoomLevel();
    232    CheckFullZoom();
    233    mOriginalResolution = GetResolution();
    234  }
    235  RefPtr<ImageDocument> kungFuDeathGrip(this);
    236  UpdateSizeFromLayout();
    237 
    238  MediaDocument::OnPageShow(aPersisted, aDispatchStartTarget, aOnlySystemGroup);
    239 }
    240 
    241 void ImageDocument::ShrinkToFit() {
    242  if (!mImageContent) {
    243    return;
    244  }
    245  if (GetZoomLevel() != mOriginalZoomLevel && mImageIsResized &&
    246      !nsContentUtils::IsChildOfSameType(this)) {
    247    // If we're zoomed, so that we don't maintain the invariant that
    248    // mImageIsResized if and only if its displayed width/height fit in
    249    // mVisibleWidth/mVisibleHeight, then we may need to switch to/from the
    250    // overflowingVertical class here, because our viewport size may have
    251    // changed and we don't plan to adjust the image size to compensate.  Since
    252    // mImageIsResized it has a "height" attribute set, and we can just get the
    253    // displayed image height by getting .height on the HTMLImageElement.
    254    //
    255    // Hold strong ref, because Height() can run script.
    256    RefPtr<HTMLImageElement> img = mImageContent;
    257    uint32_t imageHeight = img->Height();
    258    nsDOMTokenList* classList = img->ClassList();
    259    if (imageHeight > mVisibleHeight) {
    260      classList->Add(u"overflowingVertical"_ns, IgnoreErrors());
    261    } else {
    262      classList->Remove(u"overflowingVertical"_ns, IgnoreErrors());
    263    }
    264    return;
    265  }
    266  if (GetResolution() != mOriginalResolution && mImageIsResized) {
    267    // Don't resize if resolution has changed, e.g., through pinch-zooming on
    268    // Android.
    269    return;
    270  }
    271 
    272  // Keep image content alive while changing the attributes.
    273  RefPtr<HTMLImageElement> image = mImageContent;
    274 
    275  uint32_t newWidth = std::max(1, NSToCoordFloor(GetRatio() * mImageWidth));
    276  uint32_t newHeight = std::max(1, NSToCoordFloor(GetRatio() * mImageHeight));
    277  image->SetWidth(newWidth, IgnoreErrors());
    278  image->SetHeight(newHeight, IgnoreErrors());
    279 
    280  // The view might have been scrolled when zooming in, scroll back to the
    281  // origin now that we're showing a shrunk-to-window version.
    282  ScrollImageTo(0, 0);
    283 
    284  if (!mImageContent) {
    285    // ScrollImageTo flush destroyed our content.
    286    return;
    287  }
    288 
    289  SetModeClass(eShrinkToFit);
    290 
    291  mImageIsResized = true;
    292 
    293  UpdateTitleAndCharset();
    294 }
    295 
    296 void ImageDocument::ScrollImageTo(int32_t aX, int32_t aY) {
    297  RefPtr<PresShell> presShell = GetPresShell();
    298  if (!presShell) {
    299    return;
    300  }
    301 
    302  ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame();
    303  if (!sf) {
    304    return;
    305  }
    306 
    307  float ratio = GetRatio();
    308  // Don't try to scroll image if the document is not visible (mVisibleWidth or
    309  // mVisibleHeight is zero).
    310  if (ratio <= 0.0) {
    311    return;
    312  }
    313  nsRect portRect = sf->GetScrollPortRect();
    314  sf->ScrollTo(
    315      nsPoint(
    316          nsPresContext::CSSPixelsToAppUnits(aX / ratio) - portRect.width / 2,
    317          nsPresContext::CSSPixelsToAppUnits(aY / ratio) - portRect.height / 2),
    318      ScrollMode::Instant);
    319 }
    320 
    321 void ImageDocument::RestoreImage() {
    322  if (!mImageContent) {
    323    return;
    324  }
    325  // Keep image content alive while changing the attributes.
    326  RefPtr<HTMLImageElement> imageContent = mImageContent;
    327  imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::width, true);
    328  imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::height, true);
    329 
    330  if (mIsInObjectOrEmbed) {
    331    SetModeClass(eIsInObjectOrEmbed);
    332  } else if (ImageIsOverflowing()) {
    333    if (!ImageIsOverflowingVertically()) {
    334      SetModeClass(eOverflowingHorizontalOnly);
    335    } else {
    336      SetModeClass(eOverflowingVertical);
    337    }
    338  } else {
    339    SetModeClass(eNone);
    340  }
    341 
    342  mImageIsResized = false;
    343 
    344  UpdateTitleAndCharset();
    345 }
    346 
    347 void ImageDocument::NotifyPossibleTitleChange(bool aBoundTitleElement) {
    348  if (!mHasCustomTitle && !mTitleUpdateInProgress) {
    349    mHasCustomTitle = true;
    350  }
    351 
    352  Document::NotifyPossibleTitleChange(aBoundTitleElement);
    353 }
    354 
    355 void ImageDocument::Notify(imgIRequest* aRequest, int32_t aType,
    356                           const nsIntRect* aData) {
    357  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
    358    nsCOMPtr<imgIContainer> image;
    359    aRequest->GetImage(getter_AddRefs(image));
    360    return OnSizeAvailable(aRequest, image);
    361  }
    362 
    363  // Run this using a script runner because HAS_TRANSPARENCY notifications can
    364  // come during painting and this will trigger invalidation.
    365  if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
    366    nsCOMPtr<nsIRunnable> runnable =
    367        NewRunnableMethod("dom::ImageDocument::OnHasTransparency", this,
    368                          &ImageDocument::OnHasTransparency);
    369    nsContentUtils::AddScriptRunner(runnable);
    370  }
    371 
    372  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
    373    uint32_t reqStatus;
    374    aRequest->GetImageStatus(&reqStatus);
    375    nsresult status =
    376        reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
    377    return OnLoadComplete(aRequest, status);
    378  }
    379 }
    380 
    381 void ImageDocument::OnHasTransparency() {
    382  if (!mImageContent || nsContentUtils::IsChildOfSameType(this)) {
    383    return;
    384  }
    385 
    386  nsDOMTokenList* classList = mImageContent->ClassList();
    387  classList->Add(u"transparent"_ns, IgnoreErrors());
    388 }
    389 
    390 void ImageDocument::SetModeClass(eModeClasses mode) {
    391  nsDOMTokenList* classList = mImageContent->ClassList();
    392 
    393  if (mode == eShrinkToFit) {
    394    classList->Add(u"shrinkToFit"_ns, IgnoreErrors());
    395  } else {
    396    classList->Remove(u"shrinkToFit"_ns, IgnoreErrors());
    397  }
    398 
    399  if (mode == eOverflowingVertical) {
    400    classList->Add(u"overflowingVertical"_ns, IgnoreErrors());
    401  } else {
    402    classList->Remove(u"overflowingVertical"_ns, IgnoreErrors());
    403  }
    404 
    405  if (mode == eOverflowingHorizontalOnly) {
    406    classList->Add(u"overflowingHorizontalOnly"_ns, IgnoreErrors());
    407  } else {
    408    classList->Remove(u"overflowingHorizontalOnly"_ns, IgnoreErrors());
    409  }
    410 
    411  if (mode == eIsInObjectOrEmbed) {
    412    classList->Add(u"isInObjectOrEmbed"_ns, IgnoreErrors());
    413  }
    414 }
    415 
    416 void ImageDocument::OnSizeAvailable(imgIRequest* aRequest,
    417                                    imgIContainer* aImage) {
    418  int32_t oldWidth = mImageWidth;
    419  int32_t oldHeight = mImageHeight;
    420 
    421  // Styles have not yet been applied, so we don't know the final size. For now,
    422  // default to the image's intrinsic size.
    423  aImage->GetWidth(&mImageWidth);
    424  aImage->GetHeight(&mImageHeight);
    425 
    426  // Multipart images send size available for each part; ignore them if it
    427  // doesn't change our size. (We may not even support changing size in
    428  // multipart images in the future.)
    429  if (oldWidth == mImageWidth && oldHeight == mImageHeight) {
    430    return;
    431  }
    432 
    433  nsCOMPtr<nsIRunnable> runnable =
    434      NewRunnableMethod("dom::ImageDocument::DefaultCheckOverflowing", this,
    435                        &ImageDocument::DefaultCheckOverflowing);
    436  nsContentUtils::AddScriptRunner(runnable);
    437  UpdateTitleAndCharset();
    438 }
    439 
    440 void ImageDocument::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) {
    441  UpdateTitleAndCharset();
    442 
    443  // mImageContent can be null if the document is already destroyed
    444  if (NS_FAILED(aStatus) && mImageContent) {
    445    nsAutoCString src;
    446    mDocumentURI->GetSpec(src);
    447    AutoTArray<nsString, 1> formatString;
    448    CopyUTF8toUTF16(src, *formatString.AppendElement());
    449    nsAutoString errorMsg;
    450    FormatStringFromName("InvalidImage", formatString, errorMsg);
    451 
    452    mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false);
    453  }
    454 
    455  MaybeSendResultToEmbedder(aStatus);
    456 }
    457 
    458 NS_IMETHODIMP
    459 ImageDocument::HandleEvent(Event* aEvent) {
    460  nsAutoString eventType;
    461  aEvent->GetType(eventType);
    462  if (eventType.EqualsLiteral("resize")) {
    463    CheckOverflowing(false);
    464    CheckFullZoom();
    465  } else if (eventType.EqualsLiteral("click") &&
    466             StaticPrefs::browser_enable_click_image_resizing() &&
    467             !mIsInObjectOrEmbed) {
    468    ResetZoomLevel();
    469    mShouldResize = true;
    470    if (mImageIsResized) {
    471      int32_t x = 0, y = 0;
    472      MouseEvent* event = aEvent->AsMouseEvent();
    473      if (event) {
    474        RefPtr<HTMLImageElement> img = mImageContent;
    475        x = event->ClientX() - img->OffsetLeft();
    476        y = event->ClientY() - img->OffsetTop();
    477      }
    478      mShouldResize = false;
    479      RestoreImage();
    480      FlushPendingNotifications(FlushType::Layout);
    481      ScrollImageTo(x, y);
    482    } else if (ImageIsOverflowing()) {
    483      ShrinkToFit();
    484    }
    485  } else if (eventType.EqualsLiteral("load")) {
    486    UpdateSizeFromLayout();
    487  }
    488 
    489  return NS_OK;
    490 }
    491 
    492 void ImageDocument::UpdateSizeFromLayout() {
    493  // Pull an updated size from the content frame to account for any size
    494  // change due to CSS properties like |image-orientation|.
    495  if (!mImageContent) {
    496    return;
    497  }
    498 
    499  // Need strong ref, because GetPrimaryFrame can run script.
    500  RefPtr<HTMLImageElement> imageContent = mImageContent;
    501  nsIFrame* contentFrame = imageContent->GetPrimaryFrame(FlushType::Frames);
    502  if (!contentFrame) {
    503    return;
    504  }
    505 
    506  nsIntSize oldSize(mImageWidth, mImageHeight);
    507  IntrinsicSize newSize = contentFrame->GetIntrinsicSize();
    508 
    509  if (newSize.width) {
    510    mImageWidth = nsPresContext::AppUnitsToFloatCSSPixels(*newSize.width);
    511  }
    512  if (newSize.height) {
    513    mImageHeight = nsPresContext::AppUnitsToFloatCSSPixels(*newSize.height);
    514  }
    515 
    516  // Ensure that our information about overflow is up-to-date if needed.
    517  if (mImageWidth != oldSize.width || mImageHeight != oldSize.height) {
    518    CheckOverflowing(false);
    519  }
    520 }
    521 
    522 void ImageDocument::UpdateRemoteStyle(StyleImageRendering aImageRendering) {
    523  if (!mImageContent) {
    524    return;
    525  }
    526 
    527  // Using ScriptRunner to avoid doing DOM mutation at an unexpected time.
    528  if (!nsContentUtils::IsSafeToRunScript()) {
    529    return nsContentUtils::AddScriptRunner(
    530        NewRunnableMethod<StyleImageRendering>(
    531            "UpdateRemoteStyle", this, &ImageDocument::UpdateRemoteStyle,
    532            aImageRendering));
    533  }
    534 
    535  nsCOMPtr<nsDOMCSSDeclaration> style = mImageContent->Style();
    536  switch (aImageRendering) {
    537    case StyleImageRendering::Auto:
    538    case StyleImageRendering::Smooth:
    539    case StyleImageRendering::Optimizequality:
    540      style->SetProperty("image-rendering"_ns, "auto"_ns, ""_ns,
    541                         IgnoreErrors());
    542      break;
    543    case StyleImageRendering::Optimizespeed:
    544    case StyleImageRendering::Pixelated:
    545      style->SetProperty("image-rendering"_ns, "pixelated"_ns, ""_ns,
    546                         IgnoreErrors());
    547      break;
    548    case StyleImageRendering::CrispEdges:
    549      style->SetProperty("image-rendering"_ns, "crisp-edges"_ns, ""_ns,
    550                         IgnoreErrors());
    551      break;
    552  }
    553 }
    554 
    555 nsresult ImageDocument::CreateSyntheticDocument() {
    556  // Synthesize an html document that refers to the image
    557  nsresult rv = MediaDocument::CreateSyntheticDocument();
    558  NS_ENSURE_SUCCESS(rv, rv);
    559 
    560  // Add the image element
    561  RefPtr<Element> body = GetBodyElement();
    562  if (!body) {
    563    NS_WARNING("no body on image document!");
    564    return NS_ERROR_FAILURE;
    565  }
    566 
    567  RefPtr<mozilla::dom::NodeInfo> nodeInfo;
    568  nodeInfo = mNodeInfoManager->GetNodeInfo(
    569      nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
    570 
    571  RefPtr<Element> image = NS_NewHTMLImageElement(nodeInfo.forget());
    572  mImageContent = HTMLImageElement::FromNodeOrNull(image);
    573  if (!mImageContent) {
    574    return NS_ERROR_OUT_OF_MEMORY;
    575  }
    576 
    577  nsAutoCString src;
    578  mDocumentURI->GetSpec(src);
    579 
    580  NS_ConvertUTF8toUTF16 srcString(src);
    581  // Make sure not to start the image load from here...
    582  mImageContent->SetLoadingEnabled(false);
    583  mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::src, srcString, false);
    584  mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, srcString, false);
    585 
    586  if (mIsInObjectOrEmbed) {
    587    SetModeClass(eIsInObjectOrEmbed);
    588  }
    589 
    590  body->AppendChildTo(mImageContent, false, IgnoreErrors());
    591  mImageContent->SetLoadingEnabled(true);
    592 
    593  return NS_OK;
    594 }
    595 
    596 void ImageDocument::DefaultCheckOverflowing() {
    597  CheckOverflowing(StaticPrefs::browser_enable_automatic_image_resizing());
    598 }
    599 
    600 nsresult ImageDocument::CheckOverflowing(bool changeState) {
    601  const bool imageWasOverflowing = ImageIsOverflowing();
    602  const bool imageWasOverflowingVertically = ImageIsOverflowingVertically();
    603 
    604  {
    605    nsPresContext* context = GetPresContext();
    606    if (!context) {
    607      return NS_OK;
    608    }
    609 
    610    nsRect visibleArea = context->GetVisibleArea();
    611 
    612    mVisibleWidth = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.width);
    613    mVisibleHeight =
    614        nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.height);
    615  }
    616 
    617  const bool windowBecameBigEnough =
    618      imageWasOverflowing && !ImageIsOverflowing();
    619  const bool verticalOverflowChanged =
    620      imageWasOverflowingVertically != ImageIsOverflowingVertically();
    621 
    622  if (changeState || mShouldResize || mFirstResize || windowBecameBigEnough ||
    623      verticalOverflowChanged) {
    624    if (mIsInObjectOrEmbed) {
    625      SetModeClass(eIsInObjectOrEmbed);
    626    } else if (ImageIsOverflowing() && (changeState || mShouldResize)) {
    627      ShrinkToFit();
    628    } else if (mImageIsResized || mFirstResize || windowBecameBigEnough) {
    629      RestoreImage();
    630    } else if (!mImageIsResized && verticalOverflowChanged) {
    631      if (ImageIsOverflowingVertically()) {
    632        SetModeClass(eOverflowingVertical);
    633      } else {
    634        SetModeClass(eOverflowingHorizontalOnly);
    635      }
    636    }
    637  }
    638  mFirstResize = false;
    639  return NS_OK;
    640 }
    641 
    642 void ImageDocument::UpdateTitleAndCharset() {
    643  if (mHasCustomTitle) {
    644    return;
    645  }
    646 
    647  AutoRestore<bool> restore(mTitleUpdateInProgress);
    648  mTitleUpdateInProgress = true;
    649 
    650  nsAutoCString typeStr;
    651  nsCOMPtr<imgIRequest> imageRequest;
    652  if (mImageContent) {
    653    mImageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
    654                              getter_AddRefs(imageRequest));
    655  }
    656 
    657  if (imageRequest) {
    658    nsCString mimeType;
    659    imageRequest->GetMimeType(getter_Copies(mimeType));
    660    ToUpperCase(mimeType);
    661    nsCString::const_iterator start, end;
    662    mimeType.BeginReading(start);
    663    mimeType.EndReading(end);
    664    nsCString::const_iterator iter = end;
    665    if (FindInReadable("IMAGE/"_ns, start, iter) && iter != end) {
    666      // strip out "X-" if any
    667      if (*iter == 'X') {
    668        ++iter;
    669        if (iter != end && *iter == '-') {
    670          ++iter;
    671          if (iter == end) {
    672            // looks like "IMAGE/X-" is the type??  Bail out of here.
    673            mimeType.BeginReading(iter);
    674          }
    675        } else {
    676          --iter;
    677        }
    678      }
    679      typeStr = Substring(iter, end);
    680    } else {
    681      typeStr = mimeType;
    682    }
    683  }
    684 
    685  nsAutoString status;
    686  if (mImageIsResized) {
    687    AutoTArray<nsString, 1> formatString;
    688    formatString.AppendElement()->AppendInt(NSToCoordFloor(GetRatio() * 100));
    689 
    690    FormatStringFromName("ScaledImage", formatString, status);
    691  }
    692 
    693  static const char* const formatNames[4] = {
    694      "ImageTitleWithNeitherDimensionsNorFile",
    695      "ImageTitleWithoutDimensions",
    696      "ImageTitleWithDimensions2",
    697      "ImageTitleWithDimensions2AndFile",
    698  };
    699 
    700  MediaDocument::UpdateTitleAndCharset(typeStr, mChannel, formatNames,
    701                                       mImageWidth, mImageHeight, status);
    702 }
    703 
    704 bool ImageDocument::IsSiteSpecific() {
    705  return !ShouldResistFingerprinting(RFPTarget::SiteSpecificZoom) &&
    706         StaticPrefs::browser_zoom_siteSpecific();
    707 }
    708 
    709 void ImageDocument::ResetZoomLevel() {
    710  if (nsContentUtils::IsChildOfSameType(this)) {
    711    return;
    712  }
    713 
    714  if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
    715    // Resetting the zoom level on a discarded browsing context has no effect.
    716    (void)bc->SetFullZoom(mOriginalZoomLevel);
    717  }
    718 }
    719 
    720 float ImageDocument::GetZoomLevel() {
    721  if (BrowsingContext* bc = GetBrowsingContext()) {
    722    return bc->FullZoom();
    723  }
    724  return mOriginalZoomLevel;
    725 }
    726 
    727 void ImageDocument::CheckFullZoom() {
    728  nsDOMTokenList* classList =
    729      mImageContent ? mImageContent->ClassList() : nullptr;
    730 
    731  if (!classList) {
    732    return;
    733  }
    734 
    735  classList->Toggle(u"fullZoomOut"_ns,
    736                    dom::Optional<bool>(GetZoomLevel() > mOriginalZoomLevel),
    737                    IgnoreErrors());
    738  classList->Toggle(u"fullZoomIn"_ns,
    739                    dom::Optional<bool>(GetZoomLevel() < mOriginalZoomLevel),
    740                    IgnoreErrors());
    741 }
    742 
    743 float ImageDocument::GetResolution() {
    744  if (PresShell* presShell = GetPresShell()) {
    745    return presShell->GetResolution();
    746  }
    747  return mOriginalResolution;
    748 }
    749 
    750 void ImageDocument::MaybeSendResultToEmbedder(nsresult aResult) {
    751  if (!mIsInObjectOrEmbed) {
    752    return;
    753  }
    754 
    755  BrowsingContext* context = GetBrowsingContext();
    756 
    757  if (!context) {
    758    return;
    759  }
    760 
    761  if (context->GetParent() && context->GetParent()->IsInProcess()) {
    762    if (Element* embedder = context->GetEmbedderElement()) {
    763      if (nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent =
    764              do_QueryInterface(embedder)) {
    765        NS_DispatchToMainThread(NS_NewRunnableFunction(
    766            "nsObjectLoadingContent::SubdocumentImageLoadComplete",
    767            [objectLoadingContent, aResult]() {
    768              static_cast<nsObjectLoadingContent*>(objectLoadingContent.get())
    769                  ->SubdocumentImageLoadComplete(aResult);
    770            }));
    771      }
    772      return;
    773    }
    774  }
    775 
    776  if (BrowserChild* browserChild =
    777          BrowserChild::GetFrom(context->GetDocShell())) {
    778    browserChild->SendImageLoadComplete(aResult);
    779  }
    780 }
    781 }  // namespace mozilla::dom
    782 
    783 nsresult NS_NewImageDocument(mozilla::dom::Document** aResult,
    784                             nsIPrincipal* aPrincipal,
    785                             nsIPrincipal* aPartitionedPrincipal) {
    786  auto* doc = new mozilla::dom::ImageDocument();
    787  NS_ADDREF(doc);
    788 
    789  nsresult rv = doc->Init(aPrincipal, aPartitionedPrincipal);
    790  if (NS_FAILED(rv)) {
    791    NS_RELEASE(doc);
    792  }
    793 
    794  *aResult = doc;
    795 
    796  return rv;
    797 }