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 }