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