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