1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Morten Nilsen <morten@nilsen.com>
24 : * Christian Biesinger <cbiesinger@web.de>
25 : * Jan Varga <varga@ku.sk>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "MediaDocument.h"
42 : #include "nsRect.h"
43 : #include "nsHTMLDocument.h"
44 : #include "nsIImageDocument.h"
45 : #include "nsIImageLoadingContent.h"
46 : #include "nsGenericHTMLElement.h"
47 : #include "nsIDOMHTMLImageElement.h"
48 : #include "nsIDOMEvent.h"
49 : #include "nsIDOMKeyEvent.h"
50 : #include "nsIDOMMouseEvent.h"
51 : #include "nsIDOMEventListener.h"
52 : #include "nsGkAtoms.h"
53 : #include "imgIRequest.h"
54 : #include "imgILoader.h"
55 : #include "imgIContainer.h"
56 : #include "nsStubImageDecoderObserver.h"
57 : #include "nsIPresShell.h"
58 : #include "nsPresContext.h"
59 : #include "nsStyleContext.h"
60 : #include "nsAutoPtr.h"
61 : #include "nsStyleSet.h"
62 : #include "nsIChannel.h"
63 : #include "nsIContentPolicy.h"
64 : #include "nsContentPolicyUtils.h"
65 : #include "nsPIDOMWindow.h"
66 : #include "nsIDOMElement.h"
67 : #include "nsIDOMHTMLElement.h"
68 : #include "nsContentErrors.h"
69 : #include "nsURILoader.h"
70 : #include "nsIDocShell.h"
71 : #include "nsIContentViewer.h"
72 : #include "nsIMarkupDocumentViewer.h"
73 : #include "nsIDocShellTreeItem.h"
74 : #include "nsThreadUtils.h"
75 : #include "nsIScrollableFrame.h"
76 : #include "nsContentUtils.h"
77 : #include "mozilla/dom/Element.h"
78 : #include "mozilla/Preferences.h"
79 :
80 : #define AUTOMATIC_IMAGE_RESIZING_PREF "browser.enable_automatic_image_resizing"
81 : #define CLICK_IMAGE_RESIZING_PREF "browser.enable_click_image_resizing"
82 : //XXX A hack needed for Firefox's site specific zoom.
83 : #define SITE_SPECIFIC_ZOOM "browser.zoom.siteSpecific"
84 :
85 : namespace mozilla {
86 : namespace dom {
87 :
88 : class ImageDocument;
89 :
90 : class ImageListener : public MediaDocumentStreamListener
91 : {
92 : public:
93 : ImageListener(ImageDocument* aDocument);
94 : virtual ~ImageListener();
95 :
96 : /* nsIRequestObserver */
97 : NS_IMETHOD OnStartRequest(nsIRequest* request, nsISupports *ctxt);
98 : };
99 :
100 : class ImageDocument : public MediaDocument
101 : , public nsIImageDocument
102 : , public nsStubImageDecoderObserver
103 : , public nsIDOMEventListener
104 : {
105 : public:
106 : ImageDocument();
107 : virtual ~ImageDocument();
108 :
109 : NS_DECL_ISUPPORTS_INHERITED
110 :
111 : virtual nsresult Init();
112 :
113 : virtual nsresult StartDocumentLoad(const char* aCommand,
114 : nsIChannel* aChannel,
115 : nsILoadGroup* aLoadGroup,
116 : nsISupports* aContainer,
117 : nsIStreamListener** aDocListener,
118 : bool aReset = true,
119 : nsIContentSink* aSink = nsnull);
120 :
121 : virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject);
122 : virtual void Destroy();
123 : virtual void OnPageShow(bool aPersisted,
124 : nsIDOMEventTarget* aDispatchStartTarget);
125 :
126 : NS_DECL_NSIIMAGEDOCUMENT
127 :
128 : // imgIDecoderObserver (override nsStubImageDecoderObserver)
129 : NS_IMETHOD OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage);
130 : NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult aStatus, const PRUnichar *aStatusArg);
131 :
132 : // nsIDOMEventListener
133 : NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
134 :
135 1464 : NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ImageDocument, MediaDocument)
136 :
137 : friend class ImageListener;
138 :
139 0 : void DefaultCheckOverflowing() { CheckOverflowing(mResizeImageByDefault); }
140 :
141 : virtual nsXPCClassInfo* GetClassInfo();
142 : protected:
143 : virtual nsresult CreateSyntheticDocument();
144 :
145 : nsresult CheckOverflowing(bool changeState);
146 :
147 : void UpdateTitleAndCharset();
148 :
149 : nsresult ScrollImageTo(PRInt32 aX, PRInt32 aY, bool restoreImage);
150 :
151 0 : float GetRatio() {
152 : return NS_MIN((float)mVisibleWidth / mImageWidth,
153 0 : (float)mVisibleHeight / mImageHeight);
154 : }
155 :
156 : void ResetZoomLevel();
157 : float GetZoomLevel();
158 :
159 : nsCOMPtr<nsIContent> mImageContent;
160 :
161 : PRInt32 mVisibleWidth;
162 : PRInt32 mVisibleHeight;
163 : PRInt32 mImageWidth;
164 : PRInt32 mImageHeight;
165 :
166 : bool mResizeImageByDefault;
167 : bool mClickResizingEnabled;
168 : bool mImageIsOverflowing;
169 : // mImageIsResized is true if the image is currently resized
170 : bool mImageIsResized;
171 : // mShouldResize is true if the image should be resized when it doesn't fit
172 : // mImageIsResized cannot be true when this is false, but mImageIsResized
173 : // can be false when this is true
174 : bool mShouldResize;
175 : bool mFirstResize;
176 : // mObservingImageLoader is true while the observer is set.
177 : bool mObservingImageLoader;
178 :
179 : float mOriginalZoomLevel;
180 : };
181 :
182 0 : ImageListener::ImageListener(ImageDocument* aDocument)
183 0 : : MediaDocumentStreamListener(aDocument)
184 : {
185 0 : }
186 :
187 0 : ImageListener::~ImageListener()
188 : {
189 0 : }
190 :
191 : NS_IMETHODIMP
192 0 : ImageListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
193 : {
194 0 : NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
195 :
196 0 : ImageDocument *imgDoc = static_cast<ImageDocument*>(mDocument.get());
197 0 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
198 0 : if (!channel) {
199 0 : return NS_ERROR_FAILURE;
200 : }
201 :
202 : nsCOMPtr<nsPIDOMWindow> domWindow =
203 0 : do_QueryInterface(imgDoc->GetScriptGlobalObject());
204 0 : NS_ENSURE_TRUE(domWindow, NS_ERROR_UNEXPECTED);
205 :
206 : // Do a ShouldProcess check to see whether to keep loading the image.
207 0 : nsCOMPtr<nsIURI> channelURI;
208 0 : channel->GetURI(getter_AddRefs(channelURI));
209 :
210 0 : nsCAutoString mimeType;
211 0 : channel->GetContentType(mimeType);
212 :
213 0 : nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
214 0 : nsCOMPtr<nsIPrincipal> channelPrincipal;
215 0 : if (secMan) {
216 0 : secMan->GetChannelPrincipal(channel, getter_AddRefs(channelPrincipal));
217 : }
218 :
219 0 : PRInt16 decision = nsIContentPolicy::ACCEPT;
220 : nsresult rv = NS_CheckContentProcessPolicy(nsIContentPolicy::TYPE_IMAGE,
221 : channelURI,
222 : channelPrincipal,
223 0 : domWindow->GetFrameElementInternal(),
224 : mimeType,
225 : nsnull,
226 : &decision,
227 : nsContentUtils::GetContentPolicy(),
228 0 : secMan);
229 :
230 0 : if (NS_FAILED(rv) || NS_CP_REJECTED(decision)) {
231 0 : request->Cancel(NS_ERROR_CONTENT_BLOCKED);
232 0 : return NS_OK;
233 : }
234 :
235 0 : nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(imgDoc->mImageContent);
236 0 : NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
237 :
238 0 : imageLoader->AddObserver(imgDoc);
239 0 : imgDoc->mObservingImageLoader = true;
240 0 : imageLoader->LoadImageWithChannel(channel, getter_AddRefs(mNextStream));
241 :
242 0 : return MediaDocumentStreamListener::OnStartRequest(request, ctxt);
243 : }
244 :
245 0 : ImageDocument::ImageDocument()
246 0 : : mOriginalZoomLevel(1.0)
247 : {
248 : // NOTE! nsDocument::operator new() zeroes out all members, so don't
249 : // bother initializing members to 0.
250 0 : }
251 :
252 0 : ImageDocument::~ImageDocument()
253 : {
254 0 : }
255 :
256 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(ImageDocument)
257 :
258 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ImageDocument, MediaDocument)
259 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mImageContent)
260 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
261 :
262 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ImageDocument, MediaDocument)
263 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mImageContent)
264 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
265 :
266 0 : NS_IMPL_ADDREF_INHERITED(ImageDocument, MediaDocument)
267 0 : NS_IMPL_RELEASE_INHERITED(ImageDocument, MediaDocument)
268 :
269 0 : DOMCI_NODE_DATA(ImageDocument, ImageDocument)
270 :
271 0 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(ImageDocument)
272 : NS_HTML_DOCUMENT_INTERFACE_TABLE_BEGIN(ImageDocument)
273 : NS_INTERFACE_TABLE_ENTRY(ImageDocument, nsIImageDocument)
274 : NS_INTERFACE_TABLE_ENTRY(ImageDocument, imgIDecoderObserver)
275 : NS_INTERFACE_TABLE_ENTRY(ImageDocument, imgIContainerObserver)
276 : NS_INTERFACE_TABLE_ENTRY(ImageDocument, nsIDOMEventListener)
277 0 : NS_OFFSET_AND_INTERFACE_TABLE_END
278 0 : NS_OFFSET_AND_INTERFACE_TABLE_TO_MAP_SEGUE
279 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ImageDocument)
280 0 : NS_INTERFACE_MAP_END_INHERITING(MediaDocument)
281 :
282 :
283 : nsresult
284 0 : ImageDocument::Init()
285 : {
286 0 : nsresult rv = MediaDocument::Init();
287 0 : NS_ENSURE_SUCCESS(rv, rv);
288 :
289 0 : mResizeImageByDefault = Preferences::GetBool(AUTOMATIC_IMAGE_RESIZING_PREF);
290 0 : mClickResizingEnabled = Preferences::GetBool(CLICK_IMAGE_RESIZING_PREF);
291 0 : mShouldResize = mResizeImageByDefault;
292 0 : mFirstResize = true;
293 :
294 0 : return NS_OK;
295 : }
296 :
297 : nsresult
298 0 : ImageDocument::StartDocumentLoad(const char* aCommand,
299 : nsIChannel* aChannel,
300 : nsILoadGroup* aLoadGroup,
301 : nsISupports* aContainer,
302 : nsIStreamListener** aDocListener,
303 : bool aReset,
304 : nsIContentSink* aSink)
305 : {
306 : nsresult rv =
307 : MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer,
308 0 : aDocListener, aReset, aSink);
309 0 : if (NS_FAILED(rv)) {
310 0 : return rv;
311 : }
312 :
313 : mOriginalZoomLevel =
314 0 : Preferences::GetBool(SITE_SPECIFIC_ZOOM, false) ? 1.0 : GetZoomLevel();
315 :
316 0 : NS_ASSERTION(aDocListener, "null aDocListener");
317 0 : *aDocListener = new ImageListener(this);
318 0 : NS_ADDREF(*aDocListener);
319 :
320 0 : return NS_OK;
321 : }
322 :
323 : void
324 0 : ImageDocument::Destroy()
325 : {
326 0 : if (mImageContent) {
327 : // Remove our event listener from the image content.
328 0 : nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(mImageContent);
329 0 : target->RemoveEventListener(NS_LITERAL_STRING("click"), this, false);
330 :
331 : // Break reference cycle with mImageContent, if we have one
332 0 : if (mObservingImageLoader) {
333 0 : nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
334 0 : if (imageLoader) {
335 : // Push a null JSContext on the stack so that code that
336 : // nsImageLoadingContent doesn't think it's being called by JS. See
337 : // Bug 631241
338 0 : nsCxPusher pusher;
339 0 : pusher.PushNull();
340 0 : imageLoader->RemoveObserver(this);
341 : }
342 : }
343 :
344 0 : mImageContent = nsnull;
345 : }
346 :
347 0 : MediaDocument::Destroy();
348 0 : }
349 :
350 : void
351 0 : ImageDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject)
352 : {
353 : // If the script global object is changing, we need to unhook our event
354 : // listeners on the window.
355 0 : nsCOMPtr<nsIDOMEventTarget> target;
356 0 : if (mScriptGlobalObject &&
357 0 : aScriptGlobalObject != mScriptGlobalObject) {
358 0 : target = do_QueryInterface(mScriptGlobalObject);
359 0 : target->RemoveEventListener(NS_LITERAL_STRING("resize"), this, false);
360 0 : target->RemoveEventListener(NS_LITERAL_STRING("keypress"), this,
361 0 : false);
362 : }
363 :
364 : // Set the script global object on the superclass before doing
365 : // anything that might require it....
366 0 : MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
367 :
368 0 : if (aScriptGlobalObject) {
369 0 : if (!GetRootElement()) {
370 : // Create synthetic document
371 : #ifdef DEBUG
372 : nsresult rv =
373 : #endif
374 0 : CreateSyntheticDocument();
375 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document");
376 :
377 0 : target = do_QueryInterface(mImageContent);
378 0 : target->AddEventListener(NS_LITERAL_STRING("click"), this, false);
379 : }
380 :
381 0 : target = do_QueryInterface(aScriptGlobalObject);
382 0 : target->AddEventListener(NS_LITERAL_STRING("resize"), this, false);
383 0 : target->AddEventListener(NS_LITERAL_STRING("keypress"), this, false);
384 :
385 0 : if (!nsContentUtils::IsChildOfSameType(this)) {
386 0 : LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/TopLevelImageDocument.css"));
387 : }
388 : }
389 0 : }
390 :
391 : void
392 0 : ImageDocument::OnPageShow(bool aPersisted,
393 : nsIDOMEventTarget* aDispatchStartTarget)
394 : {
395 0 : if (aPersisted) {
396 : mOriginalZoomLevel =
397 0 : Preferences::GetBool(SITE_SPECIFIC_ZOOM, false) ? 1.0 : GetZoomLevel();
398 : }
399 0 : MediaDocument::OnPageShow(aPersisted, aDispatchStartTarget);
400 0 : }
401 :
402 : NS_IMETHODIMP
403 0 : ImageDocument::GetImageResizingEnabled(bool* aImageResizingEnabled)
404 : {
405 0 : *aImageResizingEnabled = true;
406 0 : return NS_OK;
407 : }
408 :
409 : NS_IMETHODIMP
410 0 : ImageDocument::GetImageIsOverflowing(bool* aImageIsOverflowing)
411 : {
412 0 : *aImageIsOverflowing = mImageIsOverflowing;
413 0 : return NS_OK;
414 : }
415 :
416 : NS_IMETHODIMP
417 0 : ImageDocument::GetImageIsResized(bool* aImageIsResized)
418 : {
419 0 : *aImageIsResized = mImageIsResized;
420 0 : return NS_OK;
421 : }
422 :
423 : NS_IMETHODIMP
424 0 : ImageDocument::GetImageRequest(imgIRequest** aImageRequest)
425 : {
426 0 : nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
427 0 : if (imageLoader) {
428 0 : return imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
429 0 : aImageRequest);
430 : }
431 :
432 0 : *aImageRequest = nsnull;
433 0 : return NS_OK;
434 : }
435 :
436 : NS_IMETHODIMP
437 0 : ImageDocument::ShrinkToFit()
438 : {
439 0 : if (!mImageContent) {
440 0 : return NS_OK;
441 : }
442 0 : if (GetZoomLevel() != mOriginalZoomLevel && mImageIsResized &&
443 0 : !nsContentUtils::IsChildOfSameType(this)) {
444 0 : return NS_OK;
445 : }
446 :
447 : // Keep image content alive while changing the attributes.
448 0 : nsCOMPtr<nsIContent> imageContent = mImageContent;
449 0 : nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(mImageContent);
450 0 : image->SetWidth(NS_MAX(1, NSToCoordFloor(GetRatio() * mImageWidth)));
451 0 : image->SetHeight(NS_MAX(1, NSToCoordFloor(GetRatio() * mImageHeight)));
452 :
453 : // The view might have been scrolled when zooming in, scroll back to the
454 : // origin now that we're showing a shrunk-to-window version.
455 0 : (void) ScrollImageTo(0, 0, false);
456 :
457 : imageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
458 0 : NS_LITERAL_STRING("cursor: -moz-zoom-in"), true);
459 :
460 0 : mImageIsResized = true;
461 :
462 0 : UpdateTitleAndCharset();
463 :
464 0 : return NS_OK;
465 : }
466 :
467 : NS_IMETHODIMP
468 0 : ImageDocument::RestoreImageTo(PRInt32 aX, PRInt32 aY)
469 : {
470 0 : return ScrollImageTo(aX, aY, true);
471 : }
472 :
473 : nsresult
474 0 : ImageDocument::ScrollImageTo(PRInt32 aX, PRInt32 aY, bool restoreImage)
475 : {
476 0 : float ratio = GetRatio();
477 :
478 0 : if (restoreImage) {
479 0 : RestoreImage();
480 0 : FlushPendingNotifications(Flush_Layout);
481 : }
482 :
483 0 : nsIPresShell *shell = GetShell();
484 0 : if (!shell)
485 0 : return NS_OK;
486 :
487 0 : nsIScrollableFrame* sf = shell->GetRootScrollFrameAsScrollable();
488 0 : if (!sf)
489 0 : return NS_OK;
490 :
491 0 : nsRect portRect = sf->GetScrollPortRect();
492 0 : sf->ScrollTo(nsPoint(nsPresContext::CSSPixelsToAppUnits(aX/ratio) - portRect.width/2,
493 0 : nsPresContext::CSSPixelsToAppUnits(aY/ratio) - portRect.height/2),
494 0 : nsIScrollableFrame::INSTANT);
495 0 : return NS_OK;
496 : }
497 :
498 : NS_IMETHODIMP
499 0 : ImageDocument::RestoreImage()
500 : {
501 0 : if (!mImageContent) {
502 0 : return NS_OK;
503 : }
504 : // Keep image content alive while changing the attributes.
505 0 : nsCOMPtr<nsIContent> imageContent = mImageContent;
506 0 : imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::width, true);
507 0 : imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::height, true);
508 :
509 0 : if (mImageIsOverflowing) {
510 : imageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
511 0 : NS_LITERAL_STRING("cursor: -moz-zoom-out"), true);
512 : }
513 : else {
514 0 : imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
515 : }
516 :
517 0 : mImageIsResized = false;
518 :
519 0 : UpdateTitleAndCharset();
520 :
521 0 : return NS_OK;
522 : }
523 :
524 : NS_IMETHODIMP
525 0 : ImageDocument::ToggleImageSize()
526 : {
527 0 : mShouldResize = true;
528 0 : if (mImageIsResized) {
529 0 : mShouldResize = false;
530 0 : ResetZoomLevel();
531 0 : RestoreImage();
532 : }
533 0 : else if (mImageIsOverflowing) {
534 0 : ResetZoomLevel();
535 0 : ShrinkToFit();
536 : }
537 :
538 0 : return NS_OK;
539 : }
540 :
541 : NS_IMETHODIMP
542 0 : ImageDocument::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage)
543 : {
544 0 : aImage->GetWidth(&mImageWidth);
545 0 : aImage->GetHeight(&mImageHeight);
546 : nsCOMPtr<nsIRunnable> runnable =
547 0 : NS_NewRunnableMethod(this, &ImageDocument::DefaultCheckOverflowing);
548 0 : nsContentUtils::AddScriptRunner(runnable);
549 0 : UpdateTitleAndCharset();
550 :
551 0 : return NS_OK;
552 : }
553 :
554 : NS_IMETHODIMP
555 0 : ImageDocument::OnStopDecode(imgIRequest *aRequest,
556 : nsresult aStatus,
557 : const PRUnichar *aStatusArg)
558 : {
559 0 : UpdateTitleAndCharset();
560 :
561 0 : nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
562 0 : if (imageLoader) {
563 0 : mObservingImageLoader = false;
564 0 : imageLoader->RemoveObserver(this);
565 : }
566 :
567 : // mImageContent can be null if the document is already destroyed
568 0 : if (NS_FAILED(aStatus) && mStringBundle && mImageContent) {
569 0 : nsCAutoString src;
570 0 : mDocumentURI->GetSpec(src);
571 0 : NS_ConvertUTF8toUTF16 srcString(src);
572 0 : const PRUnichar* formatString[] = { srcString.get() };
573 0 : nsXPIDLString errorMsg;
574 0 : NS_NAMED_LITERAL_STRING(str, "InvalidImage");
575 0 : mStringBundle->FormatStringFromName(str.get(), formatString, 1,
576 0 : getter_Copies(errorMsg));
577 :
578 0 : mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false);
579 : }
580 :
581 0 : return NS_OK;
582 : }
583 :
584 : NS_IMETHODIMP
585 0 : ImageDocument::HandleEvent(nsIDOMEvent* aEvent)
586 : {
587 0 : nsAutoString eventType;
588 0 : aEvent->GetType(eventType);
589 0 : if (eventType.EqualsLiteral("resize")) {
590 0 : CheckOverflowing(false);
591 : }
592 0 : else if (eventType.EqualsLiteral("click") && mClickResizingEnabled) {
593 0 : ResetZoomLevel();
594 0 : mShouldResize = true;
595 0 : if (mImageIsResized) {
596 0 : PRInt32 x = 0, y = 0;
597 0 : nsCOMPtr<nsIDOMMouseEvent> event(do_QueryInterface(aEvent));
598 0 : if (event) {
599 0 : event->GetClientX(&x);
600 0 : event->GetClientY(&y);
601 0 : PRInt32 left = 0, top = 0;
602 : nsCOMPtr<nsIDOMHTMLElement> htmlElement =
603 0 : do_QueryInterface(mImageContent);
604 0 : htmlElement->GetOffsetLeft(&left);
605 0 : htmlElement->GetOffsetTop(&top);
606 0 : x -= left;
607 0 : y -= top;
608 : }
609 0 : mShouldResize = false;
610 0 : RestoreImageTo(x, y);
611 : }
612 0 : else if (mImageIsOverflowing) {
613 0 : ShrinkToFit();
614 : }
615 : }
616 :
617 0 : return NS_OK;
618 : }
619 :
620 : nsresult
621 0 : ImageDocument::CreateSyntheticDocument()
622 : {
623 : // Synthesize an html document that refers to the image
624 0 : nsresult rv = MediaDocument::CreateSyntheticDocument();
625 0 : NS_ENSURE_SUCCESS(rv, rv);
626 :
627 : // We must declare the image as a block element. If we stay as
628 : // an inline element, our parent LineBox will be inline too and
629 : // ignore the available height during reflow.
630 : // This is bad during printing, it means tall image frames won't know
631 : // the size of the paper and cannot break into continuations along
632 : // multiple pages.
633 0 : Element* head = GetHeadElement();
634 0 : NS_ENSURE_TRUE(head, NS_ERROR_FAILURE);
635 :
636 0 : nsCOMPtr<nsINodeInfo> nodeInfo;
637 0 : if (nsContentUtils::IsChildOfSameType(this)) {
638 : nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::style, nsnull,
639 : kNameSpaceID_XHTML,
640 0 : nsIDOMNode::ELEMENT_NODE);
641 0 : NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
642 0 : nsRefPtr<nsGenericHTMLElement> styleContent = NS_NewHTMLStyleElement(nodeInfo.forget());
643 0 : NS_ENSURE_TRUE(styleContent, NS_ERROR_OUT_OF_MEMORY);
644 :
645 0 : styleContent->SetTextContent(NS_LITERAL_STRING("img { display: block; }"));
646 0 : head->AppendChildTo(styleContent, false);
647 : }
648 :
649 : // Add the image element
650 0 : Element* body = GetBodyElement();
651 0 : if (!body) {
652 0 : NS_WARNING("no body on image document!");
653 0 : return NS_ERROR_FAILURE;
654 : }
655 :
656 : nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::img, nsnull,
657 : kNameSpaceID_XHTML,
658 0 : nsIDOMNode::ELEMENT_NODE);
659 0 : NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
660 :
661 0 : mImageContent = NS_NewHTMLImageElement(nodeInfo.forget());
662 0 : if (!mImageContent) {
663 0 : return NS_ERROR_OUT_OF_MEMORY;
664 : }
665 0 : nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
666 0 : NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
667 :
668 0 : nsCAutoString src;
669 0 : mDocumentURI->GetSpec(src);
670 :
671 : // Push a null JSContext on the stack so that code that runs within
672 : // the below code doesn't think it's being called by JS. See bug
673 : // 604262.
674 0 : nsCxPusher pusher;
675 0 : pusher.PushNull();
676 :
677 0 : NS_ConvertUTF8toUTF16 srcString(src);
678 : // Make sure not to start the image load from here...
679 0 : imageLoader->SetLoadingEnabled(false);
680 0 : mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::src, srcString, false);
681 0 : mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, srcString, false);
682 :
683 0 : body->AppendChildTo(mImageContent, false);
684 0 : imageLoader->SetLoadingEnabled(true);
685 :
686 0 : return NS_OK;
687 : }
688 :
689 : nsresult
690 0 : ImageDocument::CheckOverflowing(bool changeState)
691 : {
692 : /* Create a scope so that the style context gets destroyed before we might
693 : * call RebuildStyleData. Also, holding onto pointers to the
694 : * presentation through style resolution is potentially dangerous.
695 : */
696 : {
697 0 : nsIPresShell *shell = GetShell();
698 0 : if (!shell) {
699 0 : return NS_OK;
700 : }
701 :
702 0 : nsPresContext *context = shell->GetPresContext();
703 0 : nsRect visibleArea = context->GetVisibleArea();
704 :
705 0 : mVisibleWidth = nsPresContext::AppUnitsToIntCSSPixels(visibleArea.width);
706 0 : mVisibleHeight = nsPresContext::AppUnitsToIntCSSPixels(visibleArea.height);
707 : }
708 :
709 0 : bool imageWasOverflowing = mImageIsOverflowing;
710 : mImageIsOverflowing =
711 0 : mImageWidth > mVisibleWidth || mImageHeight > mVisibleHeight;
712 0 : bool windowBecameBigEnough = imageWasOverflowing && !mImageIsOverflowing;
713 :
714 0 : if (changeState || mShouldResize || mFirstResize ||
715 : windowBecameBigEnough) {
716 0 : if (mImageIsOverflowing && (changeState || mShouldResize)) {
717 0 : ShrinkToFit();
718 : }
719 0 : else if (mImageIsResized || mFirstResize || windowBecameBigEnough) {
720 0 : RestoreImage();
721 : }
722 : }
723 0 : mFirstResize = false;
724 :
725 0 : return NS_OK;
726 : }
727 :
728 : void
729 0 : ImageDocument::UpdateTitleAndCharset()
730 : {
731 0 : nsCAutoString typeStr;
732 0 : nsCOMPtr<imgIRequest> imageRequest;
733 0 : nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
734 0 : if (imageLoader) {
735 0 : imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
736 0 : getter_AddRefs(imageRequest));
737 : }
738 :
739 0 : if (imageRequest) {
740 0 : nsXPIDLCString mimeType;
741 0 : imageRequest->GetMimeType(getter_Copies(mimeType));
742 0 : ToUpperCase(mimeType);
743 0 : nsXPIDLCString::const_iterator start, end;
744 0 : mimeType.BeginReading(start);
745 0 : mimeType.EndReading(end);
746 0 : nsXPIDLCString::const_iterator iter = end;
747 0 : if (FindInReadable(NS_LITERAL_CSTRING("IMAGE/"), start, iter) &&
748 0 : iter != end) {
749 : // strip out "X-" if any
750 0 : if (*iter == 'X') {
751 0 : ++iter;
752 0 : if (iter != end && *iter == '-') {
753 0 : ++iter;
754 0 : if (iter == end) {
755 : // looks like "IMAGE/X-" is the type?? Bail out of here.
756 0 : mimeType.BeginReading(iter);
757 : }
758 : } else {
759 0 : --iter;
760 : }
761 : }
762 0 : typeStr = Substring(iter, end);
763 : } else {
764 0 : typeStr = mimeType;
765 : }
766 : }
767 :
768 0 : nsXPIDLString status;
769 0 : if (mImageIsResized) {
770 0 : nsAutoString ratioStr;
771 0 : ratioStr.AppendInt(NSToCoordFloor(GetRatio() * 100));
772 :
773 0 : const PRUnichar* formatString[1] = { ratioStr.get() };
774 0 : mStringBundle->FormatStringFromName(NS_LITERAL_STRING("ScaledImage").get(),
775 : formatString, 1,
776 0 : getter_Copies(status));
777 : }
778 :
779 : static const char* const formatNames[4] =
780 : {
781 : "ImageTitleWithNeitherDimensionsNorFile",
782 : "ImageTitleWithoutDimensions",
783 : "ImageTitleWithDimensions",
784 : "ImageTitleWithDimensionsAndFile",
785 : };
786 :
787 : MediaDocument::UpdateTitleAndCharset(typeStr, formatNames,
788 0 : mImageWidth, mImageHeight, status);
789 0 : }
790 :
791 : void
792 0 : ImageDocument::ResetZoomLevel()
793 : {
794 0 : nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
795 0 : if (docShell) {
796 0 : if (nsContentUtils::IsChildOfSameType(this)) {
797 : return;
798 : }
799 :
800 0 : nsCOMPtr<nsIContentViewer> cv;
801 0 : docShell->GetContentViewer(getter_AddRefs(cv));
802 0 : nsCOMPtr<nsIMarkupDocumentViewer> mdv = do_QueryInterface(cv);
803 0 : if (mdv) {
804 0 : mdv->SetFullZoom(mOriginalZoomLevel);
805 : }
806 : }
807 : }
808 :
809 : float
810 0 : ImageDocument::GetZoomLevel()
811 : {
812 0 : float zoomLevel = mOriginalZoomLevel;
813 0 : nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
814 0 : if (docShell) {
815 0 : nsCOMPtr<nsIContentViewer> cv;
816 0 : docShell->GetContentViewer(getter_AddRefs(cv));
817 0 : nsCOMPtr<nsIMarkupDocumentViewer> mdv = do_QueryInterface(cv);
818 0 : if (mdv) {
819 0 : mdv->GetFullZoom(&zoomLevel);
820 : }
821 : }
822 0 : return zoomLevel;
823 : }
824 :
825 : } // namespace dom
826 : } // namespace mozilla
827 :
828 : DOMCI_DATA(ImageDocument, mozilla::dom::ImageDocument)
829 :
830 : nsresult
831 0 : NS_NewImageDocument(nsIDocument** aResult)
832 : {
833 0 : mozilla::dom::ImageDocument* doc = new mozilla::dom::ImageDocument();
834 0 : NS_ADDREF(doc);
835 :
836 0 : nsresult rv = doc->Init();
837 0 : if (NS_FAILED(rv)) {
838 0 : NS_RELEASE(doc);
839 : }
840 :
841 0 : *aResult = doc;
842 :
843 0 : return rv;
844 4392 : }
|