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