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