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