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