Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | * |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include <algorithm> |
michael@0 | 8 | |
michael@0 | 9 | #include "mozilla/Preferences.h" |
michael@0 | 10 | #include "mozilla/Likely.h" |
michael@0 | 11 | |
michael@0 | 12 | #include "nsIHttpChannel.h" |
michael@0 | 13 | #include "nsIFileChannel.h" |
michael@0 | 14 | #include "nsIFile.h" |
michael@0 | 15 | #include "nsMimeTypes.h" |
michael@0 | 16 | #include "nsIRequest.h" |
michael@0 | 17 | |
michael@0 | 18 | #include "RasterImage.h" |
michael@0 | 19 | #include "VectorImage.h" |
michael@0 | 20 | #include "Image.h" |
michael@0 | 21 | #include "nsMediaFragmentURIParser.h" |
michael@0 | 22 | #include "nsContentUtils.h" |
michael@0 | 23 | #include "nsIScriptSecurityManager.h" |
michael@0 | 24 | |
michael@0 | 25 | #include "ImageFactory.h" |
michael@0 | 26 | #include "gfxPrefs.h" |
michael@0 | 27 | |
michael@0 | 28 | namespace mozilla { |
michael@0 | 29 | namespace image { |
michael@0 | 30 | |
michael@0 | 31 | // Global preferences related to image containers. |
michael@0 | 32 | static bool gInitializedPrefCaches = false; |
michael@0 | 33 | static bool gDecodeOnDraw = false; |
michael@0 | 34 | static bool gDiscardable = false; |
michael@0 | 35 | static bool gEnableMozSampleSize = false; |
michael@0 | 36 | |
michael@0 | 37 | /*static*/ void |
michael@0 | 38 | ImageFactory::Initialize() |
michael@0 | 39 | { |
michael@0 | 40 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 41 | if (!gInitializedPrefCaches) { |
michael@0 | 42 | // Initialize the graphics preferences |
michael@0 | 43 | gfxPrefs::GetSingleton(); |
michael@0 | 44 | Preferences::AddBoolVarCache(&gDiscardable, "image.mem.discardable"); |
michael@0 | 45 | Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw"); |
michael@0 | 46 | Preferences::AddBoolVarCache(&gEnableMozSampleSize, "image.mozsamplesize.enabled"); |
michael@0 | 47 | gInitializedPrefCaches = true; |
michael@0 | 48 | } |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | static uint32_t |
michael@0 | 52 | ComputeImageFlags(ImageURL* uri, bool isMultiPart) |
michael@0 | 53 | { |
michael@0 | 54 | nsresult rv; |
michael@0 | 55 | |
michael@0 | 56 | // We default to the static globals. |
michael@0 | 57 | bool isDiscardable = gDiscardable; |
michael@0 | 58 | bool doDecodeOnDraw = gDecodeOnDraw; |
michael@0 | 59 | |
michael@0 | 60 | // We want UI to be as snappy as possible and not to flicker. Disable discarding |
michael@0 | 61 | // and decode-on-draw for chrome URLS. |
michael@0 | 62 | bool isChrome = false; |
michael@0 | 63 | rv = uri->SchemeIs("chrome", &isChrome); |
michael@0 | 64 | if (NS_SUCCEEDED(rv) && isChrome) |
michael@0 | 65 | isDiscardable = doDecodeOnDraw = false; |
michael@0 | 66 | |
michael@0 | 67 | // We don't want resources like the "loading" icon to be discardable or |
michael@0 | 68 | // decode-on-draw either. |
michael@0 | 69 | bool isResource = false; |
michael@0 | 70 | rv = uri->SchemeIs("resource", &isResource); |
michael@0 | 71 | if (NS_SUCCEEDED(rv) && isResource) |
michael@0 | 72 | isDiscardable = doDecodeOnDraw = false; |
michael@0 | 73 | |
michael@0 | 74 | // For multipart/x-mixed-replace, we basically want a direct channel to the |
michael@0 | 75 | // decoder. Disable both for this case as well. |
michael@0 | 76 | if (isMultiPart) |
michael@0 | 77 | isDiscardable = doDecodeOnDraw = false; |
michael@0 | 78 | |
michael@0 | 79 | // We have all the information we need. |
michael@0 | 80 | uint32_t imageFlags = Image::INIT_FLAG_NONE; |
michael@0 | 81 | if (isDiscardable) |
michael@0 | 82 | imageFlags |= Image::INIT_FLAG_DISCARDABLE; |
michael@0 | 83 | if (doDecodeOnDraw) |
michael@0 | 84 | imageFlags |= Image::INIT_FLAG_DECODE_ON_DRAW; |
michael@0 | 85 | if (isMultiPart) |
michael@0 | 86 | imageFlags |= Image::INIT_FLAG_MULTIPART; |
michael@0 | 87 | |
michael@0 | 88 | return imageFlags; |
michael@0 | 89 | } |
michael@0 | 90 | |
michael@0 | 91 | /* static */ bool |
michael@0 | 92 | ImageFactory::CanRetargetOnDataAvailable(ImageURL* aURI, bool aIsMultiPart) |
michael@0 | 93 | { |
michael@0 | 94 | // We can't retarget OnDataAvailable safely in cases where we aren't storing |
michael@0 | 95 | // source data (and thus need to sync decode in ODA) because allocating frames |
michael@0 | 96 | // off-main-thread is currently not possible and we don't have a workaround in |
michael@0 | 97 | // place yet. (See bug 967985.) For now, we detect those cases and refuse to |
michael@0 | 98 | // retarget. When the problem is fixed, this function can be removed. |
michael@0 | 99 | |
michael@0 | 100 | if (aIsMultiPart) { |
michael@0 | 101 | return false; |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | uint32_t imageFlags = ComputeImageFlags(aURI, aIsMultiPart); |
michael@0 | 105 | if (!(imageFlags & Image::INIT_FLAG_DISCARDABLE) && |
michael@0 | 106 | !(imageFlags & Image::INIT_FLAG_DECODE_ON_DRAW)) { |
michael@0 | 107 | return false; |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | return true; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | /* static */ already_AddRefed<Image> |
michael@0 | 114 | ImageFactory::CreateImage(nsIRequest* aRequest, |
michael@0 | 115 | imgStatusTracker* aStatusTracker, |
michael@0 | 116 | const nsCString& aMimeType, |
michael@0 | 117 | ImageURL* aURI, |
michael@0 | 118 | bool aIsMultiPart, |
michael@0 | 119 | uint32_t aInnerWindowId) |
michael@0 | 120 | { |
michael@0 | 121 | MOZ_ASSERT(gInitializedPrefCaches, |
michael@0 | 122 | "Pref observers should have been initialized already"); |
michael@0 | 123 | |
michael@0 | 124 | // Compute the image's initialization flags. |
michael@0 | 125 | uint32_t imageFlags = ComputeImageFlags(aURI, aIsMultiPart); |
michael@0 | 126 | |
michael@0 | 127 | // Select the type of image to create based on MIME type. |
michael@0 | 128 | if (aMimeType.EqualsLiteral(IMAGE_SVG_XML)) { |
michael@0 | 129 | return CreateVectorImage(aRequest, aStatusTracker, aMimeType, |
michael@0 | 130 | aURI, imageFlags, aInnerWindowId); |
michael@0 | 131 | } else { |
michael@0 | 132 | return CreateRasterImage(aRequest, aStatusTracker, aMimeType, |
michael@0 | 133 | aURI, imageFlags, aInnerWindowId); |
michael@0 | 134 | } |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | // Marks an image as having an error before returning it. Used with macros like |
michael@0 | 138 | // NS_ENSURE_SUCCESS, since we guarantee to always return an image even if an |
michael@0 | 139 | // error occurs, but callers need to be able to tell that this happened. |
michael@0 | 140 | template <typename T> |
michael@0 | 141 | static already_AddRefed<Image> |
michael@0 | 142 | BadImage(nsRefPtr<T>& image) |
michael@0 | 143 | { |
michael@0 | 144 | image->SetHasError(); |
michael@0 | 145 | return image.forget(); |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | /* static */ already_AddRefed<Image> |
michael@0 | 149 | ImageFactory::CreateAnonymousImage(const nsCString& aMimeType) |
michael@0 | 150 | { |
michael@0 | 151 | nsresult rv; |
michael@0 | 152 | |
michael@0 | 153 | nsRefPtr<RasterImage> newImage = new RasterImage(); |
michael@0 | 154 | |
michael@0 | 155 | rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_NONE); |
michael@0 | 156 | NS_ENSURE_SUCCESS(rv, BadImage(newImage)); |
michael@0 | 157 | |
michael@0 | 158 | return newImage.forget(); |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | int32_t |
michael@0 | 162 | SaturateToInt32(int64_t val) |
michael@0 | 163 | { |
michael@0 | 164 | if (val > INT_MAX) |
michael@0 | 165 | return INT_MAX; |
michael@0 | 166 | if (val < INT_MIN) |
michael@0 | 167 | return INT_MIN; |
michael@0 | 168 | |
michael@0 | 169 | return static_cast<int32_t>(val); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | uint32_t |
michael@0 | 173 | GetContentSize(nsIRequest* aRequest) |
michael@0 | 174 | { |
michael@0 | 175 | nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); |
michael@0 | 176 | if (channel) { |
michael@0 | 177 | int64_t size; |
michael@0 | 178 | nsresult rv = channel->GetContentLength(&size); |
michael@0 | 179 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 180 | return std::max(SaturateToInt32(size), 0); |
michael@0 | 181 | } |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | // Use the file size as a size hint for file channels. |
michael@0 | 185 | nsCOMPtr<nsIFileChannel> fileChannel(do_QueryInterface(aRequest)); |
michael@0 | 186 | if (fileChannel) { |
michael@0 | 187 | nsCOMPtr<nsIFile> file; |
michael@0 | 188 | nsresult rv = fileChannel->GetFile(getter_AddRefs(file)); |
michael@0 | 189 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 190 | int64_t filesize; |
michael@0 | 191 | rv = file->GetFileSize(&filesize); |
michael@0 | 192 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 193 | return std::max(SaturateToInt32(filesize), 0); |
michael@0 | 194 | } |
michael@0 | 195 | } |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | // Fallback - neither http nor file. We'll use dynamic allocation. |
michael@0 | 199 | return 0; |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | /* static */ already_AddRefed<Image> |
michael@0 | 203 | ImageFactory::CreateRasterImage(nsIRequest* aRequest, |
michael@0 | 204 | imgStatusTracker* aStatusTracker, |
michael@0 | 205 | const nsCString& aMimeType, |
michael@0 | 206 | ImageURL* aURI, |
michael@0 | 207 | uint32_t aImageFlags, |
michael@0 | 208 | uint32_t aInnerWindowId) |
michael@0 | 209 | { |
michael@0 | 210 | nsresult rv; |
michael@0 | 211 | |
michael@0 | 212 | nsRefPtr<RasterImage> newImage = new RasterImage(aStatusTracker, aURI); |
michael@0 | 213 | |
michael@0 | 214 | rv = newImage->Init(aMimeType.get(), aImageFlags); |
michael@0 | 215 | NS_ENSURE_SUCCESS(rv, BadImage(newImage)); |
michael@0 | 216 | |
michael@0 | 217 | newImage->SetInnerWindowID(aInnerWindowId); |
michael@0 | 218 | |
michael@0 | 219 | uint32_t len = GetContentSize(aRequest); |
michael@0 | 220 | |
michael@0 | 221 | // Pass anything usable on so that the RasterImage can preallocate |
michael@0 | 222 | // its source buffer. |
michael@0 | 223 | if (len > 0) { |
michael@0 | 224 | uint32_t sizeHint = std::min<uint32_t>(len, 20000000); // Bound by something reasonable |
michael@0 | 225 | rv = newImage->SetSourceSizeHint(sizeHint); |
michael@0 | 226 | if (NS_FAILED(rv)) { |
michael@0 | 227 | // Flush memory, try to get some back, and try again. |
michael@0 | 228 | rv = nsMemory::HeapMinimize(true); |
michael@0 | 229 | nsresult rv2 = newImage->SetSourceSizeHint(sizeHint); |
michael@0 | 230 | // If we've still failed at this point, things are going downhill. |
michael@0 | 231 | if (NS_FAILED(rv) || NS_FAILED(rv2)) { |
michael@0 | 232 | NS_WARNING("About to hit OOM in imagelib!"); |
michael@0 | 233 | } |
michael@0 | 234 | } |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | nsAutoCString ref; |
michael@0 | 238 | aURI->GetRef(ref); |
michael@0 | 239 | mozilla::net::nsMediaFragmentURIParser parser(ref); |
michael@0 | 240 | if (parser.HasResolution()) { |
michael@0 | 241 | newImage->SetRequestedResolution(parser.GetResolution()); |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | if (parser.HasSampleSize()) { |
michael@0 | 245 | /* Get our principal */ |
michael@0 | 246 | nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); |
michael@0 | 247 | nsCOMPtr<nsIPrincipal> principal; |
michael@0 | 248 | if (chan) { |
michael@0 | 249 | nsContentUtils::GetSecurityManager()->GetChannelPrincipal(chan, |
michael@0 | 250 | getter_AddRefs(principal)); |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | if ((principal && |
michael@0 | 254 | principal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED) || |
michael@0 | 255 | gEnableMozSampleSize) { |
michael@0 | 256 | newImage->SetRequestedSampleSize(parser.GetSampleSize()); |
michael@0 | 257 | } |
michael@0 | 258 | } |
michael@0 | 259 | |
michael@0 | 260 | return newImage.forget(); |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | /* static */ already_AddRefed<Image> |
michael@0 | 264 | ImageFactory::CreateVectorImage(nsIRequest* aRequest, |
michael@0 | 265 | imgStatusTracker* aStatusTracker, |
michael@0 | 266 | const nsCString& aMimeType, |
michael@0 | 267 | ImageURL* aURI, |
michael@0 | 268 | uint32_t aImageFlags, |
michael@0 | 269 | uint32_t aInnerWindowId) |
michael@0 | 270 | { |
michael@0 | 271 | nsresult rv; |
michael@0 | 272 | |
michael@0 | 273 | nsRefPtr<VectorImage> newImage = new VectorImage(aStatusTracker, aURI); |
michael@0 | 274 | |
michael@0 | 275 | rv = newImage->Init(aMimeType.get(), aImageFlags); |
michael@0 | 276 | NS_ENSURE_SUCCESS(rv, BadImage(newImage)); |
michael@0 | 277 | |
michael@0 | 278 | newImage->SetInnerWindowID(aInnerWindowId); |
michael@0 | 279 | |
michael@0 | 280 | rv = newImage->OnStartRequest(aRequest, nullptr); |
michael@0 | 281 | NS_ENSURE_SUCCESS(rv, BadImage(newImage)); |
michael@0 | 282 | |
michael@0 | 283 | return newImage.forget(); |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | } // namespace image |
michael@0 | 287 | } // namespace mozilla |