michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ImageDocument.h" michael@0: #include "mozilla/dom/ImageDocumentBinding.h" michael@0: #include "nsRect.h" michael@0: #include "nsIImageLoadingContent.h" michael@0: #include "nsGenericHTMLElement.h" michael@0: #include "nsDocShell.h" michael@0: #include "nsIDocumentInlines.h" michael@0: #include "nsDOMTokenList.h" michael@0: #include "nsIDOMHTMLImageElement.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "imgIRequest.h" michael@0: #include "imgILoader.h" michael@0: #include "imgIContainer.h" michael@0: #include "imgINotificationObserver.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIContentPolicy.h" michael@0: #include "nsContentPolicyUtils.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMHTMLElement.h" michael@0: #include "nsError.h" michael@0: #include "nsURILoader.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIMarkupDocumentViewer.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include michael@0: michael@0: #define AUTOMATIC_IMAGE_RESIZING_PREF "browser.enable_automatic_image_resizing" michael@0: #define CLICK_IMAGE_RESIZING_PREF "browser.enable_click_image_resizing" michael@0: //XXX A hack needed for Firefox's site specific zoom. michael@0: #define SITE_SPECIFIC_ZOOM "browser.zoom.siteSpecific" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: class ImageListener : public MediaDocumentStreamListener michael@0: { michael@0: public: michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: michael@0: ImageListener(ImageDocument* aDocument); michael@0: virtual ~ImageListener(); michael@0: }; michael@0: michael@0: ImageListener::ImageListener(ImageDocument* aDocument) michael@0: : MediaDocumentStreamListener(aDocument) michael@0: { michael@0: } michael@0: michael@0: ImageListener::~ImageListener() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt) michael@0: { michael@0: NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); michael@0: michael@0: ImageDocument *imgDoc = static_cast(mDocument.get()); michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: if (!channel) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr domWindow = imgDoc->GetWindow(); michael@0: NS_ENSURE_TRUE(domWindow, NS_ERROR_UNEXPECTED); michael@0: michael@0: // Do a ShouldProcess check to see whether to keep loading the image. michael@0: nsCOMPtr channelURI; michael@0: channel->GetURI(getter_AddRefs(channelURI)); michael@0: michael@0: nsAutoCString mimeType; michael@0: channel->GetContentType(mimeType); michael@0: michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: nsCOMPtr channelPrincipal; michael@0: if (secMan) { michael@0: secMan->GetChannelPrincipal(channel, getter_AddRefs(channelPrincipal)); michael@0: } michael@0: michael@0: int16_t decision = nsIContentPolicy::ACCEPT; michael@0: nsresult rv = NS_CheckContentProcessPolicy(nsIContentPolicy::TYPE_IMAGE, michael@0: channelURI, michael@0: channelPrincipal, michael@0: domWindow->GetFrameElementInternal(), michael@0: mimeType, michael@0: nullptr, michael@0: &decision, michael@0: nsContentUtils::GetContentPolicy(), michael@0: secMan); michael@0: michael@0: if (NS_FAILED(rv) || NS_CP_REJECTED(decision)) { michael@0: request->Cancel(NS_ERROR_CONTENT_BLOCKED); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr imageLoader = do_QueryInterface(imgDoc->mImageContent); michael@0: NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED); michael@0: michael@0: imageLoader->AddObserver(imgDoc); michael@0: imgDoc->mObservingImageLoader = true; michael@0: imageLoader->LoadImageWithChannel(channel, getter_AddRefs(mNextStream)); michael@0: michael@0: return MediaDocumentStreamListener::OnStartRequest(request, ctxt); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt, nsresult aStatus) michael@0: { michael@0: ImageDocument* imgDoc = static_cast(mDocument.get()); michael@0: nsContentUtils::DispatchChromeEvent(imgDoc, static_cast(imgDoc), michael@0: NS_LITERAL_STRING("ImageContentLoaded"), michael@0: true, true); michael@0: return MediaDocumentStreamListener::OnStopRequest(aRequest, aCtxt, aStatus); michael@0: } michael@0: michael@0: ImageDocument::ImageDocument() michael@0: : MediaDocument(), michael@0: mOriginalZoomLevel(1.0) michael@0: { michael@0: // NOTE! nsDocument::operator new() zeroes out all members, so don't michael@0: // bother initializing members to 0. michael@0: } michael@0: michael@0: ImageDocument::~ImageDocument() michael@0: { michael@0: } michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageDocument, MediaDocument, michael@0: mImageContent) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(ImageDocument, MediaDocument) michael@0: NS_IMPL_RELEASE_INHERITED(ImageDocument, MediaDocument) michael@0: michael@0: NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(ImageDocument) michael@0: NS_INTERFACE_TABLE_INHERITED(ImageDocument, nsIImageDocument, michael@0: imgINotificationObserver, nsIDOMEventListener) michael@0: NS_INTERFACE_TABLE_TAIL_INHERITING(MediaDocument) michael@0: michael@0: michael@0: nsresult michael@0: ImageDocument::Init() michael@0: { michael@0: nsresult rv = MediaDocument::Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mResizeImageByDefault = Preferences::GetBool(AUTOMATIC_IMAGE_RESIZING_PREF); michael@0: mClickResizingEnabled = Preferences::GetBool(CLICK_IMAGE_RESIZING_PREF); michael@0: mShouldResize = mResizeImageByDefault; michael@0: mFirstResize = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: JSObject* michael@0: ImageDocument::WrapNode(JSContext* aCx) michael@0: { michael@0: return ImageDocumentBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: nsresult michael@0: ImageDocument::StartDocumentLoad(const char* aCommand, michael@0: nsIChannel* aChannel, michael@0: nsILoadGroup* aLoadGroup, michael@0: nsISupports* aContainer, michael@0: nsIStreamListener** aDocListener, michael@0: bool aReset, michael@0: nsIContentSink* aSink) michael@0: { michael@0: nsresult rv = michael@0: MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer, michael@0: aDocListener, aReset, aSink); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: mOriginalZoomLevel = michael@0: Preferences::GetBool(SITE_SPECIFIC_ZOOM, false) ? 1.0 : GetZoomLevel(); michael@0: michael@0: NS_ASSERTION(aDocListener, "null aDocListener"); michael@0: *aDocListener = new ImageListener(this); michael@0: NS_ADDREF(*aDocListener); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ImageDocument::Destroy() michael@0: { michael@0: if (mImageContent) { michael@0: // Remove our event listener from the image content. michael@0: nsCOMPtr target = do_QueryInterface(mImageContent); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("click"), this, false); michael@0: michael@0: // Break reference cycle with mImageContent, if we have one michael@0: if (mObservingImageLoader) { michael@0: nsCOMPtr imageLoader = do_QueryInterface(mImageContent); michael@0: if (imageLoader) { michael@0: imageLoader->RemoveObserver(this); michael@0: } michael@0: } michael@0: michael@0: mImageContent = nullptr; michael@0: } michael@0: michael@0: MediaDocument::Destroy(); michael@0: } michael@0: michael@0: void michael@0: ImageDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject) michael@0: { michael@0: // If the script global object is changing, we need to unhook our event michael@0: // listeners on the window. michael@0: nsCOMPtr target; michael@0: if (mScriptGlobalObject && michael@0: aScriptGlobalObject != mScriptGlobalObject) { michael@0: target = do_QueryInterface(mScriptGlobalObject); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("resize"), this, false); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, michael@0: false); michael@0: } michael@0: michael@0: // Set the script global object on the superclass before doing michael@0: // anything that might require it.... michael@0: MediaDocument::SetScriptGlobalObject(aScriptGlobalObject); michael@0: michael@0: if (aScriptGlobalObject) { michael@0: if (!GetRootElement()) { michael@0: // Create synthetic document michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: CreateSyntheticDocument(); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document"); michael@0: michael@0: target = do_QueryInterface(mImageContent); michael@0: target->AddEventListener(NS_LITERAL_STRING("load"), this, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("click"), this, false); michael@0: } michael@0: michael@0: target = do_QueryInterface(aScriptGlobalObject); michael@0: target->AddEventListener(NS_LITERAL_STRING("resize"), this, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("keypress"), this, false); michael@0: michael@0: if (GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) { michael@0: LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/ImageDocument.css")); michael@0: if (!nsContentUtils::IsChildOfSameType(this)) { michael@0: LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/TopLevelImageDocument.css")); michael@0: LinkStylesheet(NS_LITERAL_STRING("chrome://global/skin/media/TopLevelImageDocument.css")); michael@0: } michael@0: } michael@0: BecomeInteractive(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ImageDocument::OnPageShow(bool aPersisted, michael@0: EventTarget* aDispatchStartTarget) michael@0: { michael@0: if (aPersisted) { michael@0: mOriginalZoomLevel = michael@0: Preferences::GetBool(SITE_SPECIFIC_ZOOM, false) ? 1.0 : GetZoomLevel(); michael@0: } michael@0: MediaDocument::OnPageShow(aPersisted, aDispatchStartTarget); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::GetImageResizingEnabled(bool* aImageResizingEnabled) michael@0: { michael@0: *aImageResizingEnabled = ImageResizingEnabled(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::GetImageIsOverflowing(bool* aImageIsOverflowing) michael@0: { michael@0: *aImageIsOverflowing = ImageIsOverflowing(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::GetImageIsResized(bool* aImageIsResized) michael@0: { michael@0: *aImageIsResized = ImageIsResized(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: ImageDocument::GetImageRequest(ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr imageLoader = do_QueryInterface(mImageContent); michael@0: nsCOMPtr imageRequest; michael@0: if (imageLoader) { michael@0: aRv = imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(imageRequest)); michael@0: } michael@0: return imageRequest.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::GetImageRequest(imgIRequest** aImageRequest) michael@0: { michael@0: ErrorResult rv; michael@0: *aImageRequest = GetImageRequest(rv).take(); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: ImageDocument::ShrinkToFit() michael@0: { michael@0: if (!mImageContent) { michael@0: return; michael@0: } michael@0: if (GetZoomLevel() != mOriginalZoomLevel && mImageIsResized && michael@0: !nsContentUtils::IsChildOfSameType(this)) { michael@0: return; michael@0: } michael@0: michael@0: // Keep image content alive while changing the attributes. michael@0: nsCOMPtr imageContent = mImageContent; michael@0: nsCOMPtr image = do_QueryInterface(mImageContent); michael@0: image->SetWidth(std::max(1, NSToCoordFloor(GetRatio() * mImageWidth))); michael@0: image->SetHeight(std::max(1, NSToCoordFloor(GetRatio() * mImageHeight))); michael@0: michael@0: // The view might have been scrolled when zooming in, scroll back to the michael@0: // origin now that we're showing a shrunk-to-window version. michael@0: ScrollImageTo(0, 0, false); michael@0: michael@0: if (!mImageContent) { michael@0: // ScrollImageTo flush destroyed our content. michael@0: return; michael@0: } michael@0: michael@0: SetModeClass(eShrinkToFit); michael@0: michael@0: mImageIsResized = true; michael@0: michael@0: UpdateTitleAndCharset(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::DOMShrinkToFit() michael@0: { michael@0: ShrinkToFit(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::DOMRestoreImageTo(int32_t aX, int32_t aY) michael@0: { michael@0: RestoreImageTo(aX, aY); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ImageDocument::ScrollImageTo(int32_t aX, int32_t aY, bool restoreImage) michael@0: { michael@0: float ratio = GetRatio(); michael@0: michael@0: if (restoreImage) { michael@0: RestoreImage(); michael@0: FlushPendingNotifications(Flush_Layout); michael@0: } michael@0: michael@0: nsCOMPtr shell = GetShell(); michael@0: if (!shell) michael@0: return; michael@0: michael@0: nsIScrollableFrame* sf = shell->GetRootScrollFrameAsScrollable(); michael@0: if (!sf) michael@0: return; michael@0: michael@0: nsRect portRect = sf->GetScrollPortRect(); michael@0: sf->ScrollTo(nsPoint(nsPresContext::CSSPixelsToAppUnits(aX/ratio) - portRect.width/2, michael@0: nsPresContext::CSSPixelsToAppUnits(aY/ratio) - portRect.height/2), michael@0: nsIScrollableFrame::INSTANT); michael@0: } michael@0: michael@0: void michael@0: ImageDocument::RestoreImage() michael@0: { michael@0: if (!mImageContent) { michael@0: return; michael@0: } michael@0: // Keep image content alive while changing the attributes. michael@0: nsCOMPtr imageContent = mImageContent; michael@0: imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::width, true); michael@0: imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::height, true); michael@0: michael@0: if (mImageIsOverflowing) { michael@0: SetModeClass(eOverflowing); michael@0: } michael@0: else { michael@0: SetModeClass(eNone); michael@0: } michael@0: michael@0: mImageIsResized = false; michael@0: michael@0: UpdateTitleAndCharset(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::DOMRestoreImage() michael@0: { michael@0: RestoreImage(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ImageDocument::ToggleImageSize() michael@0: { michael@0: mShouldResize = true; michael@0: if (mImageIsResized) { michael@0: mShouldResize = false; michael@0: ResetZoomLevel(); michael@0: RestoreImage(); michael@0: } michael@0: else if (mImageIsOverflowing) { michael@0: ResetZoomLevel(); michael@0: ShrinkToFit(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::DOMToggleImageSize() michael@0: { michael@0: ToggleImageSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData) michael@0: { michael@0: if (aType == imgINotificationObserver::SIZE_AVAILABLE) { michael@0: nsCOMPtr image; michael@0: aRequest->GetImage(getter_AddRefs(image)); michael@0: return OnStartContainer(aRequest, image); michael@0: } michael@0: michael@0: nsDOMTokenList* classList = mImageContent->AsElement()->GetClassList(); michael@0: mozilla::ErrorResult rv; michael@0: if (aType == imgINotificationObserver::DECODE_COMPLETE) { michael@0: if (mImageContent && !nsContentUtils::IsChildOfSameType(this)) { michael@0: // Update the background-color of the image only after the michael@0: // image has been decoded to prevent flashes of just the michael@0: // background-color. michael@0: classList->Add(NS_LITERAL_STRING("decoded"), rv); michael@0: NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode()); michael@0: } michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::DISCARD) { michael@0: // mImageContent can be null if the document is already destroyed michael@0: if (mImageContent && !nsContentUtils::IsChildOfSameType(this)) { michael@0: // Remove any decoded-related styling when the image is unloaded. michael@0: classList->Remove(NS_LITERAL_STRING("decoded"), rv); michael@0: NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode()); michael@0: } michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::LOAD_COMPLETE) { michael@0: uint32_t reqStatus; michael@0: aRequest->GetImageStatus(&reqStatus); michael@0: nsresult status = michael@0: reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; michael@0: return OnStopRequest(aRequest, status); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ImageDocument::SetModeClass(eModeClasses mode) michael@0: { michael@0: nsDOMTokenList* classList = mImageContent->AsElement()->GetClassList(); michael@0: mozilla::ErrorResult rv; michael@0: michael@0: if (mode == eShrinkToFit) { michael@0: classList->Add(NS_LITERAL_STRING("shrinkToFit"), rv); michael@0: } else { michael@0: classList->Remove(NS_LITERAL_STRING("shrinkToFit"), rv); michael@0: } michael@0: michael@0: if (mode == eOverflowing) { michael@0: classList->Add(NS_LITERAL_STRING("overflowing"), rv); michael@0: } else { michael@0: classList->Remove(NS_LITERAL_STRING("overflowing"), rv); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: ImageDocument::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage) michael@0: { michael@0: // Styles have not yet been applied, so we don't know the final size. For now, michael@0: // default to the image's intrinsic size. michael@0: aImage->GetWidth(&mImageWidth); michael@0: aImage->GetHeight(&mImageHeight); michael@0: michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &ImageDocument::DefaultCheckOverflowing); michael@0: nsContentUtils::AddScriptRunner(runnable); michael@0: UpdateTitleAndCharset(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ImageDocument::OnStopRequest(imgIRequest *aRequest, michael@0: nsresult aStatus) michael@0: { michael@0: UpdateTitleAndCharset(); michael@0: michael@0: // mImageContent can be null if the document is already destroyed michael@0: if (NS_FAILED(aStatus) && mStringBundle && mImageContent) { michael@0: nsAutoCString src; michael@0: mDocumentURI->GetSpec(src); michael@0: NS_ConvertUTF8toUTF16 srcString(src); michael@0: const char16_t* formatString[] = { srcString.get() }; michael@0: nsXPIDLString errorMsg; michael@0: NS_NAMED_LITERAL_STRING(str, "InvalidImage"); michael@0: mStringBundle->FormatStringFromName(str.get(), formatString, 1, michael@0: getter_Copies(errorMsg)); michael@0: michael@0: mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageDocument::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: if (eventType.EqualsLiteral("resize")) { michael@0: CheckOverflowing(false); michael@0: } michael@0: else if (eventType.EqualsLiteral("click") && mClickResizingEnabled) { michael@0: ResetZoomLevel(); michael@0: mShouldResize = true; michael@0: if (mImageIsResized) { michael@0: int32_t x = 0, y = 0; michael@0: nsCOMPtr event(do_QueryInterface(aEvent)); michael@0: if (event) { michael@0: event->GetClientX(&x); michael@0: event->GetClientY(&y); michael@0: int32_t left = 0, top = 0; michael@0: nsCOMPtr htmlElement = michael@0: do_QueryInterface(mImageContent); michael@0: htmlElement->GetOffsetLeft(&left); michael@0: htmlElement->GetOffsetTop(&top); michael@0: x -= left; michael@0: y -= top; michael@0: } michael@0: mShouldResize = false; michael@0: RestoreImageTo(x, y); michael@0: } michael@0: else if (mImageIsOverflowing) { michael@0: ShrinkToFit(); michael@0: } michael@0: } else if (eventType.EqualsLiteral("load")) { michael@0: UpdateSizeFromLayout(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ImageDocument::UpdateSizeFromLayout() michael@0: { michael@0: // Pull an updated size from the content frame to account for any size michael@0: // change due to CSS properties like |image-orientation|. michael@0: Element* contentElement = mImageContent->AsElement(); michael@0: if (!contentElement) { michael@0: return; michael@0: } michael@0: michael@0: nsIFrame* contentFrame = contentElement->GetPrimaryFrame(Flush_Frames); michael@0: if (!contentFrame) { michael@0: return; michael@0: } michael@0: michael@0: nsIntSize oldSize(mImageWidth, mImageHeight); michael@0: IntrinsicSize newSize = contentFrame->GetIntrinsicSize(); michael@0: michael@0: if (newSize.width.GetUnit() == eStyleUnit_Coord) { michael@0: mImageWidth = nsPresContext::AppUnitsToFloatCSSPixels(newSize.width.GetCoordValue()); michael@0: } michael@0: if (newSize.height.GetUnit() == eStyleUnit_Coord) { michael@0: mImageHeight = nsPresContext::AppUnitsToFloatCSSPixels(newSize.height.GetCoordValue()); michael@0: } michael@0: michael@0: // Ensure that our information about overflow is up-to-date if needed. michael@0: if (mImageWidth != oldSize.width || mImageHeight != oldSize.height) { michael@0: CheckOverflowing(false); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: ImageDocument::CreateSyntheticDocument() michael@0: { michael@0: // Synthesize an html document that refers to the image michael@0: nsresult rv = MediaDocument::CreateSyntheticDocument(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add the image element michael@0: Element* body = GetBodyElement(); michael@0: if (!body) { michael@0: NS_WARNING("no body on image document!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr nodeInfo; michael@0: nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::img, nullptr, michael@0: kNameSpaceID_XHTML, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: michael@0: mImageContent = NS_NewHTMLImageElement(nodeInfo.forget()); michael@0: if (!mImageContent) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: nsCOMPtr imageLoader = do_QueryInterface(mImageContent); michael@0: NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsAutoCString src; michael@0: mDocumentURI->GetSpec(src); michael@0: michael@0: NS_ConvertUTF8toUTF16 srcString(src); michael@0: // Make sure not to start the image load from here... michael@0: imageLoader->SetLoadingEnabled(false); michael@0: mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::src, srcString, false); michael@0: mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, srcString, false); michael@0: michael@0: body->AppendChildTo(mImageContent, false); michael@0: imageLoader->SetLoadingEnabled(true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ImageDocument::CheckOverflowing(bool changeState) michael@0: { michael@0: /* Create a scope so that the style context gets destroyed before we might michael@0: * call RebuildStyleData. Also, holding onto pointers to the michael@0: * presentation through style resolution is potentially dangerous. michael@0: */ michael@0: { michael@0: nsIPresShell *shell = GetShell(); michael@0: if (!shell) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsPresContext *context = shell->GetPresContext(); michael@0: nsRect visibleArea = context->GetVisibleArea(); michael@0: michael@0: mVisibleWidth = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.width); michael@0: mVisibleHeight = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.height); michael@0: } michael@0: michael@0: bool imageWasOverflowing = mImageIsOverflowing; michael@0: mImageIsOverflowing = michael@0: mImageWidth > mVisibleWidth || mImageHeight > mVisibleHeight; michael@0: bool windowBecameBigEnough = imageWasOverflowing && !mImageIsOverflowing; michael@0: michael@0: if (changeState || mShouldResize || mFirstResize || michael@0: windowBecameBigEnough) { michael@0: if (mImageIsOverflowing && (changeState || mShouldResize)) { michael@0: ShrinkToFit(); michael@0: } michael@0: else if (mImageIsResized || mFirstResize || windowBecameBigEnough) { michael@0: RestoreImage(); michael@0: } michael@0: } michael@0: mFirstResize = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ImageDocument::UpdateTitleAndCharset() michael@0: { michael@0: nsAutoCString typeStr; michael@0: nsCOMPtr imageRequest; michael@0: nsCOMPtr imageLoader = do_QueryInterface(mImageContent); michael@0: if (imageLoader) { michael@0: imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(imageRequest)); michael@0: } michael@0: michael@0: if (imageRequest) { michael@0: nsXPIDLCString mimeType; michael@0: imageRequest->GetMimeType(getter_Copies(mimeType)); michael@0: ToUpperCase(mimeType); michael@0: nsXPIDLCString::const_iterator start, end; michael@0: mimeType.BeginReading(start); michael@0: mimeType.EndReading(end); michael@0: nsXPIDLCString::const_iterator iter = end; michael@0: if (FindInReadable(NS_LITERAL_CSTRING("IMAGE/"), start, iter) && michael@0: iter != end) { michael@0: // strip out "X-" if any michael@0: if (*iter == 'X') { michael@0: ++iter; michael@0: if (iter != end && *iter == '-') { michael@0: ++iter; michael@0: if (iter == end) { michael@0: // looks like "IMAGE/X-" is the type?? Bail out of here. michael@0: mimeType.BeginReading(iter); michael@0: } michael@0: } else { michael@0: --iter; michael@0: } michael@0: } michael@0: typeStr = Substring(iter, end); michael@0: } else { michael@0: typeStr = mimeType; michael@0: } michael@0: } michael@0: michael@0: nsXPIDLString status; michael@0: if (mImageIsResized) { michael@0: nsAutoString ratioStr; michael@0: ratioStr.AppendInt(NSToCoordFloor(GetRatio() * 100)); michael@0: michael@0: const char16_t* formatString[1] = { ratioStr.get() }; michael@0: mStringBundle->FormatStringFromName(MOZ_UTF16("ScaledImage"), michael@0: formatString, 1, michael@0: getter_Copies(status)); michael@0: } michael@0: michael@0: static const char* const formatNames[4] = michael@0: { michael@0: "ImageTitleWithNeitherDimensionsNorFile", michael@0: "ImageTitleWithoutDimensions", michael@0: "ImageTitleWithDimensions2", michael@0: "ImageTitleWithDimensions2AndFile", michael@0: }; michael@0: michael@0: MediaDocument::UpdateTitleAndCharset(typeStr, formatNames, michael@0: mImageWidth, mImageHeight, status); michael@0: } michael@0: michael@0: void michael@0: ImageDocument::ResetZoomLevel() michael@0: { michael@0: nsCOMPtr docShell(mDocumentContainer); michael@0: if (docShell) { michael@0: if (nsContentUtils::IsChildOfSameType(this)) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr cv; michael@0: docShell->GetContentViewer(getter_AddRefs(cv)); michael@0: nsCOMPtr mdv = do_QueryInterface(cv); michael@0: if (mdv) { michael@0: mdv->SetFullZoom(mOriginalZoomLevel); michael@0: } michael@0: } michael@0: } michael@0: michael@0: float michael@0: ImageDocument::GetZoomLevel() michael@0: { michael@0: float zoomLevel = mOriginalZoomLevel; michael@0: nsCOMPtr docShell(mDocumentContainer); michael@0: if (docShell) { michael@0: nsCOMPtr cv; michael@0: docShell->GetContentViewer(getter_AddRefs(cv)); michael@0: nsCOMPtr mdv = do_QueryInterface(cv); michael@0: if (mdv) { michael@0: mdv->GetFullZoom(&zoomLevel); michael@0: } michael@0: } michael@0: return zoomLevel; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla michael@0: michael@0: nsresult michael@0: NS_NewImageDocument(nsIDocument** aResult) michael@0: { michael@0: mozilla::dom::ImageDocument* doc = new mozilla::dom::ImageDocument(); michael@0: NS_ADDREF(doc); michael@0: michael@0: nsresult rv = doc->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(doc); michael@0: } michael@0: michael@0: *aResult = doc; michael@0: michael@0: return rv; michael@0: }