summaryrefslogtreecommitdiffstats
path: root/dom/html/ImageDocument.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/html/ImageDocument.cpp814
1 files changed, 814 insertions, 0 deletions
diff --git a/dom/html/ImageDocument.cpp b/dom/html/ImageDocument.cpp
new file mode 100644
index 0000000000..81fc9860ee
--- /dev/null
+++ b/dom/html/ImageDocument.cpp
@@ -0,0 +1,814 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImageDocument.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/ImageDocumentBinding.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsICSSDeclaration.h"
+#include "nsObjectLoadingContent.h"
+#include "nsRect.h"
+#include "nsIImageLoadingContent.h"
+#include "nsGenericHTMLElement.h"
+#include "nsDocShell.h"
+#include "DocumentInlines.h"
+#include "nsDOMTokenList.h"
+#include "nsIDOMEventListener.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "imgINotificationObserver.h"
+#include "nsPresContext.h"
+#include "nsIChannel.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsError.h"
+#include "nsURILoader.h"
+#include "nsIDocShell.h"
+#include "nsIContentViewer.h"
+#include "nsThreadUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+// XXX A hack needed for Firefox's site specific zoom.
+static bool IsSiteSpecific() {
+ return !nsContentUtils::ShouldResistFingerprinting(
+ "This needs to read the global pref as long as "
+ "browser-fullZoom.js also does so.") &&
+ mozilla::Preferences::GetBool("browser.zoom.siteSpecific", false);
+}
+
+namespace mozilla::dom {
+
+class ImageListener : public MediaDocumentStreamListener {
+ public:
+ // NS_DECL_NSIREQUESTOBSERVER
+ // We only implement OnStartRequest; OnStopRequest is
+ // implemented by MediaDocumentStreamListener
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override;
+
+ explicit ImageListener(ImageDocument* aDocument);
+ virtual ~ImageListener();
+};
+
+ImageListener::ImageListener(ImageDocument* aDocument)
+ : MediaDocumentStreamListener(aDocument) {}
+
+ImageListener::~ImageListener() = default;
+
+NS_IMETHODIMP
+ImageListener::OnStartRequest(nsIRequest* request) {
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
+
+ ImageDocument* imgDoc = static_cast<ImageDocument*>(mDocument.get());
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (!channel) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = imgDoc->GetWindow();
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_UNEXPECTED);
+
+ // Do a ShouldProcess check to see whether to keep loading the image.
+ nsCOMPtr<nsIURI> channelURI;
+ channel->GetURI(getter_AddRefs(channelURI));
+
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ // query the corresponding arguments for the channel loadinfo and pass
+ // it on to the temporary loadinfo used for content policy checks.
+ nsCOMPtr<nsINode> requestingNode = domWindow->GetFrameElementInternal();
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ if (requestingNode) {
+ loadingPrincipal = requestingNode->NodePrincipal();
+ } else {
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ channel, getter_AddRefs(loadingPrincipal));
+ }
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
+ loadingPrincipal, loadInfo->TriggeringPrincipal(), requestingNode,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ int16_t decision = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentProcessPolicy(
+ channelURI, secCheckLoadInfo, mimeType, &decision,
+ nsContentUtils::GetContentPolicy());
+
+ if (NS_FAILED(rv) || NS_CP_REJECTED(decision)) {
+ request->Cancel(NS_ERROR_CONTENT_BLOCKED);
+ return NS_OK;
+ }
+
+ if (!imgDoc->mObservingImageLoader) {
+ NS_ENSURE_TRUE(imgDoc->mImageContent, NS_ERROR_UNEXPECTED);
+ imgDoc->mImageContent->AddNativeObserver(imgDoc);
+ imgDoc->mObservingImageLoader = true;
+ imgDoc->mImageContent->LoadImageWithChannel(channel,
+ getter_AddRefs(mNextStream));
+ }
+
+ return MediaDocumentStreamListener::OnStartRequest(request);
+}
+
+ImageDocument::ImageDocument()
+ : MediaDocument(),
+ mVisibleWidth(0.0),
+ mVisibleHeight(0.0),
+ mImageWidth(0),
+ mImageHeight(0),
+ mImageIsResized(false),
+ mShouldResize(false),
+ mFirstResize(false),
+ mObservingImageLoader(false),
+ mTitleUpdateInProgress(false),
+ mHasCustomTitle(false),
+ mIsInObjectOrEmbed(false),
+ mOriginalZoomLevel(1.0),
+ mOriginalResolution(1.0) {}
+
+ImageDocument::~ImageDocument() = default;
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageDocument, MediaDocument, mImageContent)
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(ImageDocument, MediaDocument,
+ imgINotificationObserver,
+ nsIDOMEventListener)
+
+nsresult ImageDocument::Init() {
+ nsresult rv = MediaDocument::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mShouldResize = StaticPrefs::browser_enable_automatic_image_resizing();
+ mFirstResize = true;
+
+ return NS_OK;
+}
+
+JSObject* ImageDocument::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ImageDocument_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult ImageDocument::StartDocumentLoad(
+ const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer, nsIStreamListener** aDocListener, bool aReset) {
+ nsresult rv = MediaDocument::StartDocumentLoad(
+ aCommand, aChannel, aLoadGroup, aContainer, aDocListener, aReset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mOriginalZoomLevel = IsSiteSpecific() ? 1.0 : GetZoomLevel();
+ CheckFullZoom();
+ mOriginalResolution = GetResolution();
+
+ if (BrowsingContext* context = GetBrowsingContext()) {
+ mIsInObjectOrEmbed = context->IsEmbedderTypeObjectOrEmbed();
+ }
+
+ NS_ASSERTION(aDocListener, "null aDocListener");
+ *aDocListener = new ImageListener(this);
+ NS_ADDREF(*aDocListener);
+
+ return NS_OK;
+}
+
+void ImageDocument::Destroy() {
+ if (RefPtr<HTMLImageElement> img = std::move(mImageContent)) {
+ // Remove our event listener from the image content.
+ img->RemoveEventListener(u"load"_ns, this, false);
+ img->RemoveEventListener(u"click"_ns, this, false);
+
+ // Break reference cycle with mImageContent, if we have one
+ if (mObservingImageLoader) {
+ img->RemoveNativeObserver(this);
+ }
+ }
+
+ MediaDocument::Destroy();
+}
+
+void ImageDocument::SetScriptGlobalObject(
+ nsIScriptGlobalObject* aScriptGlobalObject) {
+ // If the script global object is changing, we need to unhook our event
+ // listeners on the window.
+ nsCOMPtr<EventTarget> target;
+ if (mScriptGlobalObject && aScriptGlobalObject != mScriptGlobalObject) {
+ target = do_QueryInterface(mScriptGlobalObject);
+ target->RemoveEventListener(u"resize"_ns, this, false);
+ target->RemoveEventListener(u"keypress"_ns, this, false);
+ }
+
+ // Set the script global object on the superclass before doing
+ // anything that might require it....
+ MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
+
+ if (aScriptGlobalObject) {
+ if (!InitialSetupHasBeenDone()) {
+ MOZ_ASSERT(!GetRootElement(), "Where did the root element come from?");
+ // Create synthetic document
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ CreateSyntheticDocument();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document");
+
+ target = mImageContent;
+ target->AddEventListener(u"load"_ns, this, false);
+ target->AddEventListener(u"click"_ns, this, false);
+ }
+
+ target = do_QueryInterface(aScriptGlobalObject);
+ target->AddEventListener(u"resize"_ns, this, false);
+ target->AddEventListener(u"keypress"_ns, this, false);
+
+ if (!InitialSetupHasBeenDone()) {
+ LinkStylesheet(u"resource://content-accessible/ImageDocument.css"_ns);
+ if (!nsContentUtils::IsChildOfSameType(this)) {
+ LinkStylesheet(nsLiteralString(
+ u"resource://content-accessible/TopLevelImageDocument.css"));
+ }
+ InitialSetupDone();
+ }
+ }
+}
+
+void ImageDocument::OnPageShow(bool aPersisted,
+ EventTarget* aDispatchStartTarget,
+ bool aOnlySystemGroup) {
+ if (aPersisted) {
+ mOriginalZoomLevel = IsSiteSpecific() ? 1.0 : GetZoomLevel();
+ CheckFullZoom();
+ mOriginalResolution = GetResolution();
+ }
+ RefPtr<ImageDocument> kungFuDeathGrip(this);
+ UpdateSizeFromLayout();
+
+ MediaDocument::OnPageShow(aPersisted, aDispatchStartTarget, aOnlySystemGroup);
+}
+
+void ImageDocument::ShrinkToFit() {
+ if (!mImageContent) {
+ return;
+ }
+ if (GetZoomLevel() != mOriginalZoomLevel && mImageIsResized &&
+ !nsContentUtils::IsChildOfSameType(this)) {
+ // If we're zoomed, so that we don't maintain the invariant that
+ // mImageIsResized if and only if its displayed width/height fit in
+ // mVisibleWidth/mVisibleHeight, then we may need to switch to/from the
+ // overflowingVertical class here, because our viewport size may have
+ // changed and we don't plan to adjust the image size to compensate. Since
+ // mImageIsResized it has a "height" attribute set, and we can just get the
+ // displayed image height by getting .height on the HTMLImageElement.
+ //
+ // Hold strong ref, because Height() can run script.
+ RefPtr<HTMLImageElement> img = mImageContent;
+ uint32_t imageHeight = img->Height();
+ nsDOMTokenList* classList = img->ClassList();
+ if (imageHeight > mVisibleHeight) {
+ classList->Add(u"overflowingVertical"_ns, IgnoreErrors());
+ } else {
+ classList->Remove(u"overflowingVertical"_ns, IgnoreErrors());
+ }
+ return;
+ }
+ if (GetResolution() != mOriginalResolution && mImageIsResized) {
+ // Don't resize if resolution has changed, e.g., through pinch-zooming on
+ // Android.
+ return;
+ }
+
+ // Keep image content alive while changing the attributes.
+ RefPtr<HTMLImageElement> image = mImageContent;
+
+ uint32_t newWidth = std::max(1, NSToCoordFloor(GetRatio() * mImageWidth));
+ uint32_t newHeight = std::max(1, NSToCoordFloor(GetRatio() * mImageHeight));
+ image->SetWidth(newWidth, IgnoreErrors());
+ image->SetHeight(newHeight, IgnoreErrors());
+
+ // The view might have been scrolled when zooming in, scroll back to the
+ // origin now that we're showing a shrunk-to-window version.
+ ScrollImageTo(0, 0);
+
+ if (!mImageContent) {
+ // ScrollImageTo flush destroyed our content.
+ return;
+ }
+
+ SetModeClass(eShrinkToFit);
+
+ mImageIsResized = true;
+
+ UpdateTitleAndCharset();
+}
+
+void ImageDocument::ScrollImageTo(int32_t aX, int32_t aY) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ if (!sf) {
+ return;
+ }
+
+ float ratio = GetRatio();
+ // Don't try to scroll image if the document is not visible (mVisibleWidth or
+ // mVisibleHeight is zero).
+ if (ratio <= 0.0) {
+ return;
+ }
+ nsRect portRect = sf->GetScrollPortRect();
+ sf->ScrollTo(
+ nsPoint(
+ nsPresContext::CSSPixelsToAppUnits(aX / ratio) - portRect.width / 2,
+ nsPresContext::CSSPixelsToAppUnits(aY / ratio) - portRect.height / 2),
+ ScrollMode::Instant);
+}
+
+void ImageDocument::RestoreImage() {
+ if (!mImageContent) {
+ return;
+ }
+ // Keep image content alive while changing the attributes.
+ RefPtr<HTMLImageElement> imageContent = mImageContent;
+ imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::width, true);
+ imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::height, true);
+
+ if (mIsInObjectOrEmbed) {
+ SetModeClass(eIsInObjectOrEmbed);
+ } else if (ImageIsOverflowing()) {
+ if (!ImageIsOverflowingVertically()) {
+ SetModeClass(eOverflowingHorizontalOnly);
+ } else {
+ SetModeClass(eOverflowingVertical);
+ }
+ } else {
+ SetModeClass(eNone);
+ }
+
+ mImageIsResized = false;
+
+ UpdateTitleAndCharset();
+}
+
+void ImageDocument::NotifyPossibleTitleChange(bool aBoundTitleElement) {
+ if (!mHasCustomTitle && !mTitleUpdateInProgress) {
+ mHasCustomTitle = true;
+ }
+
+ Document::NotifyPossibleTitleChange(aBoundTitleElement);
+}
+
+void ImageDocument::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ return OnSizeAvailable(aRequest, image);
+ }
+
+ // Run this using a script runner because HAS_TRANSPARENCY notifications can
+ // come during painting and this will trigger invalidation.
+ if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod("dom::ImageDocument::OnHasTransparency", this,
+ &ImageDocument::OnHasTransparency);
+ nsContentUtils::AddScriptRunner(runnable);
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ uint32_t reqStatus;
+ aRequest->GetImageStatus(&reqStatus);
+ nsresult status =
+ reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+}
+
+void ImageDocument::OnHasTransparency() {
+ if (!mImageContent || nsContentUtils::IsChildOfSameType(this)) {
+ return;
+ }
+
+ nsDOMTokenList* classList = mImageContent->ClassList();
+ classList->Add(u"transparent"_ns, IgnoreErrors());
+}
+
+void ImageDocument::SetModeClass(eModeClasses mode) {
+ nsDOMTokenList* classList = mImageContent->ClassList();
+
+ if (mode == eShrinkToFit) {
+ classList->Add(u"shrinkToFit"_ns, IgnoreErrors());
+ } else {
+ classList->Remove(u"shrinkToFit"_ns, IgnoreErrors());
+ }
+
+ if (mode == eOverflowingVertical) {
+ classList->Add(u"overflowingVertical"_ns, IgnoreErrors());
+ } else {
+ classList->Remove(u"overflowingVertical"_ns, IgnoreErrors());
+ }
+
+ if (mode == eOverflowingHorizontalOnly) {
+ classList->Add(u"overflowingHorizontalOnly"_ns, IgnoreErrors());
+ } else {
+ classList->Remove(u"overflowingHorizontalOnly"_ns, IgnoreErrors());
+ }
+
+ if (mode == eIsInObjectOrEmbed) {
+ classList->Add(u"isInObjectOrEmbed"_ns, IgnoreErrors());
+ }
+}
+
+void ImageDocument::OnSizeAvailable(imgIRequest* aRequest,
+ imgIContainer* aImage) {
+ int32_t oldWidth = mImageWidth;
+ int32_t oldHeight = mImageHeight;
+
+ // Styles have not yet been applied, so we don't know the final size. For now,
+ // default to the image's intrinsic size.
+ aImage->GetWidth(&mImageWidth);
+ aImage->GetHeight(&mImageHeight);
+
+ // Multipart images send size available for each part; ignore them if it
+ // doesn't change our size. (We may not even support changing size in
+ // multipart images in the future.)
+ if (oldWidth == mImageWidth && oldHeight == mImageHeight) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod("dom::ImageDocument::DefaultCheckOverflowing", this,
+ &ImageDocument::DefaultCheckOverflowing);
+ nsContentUtils::AddScriptRunner(runnable);
+ UpdateTitleAndCharset();
+}
+
+void ImageDocument::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) {
+ UpdateTitleAndCharset();
+
+ // mImageContent can be null if the document is already destroyed
+ if (NS_FAILED(aStatus) && mImageContent) {
+ nsAutoCString src;
+ mDocumentURI->GetSpec(src);
+ AutoTArray<nsString, 1> formatString;
+ CopyUTF8toUTF16(src, *formatString.AppendElement());
+ nsAutoString errorMsg;
+ FormatStringFromName("InvalidImage", formatString, errorMsg);
+
+ mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false);
+ }
+
+ MaybeSendResultToEmbedder(aStatus);
+}
+
+NS_IMETHODIMP
+ImageDocument::HandleEvent(Event* aEvent) {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("resize")) {
+ CheckOverflowing(false);
+ CheckFullZoom();
+ } else if (eventType.EqualsLiteral("click") &&
+ StaticPrefs::browser_enable_click_image_resizing() &&
+ !mIsInObjectOrEmbed) {
+ ResetZoomLevel();
+ mShouldResize = true;
+ if (mImageIsResized) {
+ int32_t x = 0, y = 0;
+ MouseEvent* event = aEvent->AsMouseEvent();
+ if (event) {
+ RefPtr<HTMLImageElement> img = mImageContent;
+ x = event->ClientX() - img->OffsetLeft();
+ y = event->ClientY() - img->OffsetTop();
+ }
+ mShouldResize = false;
+ RestoreImage();
+ FlushPendingNotifications(FlushType::Layout);
+ ScrollImageTo(x, y);
+ } else if (ImageIsOverflowing()) {
+ ShrinkToFit();
+ }
+ } else if (eventType.EqualsLiteral("load")) {
+ UpdateSizeFromLayout();
+ }
+
+ return NS_OK;
+}
+
+void ImageDocument::UpdateSizeFromLayout() {
+ // Pull an updated size from the content frame to account for any size
+ // change due to CSS properties like |image-orientation|.
+ if (!mImageContent) {
+ return;
+ }
+
+ // Need strong ref, because GetPrimaryFrame can run script.
+ RefPtr<HTMLImageElement> imageContent = mImageContent;
+ nsIFrame* contentFrame = imageContent->GetPrimaryFrame(FlushType::Frames);
+ if (!contentFrame) {
+ return;
+ }
+
+ nsIntSize oldSize(mImageWidth, mImageHeight);
+ IntrinsicSize newSize = contentFrame->GetIntrinsicSize();
+
+ if (newSize.width) {
+ mImageWidth = nsPresContext::AppUnitsToFloatCSSPixels(*newSize.width);
+ }
+ if (newSize.height) {
+ mImageHeight = nsPresContext::AppUnitsToFloatCSSPixels(*newSize.height);
+ }
+
+ // Ensure that our information about overflow is up-to-date if needed.
+ if (mImageWidth != oldSize.width || mImageHeight != oldSize.height) {
+ CheckOverflowing(false);
+ }
+}
+
+void ImageDocument::UpdateRemoteStyle(StyleImageRendering aImageRendering) {
+ if (!mImageContent) {
+ return;
+ }
+
+ // Using ScriptRunner to avoid doing DOM mutation at an unexpected time.
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ return nsContentUtils::AddScriptRunner(
+ NewRunnableMethod<StyleImageRendering>(
+ "UpdateRemoteStyle", this, &ImageDocument::UpdateRemoteStyle,
+ aImageRendering));
+ }
+
+ nsCOMPtr<nsICSSDeclaration> style = mImageContent->Style();
+ switch (aImageRendering) {
+ case StyleImageRendering::Auto:
+ case StyleImageRendering::Smooth:
+ case StyleImageRendering::Optimizequality:
+ style->SetProperty("image-rendering"_ns, "auto"_ns, ""_ns,
+ IgnoreErrors());
+ break;
+ case StyleImageRendering::Optimizespeed:
+ case StyleImageRendering::Pixelated:
+ style->SetProperty("image-rendering"_ns, "pixelated"_ns, ""_ns,
+ IgnoreErrors());
+ break;
+ case StyleImageRendering::CrispEdges:
+ style->SetProperty("image-rendering"_ns, "crisp-edges"_ns, ""_ns,
+ IgnoreErrors());
+ break;
+ }
+}
+
+nsresult ImageDocument::CreateSyntheticDocument() {
+ // Synthesize an html document that refers to the image
+ nsresult rv = MediaDocument::CreateSyntheticDocument();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add the image element
+ RefPtr<Element> body = GetBodyElement();
+ if (!body) {
+ NS_WARNING("no body on image document!");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
+
+ RefPtr<Element> image = NS_NewHTMLImageElement(nodeInfo.forget());
+ mImageContent = HTMLImageElement::FromNodeOrNull(image);
+ if (!mImageContent) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString src;
+ mDocumentURI->GetSpec(src);
+
+ NS_ConvertUTF8toUTF16 srcString(src);
+ // Make sure not to start the image load from here...
+ mImageContent->SetLoadingEnabled(false);
+ mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::src, srcString, false);
+ mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, srcString, false);
+
+ body->AppendChildTo(mImageContent, false, IgnoreErrors());
+ mImageContent->SetLoadingEnabled(true);
+
+ return NS_OK;
+}
+
+void ImageDocument::DefaultCheckOverflowing() {
+ CheckOverflowing(StaticPrefs::browser_enable_automatic_image_resizing());
+}
+
+nsresult ImageDocument::CheckOverflowing(bool changeState) {
+ const bool imageWasOverflowing = ImageIsOverflowing();
+ const bool imageWasOverflowingVertically = ImageIsOverflowingVertically();
+
+ {
+ nsPresContext* context = GetPresContext();
+ if (!context) {
+ return NS_OK;
+ }
+
+ nsRect visibleArea = context->GetVisibleArea();
+
+ mVisibleWidth = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.width);
+ mVisibleHeight =
+ nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.height);
+ }
+
+ const bool windowBecameBigEnough =
+ imageWasOverflowing && !ImageIsOverflowing();
+ const bool verticalOverflowChanged =
+ imageWasOverflowingVertically != ImageIsOverflowingVertically();
+
+ if (changeState || mShouldResize || mFirstResize || windowBecameBigEnough ||
+ verticalOverflowChanged) {
+ if (mIsInObjectOrEmbed) {
+ SetModeClass(eIsInObjectOrEmbed);
+ } else if (ImageIsOverflowing() && (changeState || mShouldResize)) {
+ ShrinkToFit();
+ } else if (mImageIsResized || mFirstResize || windowBecameBigEnough) {
+ RestoreImage();
+ } else if (!mImageIsResized && verticalOverflowChanged) {
+ if (ImageIsOverflowingVertically()) {
+ SetModeClass(eOverflowingVertical);
+ } else {
+ SetModeClass(eOverflowingHorizontalOnly);
+ }
+ }
+ }
+ mFirstResize = false;
+ return NS_OK;
+}
+
+void ImageDocument::UpdateTitleAndCharset() {
+ if (mHasCustomTitle) {
+ return;
+ }
+
+ AutoRestore<bool> restore(mTitleUpdateInProgress);
+ mTitleUpdateInProgress = true;
+
+ nsAutoCString typeStr;
+ nsCOMPtr<imgIRequest> imageRequest;
+ if (mImageContent) {
+ mImageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imageRequest));
+ }
+
+ if (imageRequest) {
+ nsCString mimeType;
+ imageRequest->GetMimeType(getter_Copies(mimeType));
+ ToUpperCase(mimeType);
+ nsCString::const_iterator start, end;
+ mimeType.BeginReading(start);
+ mimeType.EndReading(end);
+ nsCString::const_iterator iter = end;
+ if (FindInReadable("IMAGE/"_ns, start, iter) && iter != end) {
+ // strip out "X-" if any
+ if (*iter == 'X') {
+ ++iter;
+ if (iter != end && *iter == '-') {
+ ++iter;
+ if (iter == end) {
+ // looks like "IMAGE/X-" is the type?? Bail out of here.
+ mimeType.BeginReading(iter);
+ }
+ } else {
+ --iter;
+ }
+ }
+ typeStr = Substring(iter, end);
+ } else {
+ typeStr = mimeType;
+ }
+ }
+
+ nsAutoString status;
+ if (mImageIsResized) {
+ AutoTArray<nsString, 1> formatString;
+ formatString.AppendElement()->AppendInt(NSToCoordFloor(GetRatio() * 100));
+
+ FormatStringFromName("ScaledImage", formatString, status);
+ }
+
+ static const char* const formatNames[4] = {
+ "ImageTitleWithNeitherDimensionsNorFile",
+ "ImageTitleWithoutDimensions",
+ "ImageTitleWithDimensions2",
+ "ImageTitleWithDimensions2AndFile",
+ };
+
+ MediaDocument::UpdateTitleAndCharset(typeStr, mChannel, formatNames,
+ mImageWidth, mImageHeight, status);
+}
+
+void ImageDocument::ResetZoomLevel() {
+ if (nsContentUtils::IsChildOfSameType(this)) {
+ return;
+ }
+
+ if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
+ // Resetting the zoom level on a discarded browsing context has no effect.
+ Unused << bc->SetFullZoom(mOriginalZoomLevel);
+ }
+}
+
+float ImageDocument::GetZoomLevel() {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ return bc->FullZoom();
+ }
+ return mOriginalZoomLevel;
+}
+
+void ImageDocument::CheckFullZoom() {
+ nsDOMTokenList* classList =
+ mImageContent ? mImageContent->ClassList() : nullptr;
+
+ if (!classList) {
+ return;
+ }
+
+ classList->Toggle(u"fullZoomOut"_ns,
+ dom::Optional<bool>(GetZoomLevel() > mOriginalZoomLevel),
+ IgnoreErrors());
+ classList->Toggle(u"fullZoomIn"_ns,
+ dom::Optional<bool>(GetZoomLevel() < mOriginalZoomLevel),
+ IgnoreErrors());
+}
+
+float ImageDocument::GetResolution() {
+ if (PresShell* presShell = GetPresShell()) {
+ return presShell->GetResolution();
+ }
+ return mOriginalResolution;
+}
+
+void ImageDocument::MaybeSendResultToEmbedder(nsresult aResult) {
+ if (!mIsInObjectOrEmbed) {
+ return;
+ }
+
+ BrowsingContext* context = GetBrowsingContext();
+
+ if (!context) {
+ return;
+ }
+
+ if (context->GetParent() && context->GetParent()->IsInProcess()) {
+ if (Element* embedder = context->GetEmbedderElement()) {
+ if (nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent =
+ do_QueryInterface(embedder)) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsObjectLoadingContent::SubdocumentImageLoadComplete",
+ [objectLoadingContent, aResult]() {
+ static_cast<nsObjectLoadingContent*>(objectLoadingContent.get())
+ ->SubdocumentImageLoadComplete(aResult);
+ }));
+ }
+ return;
+ }
+ }
+
+ if (BrowserChild* browserChild =
+ BrowserChild::GetFrom(context->GetDocShell())) {
+ browserChild->SendImageLoadComplete(aResult);
+ }
+}
+} // namespace mozilla::dom
+
+nsresult NS_NewImageDocument(mozilla::dom::Document** aResult) {
+ auto* doc = new mozilla::dom::ImageDocument();
+ NS_ADDREF(doc);
+
+ nsresult rv = doc->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(doc);
+ }
+
+ *aResult = doc;
+
+ return rv;
+}