/src/mozilla-central/dom/canvas/ImageBitmap.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
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 "mozilla/dom/ImageBitmap.h" |
8 | | #include "mozilla/CheckedInt.h" |
9 | | #include "mozilla/dom/DOMPrefs.h" |
10 | | #include "mozilla/dom/HTMLMediaElementBinding.h" |
11 | | #include "mozilla/dom/ImageBitmapBinding.h" |
12 | | #include "mozilla/dom/Promise.h" |
13 | | #include "mozilla/dom/StructuredCloneTags.h" |
14 | | #include "mozilla/dom/WorkerPrivate.h" |
15 | | #include "mozilla/dom/WorkerRef.h" |
16 | | #include "mozilla/dom/WorkerRunnable.h" |
17 | | #include "mozilla/gfx/2D.h" |
18 | | #include "mozilla/gfx/Swizzle.h" |
19 | | #include "mozilla/Mutex.h" |
20 | | #include "mozilla/ScopeExit.h" |
21 | | #include "ImageBitmapColorUtils.h" |
22 | | #include "ImageBitmapUtils.h" |
23 | | #include "ImageUtils.h" |
24 | | #include "imgTools.h" |
25 | | |
26 | | using namespace mozilla::gfx; |
27 | | using namespace mozilla::layers; |
28 | | using mozilla::dom::HTMLMediaElement_Binding::NETWORK_EMPTY; |
29 | | using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA; |
30 | | |
31 | | namespace mozilla { |
32 | | namespace dom { |
33 | | |
34 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmap, mParent) |
35 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmap) |
36 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmap) |
37 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmap) |
38 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
39 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
40 | 0 | NS_INTERFACE_MAP_END |
41 | | |
42 | | /* This class observes shutdown notifications and sends that notification |
43 | | * to the worker thread if the image bitmap is on a worker thread. |
44 | | */ |
45 | | class ImageBitmapShutdownObserver final : public nsIObserver |
46 | | { |
47 | | public: |
48 | | explicit ImageBitmapShutdownObserver(ImageBitmap* aImageBitmap) |
49 | | : mImageBitmap(nullptr) |
50 | 0 | { |
51 | 0 | if (NS_IsMainThread()) { |
52 | 0 | mImageBitmap = aImageBitmap; |
53 | 0 | } else { |
54 | 0 | WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); |
55 | 0 | MOZ_ASSERT(workerPrivate); |
56 | 0 | mMainThreadEventTarget = workerPrivate->MainThreadEventTarget(); |
57 | 0 | mSendToWorkerTask = new SendShutdownToWorkerThread(aImageBitmap); |
58 | 0 | } |
59 | 0 | } |
60 | | |
61 | 0 | void RegisterObserver() { |
62 | 0 | if (NS_IsMainThread()) { |
63 | 0 | nsContentUtils::RegisterShutdownObserver(this); |
64 | 0 | return; |
65 | 0 | } |
66 | 0 | |
67 | 0 | MOZ_ASSERT(mMainThreadEventTarget); |
68 | 0 | RefPtr<ImageBitmapShutdownObserver> self = this; |
69 | 0 | nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( |
70 | 0 | "ImageBitmapShutdownObserver::RegisterObserver", |
71 | 0 | [self]() { |
72 | 0 | self->RegisterObserver(); |
73 | 0 | }); |
74 | 0 |
|
75 | 0 | mMainThreadEventTarget->Dispatch(r.forget()); |
76 | 0 | } |
77 | | |
78 | 0 | void UnregisterObserver() { |
79 | 0 | if (NS_IsMainThread()) { |
80 | 0 | nsContentUtils::UnregisterShutdownObserver(this); |
81 | 0 | return; |
82 | 0 | } |
83 | 0 | |
84 | 0 | MOZ_ASSERT(mMainThreadEventTarget); |
85 | 0 | RefPtr<ImageBitmapShutdownObserver> self = this; |
86 | 0 | nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( |
87 | 0 | "ImageBitmapShutdownObserver::RegisterObserver", |
88 | 0 | [self]() { |
89 | 0 | self->UnregisterObserver(); |
90 | 0 | }); |
91 | 0 |
|
92 | 0 | mMainThreadEventTarget->Dispatch(r.forget()); |
93 | 0 | } |
94 | | |
95 | 0 | void Clear() { |
96 | 0 | mImageBitmap = nullptr; |
97 | 0 | if (mSendToWorkerTask) { |
98 | 0 | mSendToWorkerTask->mImageBitmap = nullptr; |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | NS_DECL_THREADSAFE_ISUPPORTS |
103 | | NS_DECL_NSIOBSERVER |
104 | | private: |
105 | 0 | ~ImageBitmapShutdownObserver() {} |
106 | | |
107 | | class SendShutdownToWorkerThread : public MainThreadWorkerControlRunnable |
108 | | { |
109 | | public: |
110 | | explicit SendShutdownToWorkerThread(ImageBitmap* aImageBitmap) |
111 | | : MainThreadWorkerControlRunnable(GetCurrentThreadWorkerPrivate()) |
112 | | , mImageBitmap(aImageBitmap) |
113 | 0 | {} |
114 | | |
115 | | bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
116 | 0 | { |
117 | 0 | if (mImageBitmap) { |
118 | 0 | mImageBitmap->OnShutdown(); |
119 | 0 | mImageBitmap = nullptr; |
120 | 0 | } |
121 | 0 | return true; |
122 | 0 | } |
123 | | |
124 | | ImageBitmap* mImageBitmap; |
125 | | }; |
126 | | |
127 | | ImageBitmap* mImageBitmap; |
128 | | nsCOMPtr<nsIEventTarget> mMainThreadEventTarget; |
129 | | RefPtr<SendShutdownToWorkerThread> mSendToWorkerTask; |
130 | | }; |
131 | | |
132 | | NS_IMPL_ISUPPORTS(ImageBitmapShutdownObserver, nsIObserver) |
133 | | |
134 | | NS_IMETHODIMP |
135 | | ImageBitmapShutdownObserver::Observe(nsISupports* aSubject, |
136 | | const char* aTopic, |
137 | | const char16_t* aData) |
138 | 0 | { |
139 | 0 | if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { |
140 | 0 | if (mSendToWorkerTask) { |
141 | 0 | mSendToWorkerTask->Dispatch(); |
142 | 0 | } else { |
143 | 0 | if (mImageBitmap) { |
144 | 0 | mImageBitmap->OnShutdown(); |
145 | 0 | mImageBitmap = nullptr; |
146 | 0 | } |
147 | 0 | } |
148 | 0 | nsContentUtils::UnregisterShutdownObserver(this); |
149 | 0 | } |
150 | 0 |
|
151 | 0 | return NS_OK; |
152 | 0 | } |
153 | | |
154 | | |
155 | | /* |
156 | | * If either aRect.width or aRect.height are negative, then return a new IntRect |
157 | | * which represents the same rectangle as the aRect does but with positive width |
158 | | * and height. |
159 | | */ |
160 | | static IntRect |
161 | | FixUpNegativeDimension(const IntRect& aRect, ErrorResult& aRv) |
162 | 0 | { |
163 | 0 | gfx::IntRect rect = aRect; |
164 | 0 |
|
165 | 0 | // fix up negative dimensions |
166 | 0 | if (rect.width < 0) { |
167 | 0 | CheckedInt32 checkedX = CheckedInt32(rect.x) + rect.width; |
168 | 0 |
|
169 | 0 | if (!checkedX.isValid()) { |
170 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
171 | 0 | return rect; |
172 | 0 | } |
173 | 0 | |
174 | 0 | rect.x = checkedX.value(); |
175 | 0 | rect.width = -(rect.width); |
176 | 0 | } |
177 | 0 |
|
178 | 0 | if (rect.height < 0) { |
179 | 0 | CheckedInt32 checkedY = CheckedInt32(rect.y) + rect.height; |
180 | 0 |
|
181 | 0 | if (!checkedY.isValid()) { |
182 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
183 | 0 | return rect; |
184 | 0 | } |
185 | 0 | |
186 | 0 | rect.y = checkedY.value(); |
187 | 0 | rect.height = -(rect.height); |
188 | 0 | } |
189 | 0 |
|
190 | 0 | return rect; |
191 | 0 | } |
192 | | |
193 | | /* |
194 | | * This helper function copies the data of the given DataSourceSurface, |
195 | | * _aSurface_, in the given area, _aCropRect_, into a new DataSourceSurface. |
196 | | * This might return null if it can not create a new SourceSurface or it cannot |
197 | | * read data from the given _aSurface_. |
198 | | * |
199 | | * Warning: Even though the area of _aCropRect_ is just the same as the size of |
200 | | * _aSurface_, this function still copy data into a new |
201 | | * DataSourceSurface. |
202 | | */ |
203 | | static already_AddRefed<DataSourceSurface> |
204 | | CropAndCopyDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRect) |
205 | 0 | { |
206 | 0 | MOZ_ASSERT(aSurface); |
207 | 0 |
|
208 | 0 | // Check the aCropRect |
209 | 0 | ErrorResult error; |
210 | 0 | const IntRect positiveCropRect = FixUpNegativeDimension(aCropRect, error); |
211 | 0 | if (NS_WARN_IF(error.Failed())) { |
212 | 0 | error.SuppressException(); |
213 | 0 | return nullptr; |
214 | 0 | } |
215 | 0 | |
216 | 0 | // Calculate the size of the new SourceSurface. |
217 | 0 | // We cannot keep using aSurface->GetFormat() to create new DataSourceSurface, |
218 | 0 | // since it might be SurfaceFormat::B8G8R8X8 which does not handle opacity, |
219 | 0 | // however the specification explicitly define that "If any of the pixels on |
220 | 0 | // this rectangle are outside the area where the input bitmap was placed, then |
221 | 0 | // they will be transparent black in output." |
222 | 0 | // So, instead, we force the output format to be SurfaceFormat::B8G8R8A8. |
223 | 0 | const SurfaceFormat format = SurfaceFormat::B8G8R8A8; |
224 | 0 | const int bytesPerPixel = BytesPerPixel(format); |
225 | 0 | const IntSize dstSize = IntSize(positiveCropRect.width, |
226 | 0 | positiveCropRect.height); |
227 | 0 | const uint32_t dstStride = dstSize.width * bytesPerPixel; |
228 | 0 |
|
229 | 0 | // Create a new SourceSurface. |
230 | 0 | RefPtr<DataSourceSurface> dstDataSurface = |
231 | 0 | Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride, true); |
232 | 0 |
|
233 | 0 | if (NS_WARN_IF(!dstDataSurface)) { |
234 | 0 | return nullptr; |
235 | 0 | } |
236 | 0 | |
237 | 0 | // Only do copying and cropping when the positiveCropRect intersects with |
238 | 0 | // the size of aSurface. |
239 | 0 | const IntRect surfRect(IntPoint(0, 0), aSurface->GetSize()); |
240 | 0 | if (surfRect.Intersects(positiveCropRect)) { |
241 | 0 | const IntRect surfPortion = surfRect.Intersect(positiveCropRect); |
242 | 0 | const IntPoint dest(std::max(0, surfPortion.X() - positiveCropRect.X()), |
243 | 0 | std::max(0, surfPortion.Y() - positiveCropRect.Y())); |
244 | 0 |
|
245 | 0 | // Copy the raw data into the newly created DataSourceSurface. |
246 | 0 | DataSourceSurface::ScopedMap srcMap(aSurface, DataSourceSurface::READ); |
247 | 0 | DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE); |
248 | 0 | if (NS_WARN_IF(!srcMap.IsMapped()) || |
249 | 0 | NS_WARN_IF(!dstMap.IsMapped())) { |
250 | 0 | return nullptr; |
251 | 0 | } |
252 | 0 | |
253 | 0 | uint8_t* srcBufferPtr = srcMap.GetData() + surfPortion.y * srcMap.GetStride() |
254 | 0 | + surfPortion.x * bytesPerPixel; |
255 | 0 | uint8_t* dstBufferPtr = dstMap.GetData() + dest.y * dstMap.GetStride() |
256 | 0 | + dest.x * bytesPerPixel; |
257 | 0 | CheckedInt<uint32_t> copiedBytesPerRaw = |
258 | 0 | CheckedInt<uint32_t>(surfPortion.width) * bytesPerPixel; |
259 | 0 | if (!copiedBytesPerRaw.isValid()) { |
260 | 0 | return nullptr; |
261 | 0 | } |
262 | 0 | |
263 | 0 | for (int i = 0; i < surfPortion.height; ++i) { |
264 | 0 | memcpy(dstBufferPtr, srcBufferPtr, copiedBytesPerRaw.value()); |
265 | 0 | srcBufferPtr += srcMap.GetStride(); |
266 | 0 | dstBufferPtr += dstMap.GetStride(); |
267 | 0 | } |
268 | 0 | } |
269 | 0 |
|
270 | 0 | return dstDataSurface.forget(); |
271 | 0 | } |
272 | | |
273 | | /* |
274 | | * Encapsulate the given _aSurface_ into a layers::SourceSurfaceImage. |
275 | | */ |
276 | | static already_AddRefed<layers::Image> |
277 | | CreateImageFromSurface(SourceSurface* aSurface) |
278 | 0 | { |
279 | 0 | MOZ_ASSERT(aSurface); |
280 | 0 | RefPtr<layers::SourceSurfaceImage> image = |
281 | 0 | new layers::SourceSurfaceImage(aSurface->GetSize(), aSurface); |
282 | 0 | return image.forget(); |
283 | 0 | } |
284 | | |
285 | | /* |
286 | | * CreateImageFromRawData(), CreateSurfaceFromRawData() and |
287 | | * CreateImageFromRawDataInMainThreadSyncTask are helpers for |
288 | | * create-from-ImageData case |
289 | | */ |
290 | | static already_AddRefed<SourceSurface> |
291 | | CreateSurfaceFromRawData(const gfx::IntSize& aSize, |
292 | | uint32_t aStride, |
293 | | gfx::SurfaceFormat aFormat, |
294 | | uint8_t* aBuffer, |
295 | | uint32_t aBufferLength, |
296 | | const Maybe<IntRect>& aCropRect) |
297 | 0 | { |
298 | 0 | MOZ_ASSERT(!aSize.IsEmpty()); |
299 | 0 | MOZ_ASSERT(aBuffer); |
300 | 0 |
|
301 | 0 | // Wrap the source buffer into a SourceSurface. |
302 | 0 | RefPtr<DataSourceSurface> dataSurface = |
303 | 0 | Factory::CreateWrappingDataSourceSurface(aBuffer, aStride, aSize, aFormat); |
304 | 0 |
|
305 | 0 | if (NS_WARN_IF(!dataSurface)) { |
306 | 0 | return nullptr; |
307 | 0 | } |
308 | 0 | |
309 | 0 | // The temporary cropRect variable is equal to the size of source buffer if we |
310 | 0 | // do not need to crop, or it equals to the given cropping size. |
311 | 0 | const IntRect cropRect = aCropRect.valueOr(IntRect(0, 0, aSize.width, aSize.height)); |
312 | 0 |
|
313 | 0 | // Copy the source buffer in the _cropRect_ area into a new SourceSurface. |
314 | 0 | RefPtr<DataSourceSurface> result = CropAndCopyDataSourceSurface(dataSurface, cropRect); |
315 | 0 |
|
316 | 0 | if (NS_WARN_IF(!result)) { |
317 | 0 | return nullptr; |
318 | 0 | } |
319 | 0 | |
320 | 0 | return result.forget(); |
321 | 0 | } |
322 | | |
323 | | static already_AddRefed<layers::Image> |
324 | | CreateImageFromRawData(const gfx::IntSize& aSize, |
325 | | uint32_t aStride, |
326 | | gfx::SurfaceFormat aFormat, |
327 | | uint8_t* aBuffer, |
328 | | uint32_t aBufferLength, |
329 | | const Maybe<IntRect>& aCropRect) |
330 | 0 | { |
331 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
332 | 0 |
|
333 | 0 | // Copy and crop the source buffer into a SourceSurface. |
334 | 0 | RefPtr<SourceSurface> rgbaSurface = |
335 | 0 | CreateSurfaceFromRawData(aSize, aStride, aFormat, |
336 | 0 | aBuffer, aBufferLength, |
337 | 0 | aCropRect); |
338 | 0 |
|
339 | 0 | if (NS_WARN_IF(!rgbaSurface)) { |
340 | 0 | return nullptr; |
341 | 0 | } |
342 | 0 | |
343 | 0 | // Convert RGBA to BGRA |
344 | 0 | RefPtr<DataSourceSurface> rgbaDataSurface = rgbaSurface->GetDataSurface(); |
345 | 0 | DataSourceSurface::ScopedMap rgbaMap(rgbaDataSurface, DataSourceSurface::READ); |
346 | 0 | if (NS_WARN_IF(!rgbaMap.IsMapped())) { |
347 | 0 | return nullptr; |
348 | 0 | } |
349 | 0 | |
350 | 0 | RefPtr<DataSourceSurface> bgraDataSurface = |
351 | 0 | Factory::CreateDataSourceSurfaceWithStride(rgbaDataSurface->GetSize(), |
352 | 0 | SurfaceFormat::B8G8R8A8, |
353 | 0 | rgbaMap.GetStride()); |
354 | 0 | if (NS_WARN_IF(!bgraDataSurface)) { |
355 | 0 | return nullptr; |
356 | 0 | } |
357 | 0 | |
358 | 0 | DataSourceSurface::ScopedMap bgraMap(bgraDataSurface, DataSourceSurface::WRITE); |
359 | 0 | if (NS_WARN_IF(!bgraMap.IsMapped())) { |
360 | 0 | return nullptr; |
361 | 0 | } |
362 | 0 | |
363 | 0 | SwizzleData(rgbaMap.GetData(), rgbaMap.GetStride(), SurfaceFormat::R8G8B8A8, |
364 | 0 | bgraMap.GetData(), bgraMap.GetStride(), SurfaceFormat::B8G8R8A8, |
365 | 0 | bgraDataSurface->GetSize()); |
366 | 0 |
|
367 | 0 | // Create an Image from the BGRA SourceSurface. |
368 | 0 | RefPtr<layers::Image> image = CreateImageFromSurface(bgraDataSurface); |
369 | 0 |
|
370 | 0 | if (NS_WARN_IF(!image)) { |
371 | 0 | return nullptr; |
372 | 0 | } |
373 | 0 | |
374 | 0 | return image.forget(); |
375 | 0 | } |
376 | | |
377 | | /* |
378 | | * This is a synchronous task. |
379 | | * This class is used to create a layers::SourceSurfaceImage from raw data in the main |
380 | | * thread. While creating an ImageBitmap from an ImageData, we need to create |
381 | | * a SouceSurface from the ImageData's raw data and then set the SourceSurface |
382 | | * into a layers::SourceSurfaceImage. However, the layers::SourceSurfaceImage asserts the |
383 | | * setting operation in the main thread, so if we are going to create an |
384 | | * ImageBitmap from an ImageData off the main thread, we post an event to the |
385 | | * main thread to create a layers::SourceSurfaceImage from an ImageData's raw data. |
386 | | */ |
387 | | class CreateImageFromRawDataInMainThreadSyncTask final : |
388 | | public WorkerMainThreadRunnable |
389 | | { |
390 | | public: |
391 | | CreateImageFromRawDataInMainThreadSyncTask(uint8_t* aBuffer, |
392 | | uint32_t aBufferLength, |
393 | | uint32_t aStride, |
394 | | gfx::SurfaceFormat aFormat, |
395 | | const gfx::IntSize& aSize, |
396 | | const Maybe<IntRect>& aCropRect, |
397 | | layers::Image** aImage) |
398 | | : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(), |
399 | | NS_LITERAL_CSTRING("ImageBitmap :: Create Image from Raw Data")) |
400 | | , mImage(aImage) |
401 | | , mBuffer(aBuffer) |
402 | | , mBufferLength(aBufferLength) |
403 | | , mStride(aStride) |
404 | | , mFormat(aFormat) |
405 | | , mSize(aSize) |
406 | | , mCropRect(aCropRect) |
407 | 0 | { |
408 | 0 | MOZ_ASSERT(!(*aImage), "Don't pass an existing Image into CreateImageFromRawDataInMainThreadSyncTask."); |
409 | 0 | } |
410 | | |
411 | | bool MainThreadRun() override |
412 | 0 | { |
413 | 0 | RefPtr<layers::Image> image = |
414 | 0 | CreateImageFromRawData(mSize, mStride, mFormat, |
415 | 0 | mBuffer, mBufferLength, |
416 | 0 | mCropRect); |
417 | 0 |
|
418 | 0 | if (NS_WARN_IF(!image)) { |
419 | 0 | return false; |
420 | 0 | } |
421 | 0 | |
422 | 0 | image.forget(mImage); |
423 | 0 |
|
424 | 0 | return true; |
425 | 0 | } |
426 | | |
427 | | private: |
428 | | layers::Image** mImage; |
429 | | uint8_t* mBuffer; |
430 | | uint32_t mBufferLength; |
431 | | uint32_t mStride; |
432 | | gfx::SurfaceFormat mFormat; |
433 | | gfx::IntSize mSize; |
434 | | const Maybe<IntRect>& mCropRect; |
435 | | }; |
436 | | |
437 | | static bool |
438 | | CheckSecurityForHTMLElements(bool aIsWriteOnly, bool aCORSUsed, nsIPrincipal* aPrincipal) |
439 | 0 | { |
440 | 0 | if (aIsWriteOnly || !aPrincipal) { |
441 | 0 | return false; |
442 | 0 | } |
443 | 0 | |
444 | 0 | if (!aCORSUsed) { |
445 | 0 | nsIGlobalObject* incumbentSettingsObject = GetIncumbentGlobal(); |
446 | 0 | if (NS_WARN_IF(!incumbentSettingsObject)) { |
447 | 0 | return false; |
448 | 0 | } |
449 | 0 | |
450 | 0 | nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull(); |
451 | 0 | if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) { |
452 | 0 | return false; |
453 | 0 | } |
454 | 0 | } |
455 | 0 | |
456 | 0 | return true; |
457 | 0 | } |
458 | | |
459 | | static bool |
460 | | CheckSecurityForHTMLElements(const nsLayoutUtils::SurfaceFromElementResult& aRes) |
461 | 0 | { |
462 | 0 | return CheckSecurityForHTMLElements(aRes.mIsWriteOnly, aRes.mCORSUsed, aRes.mPrincipal); |
463 | 0 | } |
464 | | |
465 | | /* |
466 | | * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the |
467 | | * security checking. |
468 | | */ |
469 | | template<class HTMLElementType> |
470 | | static already_AddRefed<SourceSurface> |
471 | | GetSurfaceFromElement(nsIGlobalObject* aGlobal, HTMLElementType& aElement, ErrorResult& aRv) |
472 | 0 | { |
473 | 0 | nsLayoutUtils::SurfaceFromElementResult res = |
474 | 0 | nsLayoutUtils::SurfaceFromElement(&aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE); |
475 | 0 |
|
476 | 0 | // check origin-clean |
477 | 0 | if (!CheckSecurityForHTMLElements(res)) { |
478 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
479 | 0 | return nullptr; |
480 | 0 | } |
481 | 0 | |
482 | 0 | RefPtr<SourceSurface> surface = res.GetSourceSurface(); |
483 | 0 |
|
484 | 0 | if (NS_WARN_IF(!surface)) { |
485 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
486 | 0 | return nullptr; |
487 | 0 | } |
488 | 0 | |
489 | 0 | return surface.forget(); |
490 | 0 | } Unexecuted instantiation: Unified_cpp_dom_canvas0.cpp:already_AddRefed<mozilla::gfx::SourceSurface> mozilla::dom::GetSurfaceFromElement<mozilla::dom::HTMLImageElement>(nsIGlobalObject*, mozilla::dom::HTMLImageElement&, mozilla::ErrorResult&) Unexecuted instantiation: Unified_cpp_dom_canvas0.cpp:already_AddRefed<mozilla::gfx::SourceSurface> mozilla::dom::GetSurfaceFromElement<mozilla::dom::HTMLCanvasElement>(nsIGlobalObject*, mozilla::dom::HTMLCanvasElement&, mozilla::ErrorResult&) |
491 | | |
492 | | /* |
493 | | * The specification doesn't allow to create an ImegeBitmap from a vector image. |
494 | | * This function is used to check if the given HTMLImageElement contains a |
495 | | * raster image. |
496 | | */ |
497 | | static bool |
498 | | HasRasterImage(HTMLImageElement& aImageEl) |
499 | 0 | { |
500 | 0 | nsresult rv; |
501 | 0 |
|
502 | 0 | nsCOMPtr<imgIRequest> imgRequest; |
503 | 0 | rv = aImageEl.GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, |
504 | 0 | getter_AddRefs(imgRequest)); |
505 | 0 | if (NS_SUCCEEDED(rv) && imgRequest) { |
506 | 0 | nsCOMPtr<imgIContainer> imgContainer; |
507 | 0 | rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); |
508 | 0 | if (NS_SUCCEEDED(rv) && imgContainer && |
509 | 0 | imgContainer->GetType() == imgIContainer::TYPE_RASTER) { |
510 | 0 | return true; |
511 | 0 | } |
512 | 0 | } |
513 | 0 | |
514 | 0 | return false; |
515 | 0 | } |
516 | | |
517 | | ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData, |
518 | | gfxAlphaType aAlphaType) |
519 | | : mParent(aGlobal) |
520 | | , mData(aData) |
521 | | , mSurface(nullptr) |
522 | | , mDataWrapper(new ImageUtils(mData)) |
523 | | , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height) |
524 | | , mAlphaType(aAlphaType) |
525 | | , mIsCroppingAreaOutSideOfSourceImage(false) |
526 | | , mAllocatedImageData(false) |
527 | 0 | { |
528 | 0 | MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor."); |
529 | 0 |
|
530 | 0 | mShutdownObserver = new ImageBitmapShutdownObserver(this); |
531 | 0 | mShutdownObserver->RegisterObserver(); |
532 | 0 | } |
533 | | |
534 | | ImageBitmap::~ImageBitmap() |
535 | 0 | { |
536 | 0 | if (mShutdownObserver) { |
537 | 0 | mShutdownObserver->Clear(); |
538 | 0 | mShutdownObserver->UnregisterObserver(); |
539 | 0 | mShutdownObserver = nullptr; |
540 | 0 | } |
541 | 0 | } |
542 | | |
543 | | JSObject* |
544 | | ImageBitmap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
545 | 0 | { |
546 | 0 | return ImageBitmap_Binding::Wrap(aCx, this, aGivenProto); |
547 | 0 | } |
548 | | |
549 | | void |
550 | | ImageBitmap::Close() |
551 | 0 | { |
552 | 0 | mData = nullptr; |
553 | 0 | mSurface = nullptr; |
554 | 0 | mDataWrapper = nullptr; |
555 | 0 | mPictureRect.SetEmpty(); |
556 | 0 | } |
557 | | |
558 | | void |
559 | | ImageBitmap::OnShutdown() |
560 | 0 | { |
561 | 0 | mShutdownObserver = nullptr; |
562 | 0 |
|
563 | 0 | Close(); |
564 | 0 | } |
565 | | |
566 | | void |
567 | | ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv) |
568 | 0 | { |
569 | 0 | mPictureRect = FixUpNegativeDimension(aRect, aRv); |
570 | 0 | } |
571 | | |
572 | | void |
573 | | ImageBitmap::SetIsCroppingAreaOutSideOfSourceImage(const IntSize& aSourceSize, |
574 | | const Maybe<IntRect>& aCroppingRect) |
575 | 0 | { |
576 | 0 | // No cropping at all. |
577 | 0 | if (aCroppingRect.isNothing()) { |
578 | 0 | mIsCroppingAreaOutSideOfSourceImage = false; |
579 | 0 | return; |
580 | 0 | } |
581 | 0 | |
582 | 0 | if (aCroppingRect->X() < 0 || aCroppingRect->Y() < 0 || |
583 | 0 | aCroppingRect->Width() > aSourceSize.width || |
584 | 0 | aCroppingRect->Height() > aSourceSize.height) { |
585 | 0 | mIsCroppingAreaOutSideOfSourceImage = true; |
586 | 0 | } |
587 | 0 | } |
588 | | |
589 | | static already_AddRefed<SourceSurface> |
590 | | ConvertColorFormatIfNeeded(RefPtr<SourceSurface> aSurface) |
591 | 0 | { |
592 | 0 | const SurfaceFormat srcFormat = aSurface->GetFormat(); |
593 | 0 | if (srcFormat == SurfaceFormat::R8G8B8A8 || |
594 | 0 | srcFormat == SurfaceFormat::B8G8R8A8 || |
595 | 0 | srcFormat == SurfaceFormat::R8G8B8X8 || |
596 | 0 | srcFormat == SurfaceFormat::B8G8R8X8 || |
597 | 0 | srcFormat == SurfaceFormat::A8R8G8B8 || |
598 | 0 | srcFormat == SurfaceFormat::X8R8G8B8) { |
599 | 0 | return aSurface.forget(); |
600 | 0 | } |
601 | 0 | |
602 | 0 | if (srcFormat == SurfaceFormat::A8 || |
603 | 0 | srcFormat == SurfaceFormat::Depth) { |
604 | 0 | return nullptr; |
605 | 0 | } |
606 | 0 | |
607 | 0 | const int bytesPerPixel = BytesPerPixel(SurfaceFormat::B8G8R8A8); |
608 | 0 | const IntSize dstSize = aSurface->GetSize(); |
609 | 0 | const uint32_t dstStride = dstSize.width * bytesPerPixel; |
610 | 0 |
|
611 | 0 | RefPtr<DataSourceSurface> dstDataSurface = |
612 | 0 | Factory::CreateDataSourceSurfaceWithStride(dstSize, |
613 | 0 | SurfaceFormat::B8G8R8A8, |
614 | 0 | dstStride); |
615 | 0 | if (NS_WARN_IF(!dstDataSurface)) { |
616 | 0 | return nullptr; |
617 | 0 | } |
618 | 0 | |
619 | 0 | RefPtr<DataSourceSurface> srcDataSurface = aSurface->GetDataSurface(); |
620 | 0 | if (NS_WARN_IF(!srcDataSurface)) { |
621 | 0 | return nullptr; |
622 | 0 | } |
623 | 0 | |
624 | 0 | DataSourceSurface::ScopedMap srcMap(srcDataSurface, DataSourceSurface::READ); |
625 | 0 | DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE); |
626 | 0 | if (NS_WARN_IF(!srcMap.IsMapped()) || NS_WARN_IF(!dstMap.IsMapped())) { |
627 | 0 | return nullptr; |
628 | 0 | } |
629 | 0 | |
630 | 0 | int rv = 0; |
631 | 0 | if (srcFormat == SurfaceFormat::R8G8B8) { |
632 | 0 | rv = RGB24ToBGRA32(srcMap.GetData(), srcMap.GetStride(), |
633 | 0 | dstMap.GetData(), dstMap.GetStride(), |
634 | 0 | dstSize.width, dstSize.height); |
635 | 0 | } else if (srcFormat == SurfaceFormat::B8G8R8) { |
636 | 0 | rv = BGR24ToBGRA32(srcMap.GetData(), srcMap.GetStride(), |
637 | 0 | dstMap.GetData(), dstMap.GetStride(), |
638 | 0 | dstSize.width, dstSize.height); |
639 | 0 | } else if (srcFormat == SurfaceFormat::HSV) { |
640 | 0 | rv = HSVToBGRA32((const float*)srcMap.GetData(), srcMap.GetStride(), |
641 | 0 | dstMap.GetData(), dstMap.GetStride(), |
642 | 0 | dstSize.width, dstSize.height); |
643 | 0 | } else if (srcFormat == SurfaceFormat::Lab) { |
644 | 0 | rv = LabToBGRA32((const float*)srcMap.GetData(), srcMap.GetStride(), |
645 | 0 | dstMap.GetData(), dstMap.GetStride(), |
646 | 0 | dstSize.width, dstSize.height); |
647 | 0 | } |
648 | 0 |
|
649 | 0 | if (NS_WARN_IF(rv != 0)) { |
650 | 0 | return nullptr; |
651 | 0 | } |
652 | 0 | |
653 | 0 | return dstDataSurface.forget(); |
654 | 0 | } |
655 | | |
656 | | /* |
657 | | * The functionality of PrepareForDrawTarget method: |
658 | | * (1) Get a SourceSurface from the mData (which is a layers::Image). |
659 | | * (2) Convert the SourceSurface to format B8G8R8A8 if the original format is |
660 | | * R8G8B8, B8G8R8, HSV or Lab. |
661 | | * Note: if the original format is A8 or Depth, then return null directly. |
662 | | * (3) Do cropping if the size of SourceSurface does not equal to the |
663 | | * mPictureRect. |
664 | | * (4) Pre-multiply alpha if needed. |
665 | | */ |
666 | | already_AddRefed<SourceSurface> |
667 | | ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget) |
668 | 0 | { |
669 | 0 | MOZ_ASSERT(aTarget); |
670 | 0 |
|
671 | 0 | if (!mData) { |
672 | 0 | return nullptr; |
673 | 0 | } |
674 | 0 | |
675 | 0 | if (!mSurface) { |
676 | 0 | mSurface = mData->GetAsSourceSurface(); |
677 | 0 |
|
678 | 0 | if (!mSurface) { |
679 | 0 | return nullptr; |
680 | 0 | } |
681 | 0 | } |
682 | 0 | |
683 | 0 | // Check if we need to convert the format. |
684 | 0 | // Convert R8G8B8/B8G8R8/HSV/Lab to B8G8R8A8. |
685 | 0 | // Return null if the original format is A8 or Depth. |
686 | 0 | mSurface = ConvertColorFormatIfNeeded(mSurface); |
687 | 0 | if (NS_WARN_IF(!mSurface)) { |
688 | 0 | return nullptr; |
689 | 0 | } |
690 | 0 | |
691 | 0 | RefPtr<DrawTarget> target = aTarget; |
692 | 0 | IntRect surfRect(0, 0, mSurface->GetSize().width, mSurface->GetSize().height); |
693 | 0 |
|
694 | 0 | // Check if we still need to crop our surface |
695 | 0 | if (!mPictureRect.IsEqualEdges(surfRect)) { |
696 | 0 |
|
697 | 0 | IntRect surfPortion = surfRect.Intersect(mPictureRect); |
698 | 0 |
|
699 | 0 | // the crop lies entirely outside the surface area, nothing to draw |
700 | 0 | if (surfPortion.IsEmpty()) { |
701 | 0 | mSurface = nullptr; |
702 | 0 | RefPtr<gfx::SourceSurface> surface(mSurface); |
703 | 0 | return surface.forget(); |
704 | 0 | } |
705 | 0 | |
706 | 0 | IntPoint dest(std::max(0, surfPortion.X() - mPictureRect.X()), |
707 | 0 | std::max(0, surfPortion.Y() - mPictureRect.Y())); |
708 | 0 |
|
709 | 0 | // We must initialize this target with mPictureRect.Size() because the |
710 | 0 | // specification states that if the cropping area is given, then return an |
711 | 0 | // ImageBitmap with the size equals to the cropping area. |
712 | 0 | target = target->CreateSimilarDrawTarget(mPictureRect.Size(), |
713 | 0 | target->GetFormat()); |
714 | 0 |
|
715 | 0 | if (!target) { |
716 | 0 | mSurface = nullptr; |
717 | 0 | RefPtr<gfx::SourceSurface> surface(mSurface); |
718 | 0 | return surface.forget(); |
719 | 0 | } |
720 | 0 | |
721 | 0 | target->CopySurface(mSurface, surfPortion, dest); |
722 | 0 | mSurface = target->Snapshot(); |
723 | 0 |
|
724 | 0 | // Make mCropRect match new surface we've cropped to |
725 | 0 | mPictureRect.MoveTo(0, 0); |
726 | 0 | } |
727 | 0 |
|
728 | 0 | // Pre-multiply alpha here. |
729 | 0 | // Ignore this step if the source surface does not have alpha channel; this |
730 | 0 | // kind of source surfaces might come form layers::PlanarYCbCrImage. |
731 | 0 | if (mAlphaType == gfxAlphaType::NonPremult && |
732 | 0 | !IsOpaque(mSurface->GetFormat())) |
733 | 0 | { |
734 | 0 | MOZ_ASSERT(mSurface->GetFormat() == SurfaceFormat::R8G8B8A8 || |
735 | 0 | mSurface->GetFormat() == SurfaceFormat::B8G8R8A8 || |
736 | 0 | mSurface->GetFormat() == SurfaceFormat::A8R8G8B8); |
737 | 0 |
|
738 | 0 | RefPtr<DataSourceSurface> dstSurface = mSurface->GetDataSurface(); |
739 | 0 | MOZ_ASSERT(dstSurface); |
740 | 0 |
|
741 | 0 | RefPtr<DataSourceSurface> srcSurface; |
742 | 0 | DataSourceSurface::MappedSurface srcMap; |
743 | 0 | DataSourceSurface::MappedSurface dstMap; |
744 | 0 |
|
745 | 0 | if (dstSurface->Map(DataSourceSurface::MapType::READ_WRITE, &dstMap)) { |
746 | 0 | srcMap = dstMap; |
747 | 0 | } else { |
748 | 0 | srcSurface = dstSurface; |
749 | 0 | if (!srcSurface->Map(DataSourceSurface::READ, &srcMap)) { |
750 | 0 | gfxCriticalError() << "Failed to map source surface for premultiplying alpha."; |
751 | 0 | return nullptr; |
752 | 0 | } |
753 | 0 |
|
754 | 0 | dstSurface = Factory::CreateDataSourceSurface(srcSurface->GetSize(), srcSurface->GetFormat()); |
755 | 0 |
|
756 | 0 | if (!dstSurface || !dstSurface->Map(DataSourceSurface::MapType::WRITE, &dstMap)) { |
757 | 0 | gfxCriticalError() << "Failed to map destination surface for premultiplying alpha."; |
758 | 0 | srcSurface->Unmap(); |
759 | 0 | return nullptr; |
760 | 0 | } |
761 | 0 | } |
762 | 0 |
|
763 | 0 | PremultiplyData(srcMap.mData, srcMap.mStride, mSurface->GetFormat(), |
764 | 0 | dstMap.mData, dstMap.mStride, mSurface->GetFormat(), |
765 | 0 | dstSurface->GetSize()); |
766 | 0 |
|
767 | 0 | dstSurface->Unmap(); |
768 | 0 | if (srcSurface) { |
769 | 0 | srcSurface->Unmap(); |
770 | 0 | } |
771 | 0 |
|
772 | 0 | mSurface = dstSurface; |
773 | 0 | } |
774 | 0 |
|
775 | 0 | // Replace our surface with one optimized for the target we're about to draw |
776 | 0 | // to, under the assumption it'll likely be drawn again to that target. |
777 | 0 | // This call should be a no-op for already-optimized surfaces |
778 | 0 | mSurface = target->OptimizeSourceSurface(mSurface); |
779 | 0 |
|
780 | 0 | RefPtr<gfx::SourceSurface> surface(mSurface); |
781 | 0 | return surface.forget(); |
782 | 0 | } |
783 | | |
784 | | already_AddRefed<layers::Image> |
785 | | ImageBitmap::TransferAsImage() |
786 | 0 | { |
787 | 0 | RefPtr<layers::Image> image = mData; |
788 | 0 | Close(); |
789 | 0 | return image.forget(); |
790 | 0 | } |
791 | | |
792 | | UniquePtr<ImageBitmapCloneData> |
793 | | ImageBitmap::ToCloneData() const |
794 | 0 | { |
795 | 0 | if (!mData) { |
796 | 0 | // A closed image cannot be cloned. |
797 | 0 | return nullptr; |
798 | 0 | } |
799 | 0 | |
800 | 0 | UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData()); |
801 | 0 | result->mPictureRect = mPictureRect; |
802 | 0 | result->mAlphaType = mAlphaType; |
803 | 0 | result->mIsCroppingAreaOutSideOfSourceImage = mIsCroppingAreaOutSideOfSourceImage; |
804 | 0 | RefPtr<SourceSurface> surface = mData->GetAsSourceSurface(); |
805 | 0 | result->mSurface = surface->GetDataSurface(); |
806 | 0 | MOZ_ASSERT(result->mSurface); |
807 | 0 |
|
808 | 0 | return result; |
809 | 0 | } |
810 | | |
811 | | /* static */ already_AddRefed<ImageBitmap> |
812 | | ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal, |
813 | | ImageBitmapCloneData* aData) |
814 | 0 | { |
815 | 0 | RefPtr<layers::Image> data = CreateImageFromSurface(aData->mSurface); |
816 | 0 |
|
817 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aData->mAlphaType); |
818 | 0 |
|
819 | 0 | ret->mAllocatedImageData = true; |
820 | 0 |
|
821 | 0 | ret->mIsCroppingAreaOutSideOfSourceImage = |
822 | 0 | aData->mIsCroppingAreaOutSideOfSourceImage; |
823 | 0 |
|
824 | 0 | ErrorResult rv; |
825 | 0 | ret->SetPictureRect(aData->mPictureRect, rv); |
826 | 0 | return ret.forget(); |
827 | 0 | } |
828 | | |
829 | | /* static */ already_AddRefed<ImageBitmap> |
830 | | ImageBitmap::CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal, |
831 | | OffscreenCanvas& aOffscreenCanvas, |
832 | | ErrorResult& aRv) |
833 | 0 | { |
834 | 0 | // Check origin-clean. |
835 | 0 | if (aOffscreenCanvas.IsWriteOnly()) { |
836 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
837 | 0 | return nullptr; |
838 | 0 | } |
839 | 0 | |
840 | 0 | nsLayoutUtils::SurfaceFromElementResult res = |
841 | 0 | nsLayoutUtils::SurfaceFromOffscreenCanvas(&aOffscreenCanvas, |
842 | 0 | nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE); |
843 | 0 |
|
844 | 0 | RefPtr<SourceSurface> surface = res.GetSourceSurface(); |
845 | 0 |
|
846 | 0 | if (NS_WARN_IF(!surface)) { |
847 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
848 | 0 | return nullptr; |
849 | 0 | } |
850 | 0 | |
851 | 0 | RefPtr<layers::Image> data = |
852 | 0 | CreateImageFromSurface(surface); |
853 | 0 |
|
854 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data); |
855 | 0 |
|
856 | 0 | ret->mAllocatedImageData = true; |
857 | 0 |
|
858 | 0 | return ret.forget(); |
859 | 0 | } |
860 | | |
861 | | /* static */ already_AddRefed<ImageBitmap> |
862 | | ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl, |
863 | | const Maybe<IntRect>& aCropRect, ErrorResult& aRv) |
864 | 0 | { |
865 | 0 | // Check if the image element is completely available or not. |
866 | 0 | if (!aImageEl.Complete()) { |
867 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
868 | 0 | return nullptr; |
869 | 0 | } |
870 | 0 | |
871 | 0 | // Check if the image element is a bitmap (e.g. it's a vector graphic) or not. |
872 | 0 | if (!HasRasterImage(aImageEl)) { |
873 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
874 | 0 | return nullptr; |
875 | 0 | } |
876 | 0 | |
877 | 0 | // Get the SourceSurface out from the image element and then do security |
878 | 0 | // checking. |
879 | 0 | RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl, aRv); |
880 | 0 |
|
881 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
882 | 0 | return nullptr; |
883 | 0 | } |
884 | 0 | |
885 | 0 | // Create ImageBitmap. |
886 | 0 | RefPtr<layers::Image> data = CreateImageFromSurface(surface); |
887 | 0 |
|
888 | 0 | if (NS_WARN_IF(!data)) { |
889 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
890 | 0 | return nullptr; |
891 | 0 | } |
892 | 0 | |
893 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data); |
894 | 0 |
|
895 | 0 | // Set the picture rectangle. |
896 | 0 | if (ret && aCropRect.isSome()) { |
897 | 0 | ret->SetPictureRect(aCropRect.ref(), aRv); |
898 | 0 | } |
899 | 0 |
|
900 | 0 | // Set mIsCroppingAreaOutSideOfSourceImage. |
901 | 0 | ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect); |
902 | 0 |
|
903 | 0 | return ret.forget(); |
904 | 0 | } |
905 | | |
906 | | /* static */ already_AddRefed<ImageBitmap> |
907 | | ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl, |
908 | | const Maybe<IntRect>& aCropRect, ErrorResult& aRv) |
909 | 0 | { |
910 | 0 | aVideoEl.MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_IMAGEBITMAP); |
911 | 0 |
|
912 | 0 | // Check network state. |
913 | 0 | if (aVideoEl.NetworkState() == NETWORK_EMPTY) { |
914 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
915 | 0 | return nullptr; |
916 | 0 | } |
917 | 0 | |
918 | 0 | // Check ready state. |
919 | 0 | // Cannot be HTMLMediaElement::HAVE_NOTHING or HTMLMediaElement::HAVE_METADATA. |
920 | 0 | if (aVideoEl.ReadyState() <= HAVE_METADATA) { |
921 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
922 | 0 | return nullptr; |
923 | 0 | } |
924 | 0 | |
925 | 0 | // Check security. |
926 | 0 | nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentVideoPrincipal(); |
927 | 0 | bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE; |
928 | 0 | if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) { |
929 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
930 | 0 | return nullptr; |
931 | 0 | } |
932 | 0 | |
933 | 0 | // Create ImageBitmap. |
934 | 0 | RefPtr<layers::Image> data = aVideoEl.GetCurrentImage(); |
935 | 0 | if (!data) { |
936 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
937 | 0 | return nullptr; |
938 | 0 | } |
939 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data); |
940 | 0 |
|
941 | 0 | // Set the picture rectangle. |
942 | 0 | if (ret && aCropRect.isSome()) { |
943 | 0 | ret->SetPictureRect(aCropRect.ref(), aRv); |
944 | 0 | } |
945 | 0 |
|
946 | 0 | // Set mIsCroppingAreaOutSideOfSourceImage. |
947 | 0 | ret->SetIsCroppingAreaOutSideOfSourceImage(data->GetSize(), aCropRect); |
948 | 0 |
|
949 | 0 | return ret.forget(); |
950 | 0 | } |
951 | | |
952 | | /* static */ already_AddRefed<ImageBitmap> |
953 | | ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl, |
954 | | const Maybe<IntRect>& aCropRect, ErrorResult& aRv) |
955 | 0 | { |
956 | 0 | if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) { |
957 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
958 | 0 | return nullptr; |
959 | 0 | } |
960 | 0 | |
961 | 0 | RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aCanvasEl, aRv); |
962 | 0 |
|
963 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
964 | 0 | return nullptr; |
965 | 0 | } |
966 | 0 | |
967 | 0 | // Crop the source surface if needed. |
968 | 0 | RefPtr<SourceSurface> croppedSurface; |
969 | 0 | IntRect cropRect = aCropRect.valueOr(IntRect()); |
970 | 0 |
|
971 | 0 | // If the HTMLCanvasElement's rendering context is WebGL, then the snapshot |
972 | 0 | // we got from the HTMLCanvasElement is a DataSourceSurface which is a copy |
973 | 0 | // of the rendering context. We handle cropping in this case. |
974 | 0 | bool needToReportMemoryAllocation = false; |
975 | 0 | if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 || |
976 | 0 | aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2) && |
977 | 0 | aCropRect.isSome()) { |
978 | 0 | RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); |
979 | 0 | croppedSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect); |
980 | 0 | cropRect.MoveTo(0, 0); |
981 | 0 | needToReportMemoryAllocation = true; |
982 | 0 | } |
983 | 0 | else { |
984 | 0 | croppedSurface = surface; |
985 | 0 | } |
986 | 0 |
|
987 | 0 | if (NS_WARN_IF(!croppedSurface)) { |
988 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
989 | 0 | return nullptr; |
990 | 0 | } |
991 | 0 | |
992 | 0 | // Create an Image from the SourceSurface. |
993 | 0 | RefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface); |
994 | 0 |
|
995 | 0 | if (NS_WARN_IF(!data)) { |
996 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
997 | 0 | return nullptr; |
998 | 0 | } |
999 | 0 | |
1000 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data); |
1001 | 0 |
|
1002 | 0 | if (needToReportMemoryAllocation) { |
1003 | 0 | ret->mAllocatedImageData = true; |
1004 | 0 | } |
1005 | 0 |
|
1006 | 0 | // Set the picture rectangle. |
1007 | 0 | if (ret && aCropRect.isSome()) { |
1008 | 0 | ret->SetPictureRect(cropRect, aRv); |
1009 | 0 | } |
1010 | 0 |
|
1011 | 0 | // Set mIsCroppingAreaOutSideOfSourceImage. |
1012 | 0 | ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect); |
1013 | 0 |
|
1014 | 0 | return ret.forget(); |
1015 | 0 | } |
1016 | | |
1017 | | /* static */ already_AddRefed<ImageBitmap> |
1018 | | ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData, |
1019 | | const Maybe<IntRect>& aCropRect, ErrorResult& aRv) |
1020 | 0 | { |
1021 | 0 | // Copy data into SourceSurface. |
1022 | 0 | dom::Uint8ClampedArray array; |
1023 | 0 | DebugOnly<bool> inited = array.Init(aImageData.GetDataObject()); |
1024 | 0 | MOZ_ASSERT(inited); |
1025 | 0 |
|
1026 | 0 | array.ComputeLengthAndData(); |
1027 | 0 | const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8; |
1028 | 0 | // ImageData's underlying data is not alpha-premultiplied. |
1029 | 0 | const auto alphaType = gfxAlphaType::NonPremult; |
1030 | 0 | const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT); |
1031 | 0 | const uint32_t imageWidth = aImageData.Width(); |
1032 | 0 | const uint32_t imageHeight = aImageData.Height(); |
1033 | 0 | const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL; |
1034 | 0 | const uint32_t dataLength = array.Length(); |
1035 | 0 | const gfx::IntSize imageSize(imageWidth, imageHeight); |
1036 | 0 |
|
1037 | 0 | // Check the ImageData is neutered or not. |
1038 | 0 | if (imageWidth == 0 || imageHeight == 0 || |
1039 | 0 | (imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) { |
1040 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
1041 | 0 | return nullptr; |
1042 | 0 | } |
1043 | 0 | |
1044 | 0 | // Create and Crop the raw data into a layers::Image |
1045 | 0 | RefPtr<layers::Image> data; |
1046 | 0 | if (NS_IsMainThread()) { |
1047 | 0 | data = CreateImageFromRawData(imageSize, imageStride, FORMAT, |
1048 | 0 | array.Data(), dataLength, |
1049 | 0 | aCropRect); |
1050 | 0 | } else { |
1051 | 0 | RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task |
1052 | 0 | = new CreateImageFromRawDataInMainThreadSyncTask(array.Data(), |
1053 | 0 | dataLength, |
1054 | 0 | imageStride, |
1055 | 0 | FORMAT, |
1056 | 0 | imageSize, |
1057 | 0 | aCropRect, |
1058 | 0 | getter_AddRefs(data)); |
1059 | 0 | task->Dispatch(Canceling, aRv); |
1060 | 0 | } |
1061 | 0 |
|
1062 | 0 | if (NS_WARN_IF(!data)) { |
1063 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
1064 | 0 | return nullptr; |
1065 | 0 | } |
1066 | 0 | |
1067 | 0 | // Create an ImageBimtap. |
1068 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, alphaType); |
1069 | 0 |
|
1070 | 0 | ret->mAllocatedImageData = true; |
1071 | 0 |
|
1072 | 0 | // The cropping information has been handled in the CreateImageFromRawData() |
1073 | 0 | // function. |
1074 | 0 |
|
1075 | 0 | // Set mIsCroppingAreaOutSideOfSourceImage. |
1076 | 0 | ret->SetIsCroppingAreaOutSideOfSourceImage(imageSize, aCropRect); |
1077 | 0 |
|
1078 | 0 | return ret.forget(); |
1079 | 0 | } |
1080 | | |
1081 | | /* static */ already_AddRefed<ImageBitmap> |
1082 | | ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx, |
1083 | | const Maybe<IntRect>& aCropRect, ErrorResult& aRv) |
1084 | 0 | { |
1085 | 0 | // Check origin-clean. |
1086 | 0 | if (aCanvasCtx.GetCanvas()->IsWriteOnly()) { |
1087 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
1088 | 0 | return nullptr; |
1089 | 0 | } |
1090 | 0 | |
1091 | 0 | RefPtr<SourceSurface> surface = aCanvasCtx.GetSurfaceSnapshot(); |
1092 | 0 |
|
1093 | 0 | if (NS_WARN_IF(!surface)) { |
1094 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
1095 | 0 | return nullptr; |
1096 | 0 | } |
1097 | 0 | |
1098 | 0 | const IntSize surfaceSize = surface->GetSize(); |
1099 | 0 | if (surfaceSize.width == 0 || surfaceSize.height == 0) { |
1100 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
1101 | 0 | return nullptr; |
1102 | 0 | } |
1103 | 0 | |
1104 | 0 | RefPtr<layers::Image> data = CreateImageFromSurface(surface); |
1105 | 0 |
|
1106 | 0 | if (NS_WARN_IF(!data)) { |
1107 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
1108 | 0 | return nullptr; |
1109 | 0 | } |
1110 | 0 | |
1111 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data); |
1112 | 0 |
|
1113 | 0 | ret->mAllocatedImageData = true; |
1114 | 0 |
|
1115 | 0 | // Set the picture rectangle. |
1116 | 0 | if (ret && aCropRect.isSome()) { |
1117 | 0 | ret->SetPictureRect(aCropRect.ref(), aRv); |
1118 | 0 | } |
1119 | 0 |
|
1120 | 0 | // Set mIsCroppingAreaOutSideOfSourceImage. |
1121 | 0 | ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect); |
1122 | 0 |
|
1123 | 0 | return ret.forget(); |
1124 | 0 | } |
1125 | | |
1126 | | /* static */ already_AddRefed<ImageBitmap> |
1127 | | ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap, |
1128 | | const Maybe<IntRect>& aCropRect, ErrorResult& aRv) |
1129 | 0 | { |
1130 | 0 | if (!aImageBitmap.mData) { |
1131 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
1132 | 0 | return nullptr; |
1133 | 0 | } |
1134 | 0 | |
1135 | 0 | RefPtr<layers::Image> data = aImageBitmap.mData; |
1136 | 0 | RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mAlphaType); |
1137 | 0 |
|
1138 | 0 | // Set the picture rectangle. |
1139 | 0 | if (ret && aCropRect.isSome()) { |
1140 | 0 | ret->SetPictureRect(aCropRect.ref(), aRv); |
1141 | 0 | } |
1142 | 0 |
|
1143 | 0 | // Set mIsCroppingAreaOutSideOfSourceImage. |
1144 | 0 | if (aImageBitmap.mIsCroppingAreaOutSideOfSourceImage == true) { |
1145 | 0 | ret->mIsCroppingAreaOutSideOfSourceImage = true; |
1146 | 0 | } else { |
1147 | 0 | ret->SetIsCroppingAreaOutSideOfSourceImage(aImageBitmap.mPictureRect.Size(), |
1148 | 0 | aCropRect); |
1149 | 0 | } |
1150 | 0 |
|
1151 | 0 | return ret.forget(); |
1152 | 0 | } |
1153 | | |
1154 | | class FulfillImageBitmapPromise |
1155 | | { |
1156 | | protected: |
1157 | | FulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap) |
1158 | | : mPromise(aPromise) |
1159 | | , mImageBitmap(aImageBitmap) |
1160 | 0 | { |
1161 | 0 | MOZ_ASSERT(aPromise); |
1162 | 0 | } |
1163 | | |
1164 | | void DoFulfillImageBitmapPromise() |
1165 | 0 | { |
1166 | 0 | mPromise->MaybeResolve(mImageBitmap); |
1167 | 0 | } |
1168 | | |
1169 | | private: |
1170 | | RefPtr<Promise> mPromise; |
1171 | | RefPtr<ImageBitmap> mImageBitmap; |
1172 | | }; |
1173 | | |
1174 | | class FulfillImageBitmapPromiseTask final : public Runnable, |
1175 | | public FulfillImageBitmapPromise |
1176 | | { |
1177 | | public: |
1178 | | FulfillImageBitmapPromiseTask(Promise* aPromise, ImageBitmap* aImageBitmap) |
1179 | | : Runnable("dom::FulfillImageBitmapPromiseTask") |
1180 | | , FulfillImageBitmapPromise(aPromise, aImageBitmap) |
1181 | 0 | { |
1182 | 0 | } |
1183 | | |
1184 | | NS_IMETHOD Run() override |
1185 | 0 | { |
1186 | 0 | DoFulfillImageBitmapPromise(); |
1187 | 0 | return NS_OK; |
1188 | 0 | } |
1189 | | }; |
1190 | | |
1191 | | class FulfillImageBitmapPromiseWorkerTask final : public WorkerSameThreadRunnable, |
1192 | | public FulfillImageBitmapPromise |
1193 | | { |
1194 | | public: |
1195 | | FulfillImageBitmapPromiseWorkerTask(Promise* aPromise, ImageBitmap* aImageBitmap) |
1196 | | : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()), |
1197 | | FulfillImageBitmapPromise(aPromise, aImageBitmap) |
1198 | 0 | { |
1199 | 0 | } |
1200 | | |
1201 | | bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
1202 | 0 | { |
1203 | 0 | DoFulfillImageBitmapPromise(); |
1204 | 0 | return true; |
1205 | 0 | } |
1206 | | }; |
1207 | | |
1208 | | static void |
1209 | | AsyncFulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap) |
1210 | 0 | { |
1211 | 0 | if (NS_IsMainThread()) { |
1212 | 0 | nsCOMPtr<nsIRunnable> task = |
1213 | 0 | new FulfillImageBitmapPromiseTask(aPromise, aImageBitmap); |
1214 | 0 | NS_DispatchToCurrentThread(task); // Actually, to the main-thread. |
1215 | 0 | } else { |
1216 | 0 | RefPtr<FulfillImageBitmapPromiseWorkerTask> task = |
1217 | 0 | new FulfillImageBitmapPromiseWorkerTask(aPromise, aImageBitmap); |
1218 | 0 | task->Dispatch(); // Actually, to the current worker-thread. |
1219 | 0 | } |
1220 | 0 | } |
1221 | | |
1222 | | class CreateImageBitmapFromBlobRunnable; |
1223 | | |
1224 | | class CreateImageBitmapFromBlob final : public CancelableRunnable |
1225 | | , public imgIContainerCallback |
1226 | | { |
1227 | | friend class CreateImageBitmapFromBlobRunnable; |
1228 | | |
1229 | | public: |
1230 | | NS_DECL_ISUPPORTS_INHERITED |
1231 | | NS_DECL_IMGICONTAINERCALLBACK |
1232 | | |
1233 | | static already_AddRefed<CreateImageBitmapFromBlob> |
1234 | | Create(Promise* aPromise, |
1235 | | nsIGlobalObject* aGlobal, |
1236 | | Blob& aBlob, |
1237 | | const Maybe<IntRect>& aCropRect, |
1238 | | nsIEventTarget* aMainThreadEventTarget); |
1239 | | |
1240 | | NS_IMETHOD Run() override |
1241 | 0 | { |
1242 | 0 | MOZ_ASSERT(IsCurrentThread()); |
1243 | 0 |
|
1244 | 0 | nsresult rv = StartDecodeAndCropBlob(); |
1245 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1246 | 0 | DecodeAndCropBlobCompletedMainThread(nullptr, rv); |
1247 | 0 | } |
1248 | 0 |
|
1249 | 0 | return NS_OK; |
1250 | 0 | } |
1251 | | |
1252 | | // Called by the WorkerRef. |
1253 | | void WorkerShuttingDown(); |
1254 | | |
1255 | | private: |
1256 | | CreateImageBitmapFromBlob(Promise* aPromise, |
1257 | | nsIGlobalObject* aGlobal, |
1258 | | already_AddRefed<nsIInputStream> aInputStream, |
1259 | | const nsACString& aMimeType, |
1260 | | const Maybe<IntRect>& aCropRect, |
1261 | | nsIEventTarget* aMainThreadEventTarget) |
1262 | | : CancelableRunnable("dom::CreateImageBitmapFromBlob") |
1263 | | , mMutex("dom::CreateImageBitmapFromBlob::mMutex") |
1264 | | , mPromise(aPromise) |
1265 | | , mGlobalObject(aGlobal) |
1266 | | , mInputStream(std::move(aInputStream)) |
1267 | | , mMimeType(aMimeType) |
1268 | | , mCropRect(aCropRect) |
1269 | | , mOriginalCropRect(aCropRect) |
1270 | | , mMainThreadEventTarget(aMainThreadEventTarget) |
1271 | | , mThread(GetCurrentVirtualThread()) |
1272 | 0 | { |
1273 | 0 | } |
1274 | | |
1275 | | virtual ~CreateImageBitmapFromBlob() |
1276 | 0 | { |
1277 | 0 | } |
1278 | | |
1279 | | bool IsCurrentThread() const |
1280 | 0 | { |
1281 | 0 | return mThread == GetCurrentVirtualThread(); |
1282 | 0 | } |
1283 | | |
1284 | | // Called on the owning thread. |
1285 | | nsresult StartDecodeAndCropBlob(); |
1286 | | |
1287 | | // Will be called when the decoding + cropping is completed on the |
1288 | | // main-thread. This could the not the owning thread! |
1289 | | void DecodeAndCropBlobCompletedMainThread(layers::Image* aImage, |
1290 | | nsresult aStatus); |
1291 | | |
1292 | | // Will be called when the decoding + cropping is completed on the owning |
1293 | | // thread. |
1294 | | void DecodeAndCropBlobCompletedOwningThread(layers::Image* aImage, |
1295 | | nsresult aStatus); |
1296 | | |
1297 | | // This is called on the main-thread only. |
1298 | | nsresult DecodeAndCropBlob(); |
1299 | | |
1300 | | Mutex mMutex; |
1301 | | |
1302 | | // The access to this object is protected by mutex but is always nullified on |
1303 | | // the owning thread. |
1304 | | RefPtr<ThreadSafeWorkerRef> mWorkerRef; |
1305 | | |
1306 | | // Touched only on the owning thread. |
1307 | | RefPtr<Promise> mPromise; |
1308 | | |
1309 | | // Touched only on the owning thread. |
1310 | | nsCOMPtr<nsIGlobalObject> mGlobalObject; |
1311 | | |
1312 | | nsCOMPtr<nsIInputStream> mInputStream; |
1313 | | nsCString mMimeType; |
1314 | | Maybe<IntRect> mCropRect; |
1315 | | Maybe<IntRect> mOriginalCropRect; |
1316 | | IntSize mSourceSize; |
1317 | | |
1318 | | nsCOMPtr<nsIEventTarget> mMainThreadEventTarget; |
1319 | | void* mThread; |
1320 | | }; |
1321 | | |
1322 | | NS_IMPL_ISUPPORTS_INHERITED(CreateImageBitmapFromBlob, CancelableRunnable, |
1323 | | imgIContainerCallback) |
1324 | | |
1325 | | class CreateImageBitmapFromBlobRunnable : public WorkerRunnable |
1326 | | { |
1327 | | public: |
1328 | | explicit CreateImageBitmapFromBlobRunnable(WorkerPrivate* aWorkerPrivate, |
1329 | | CreateImageBitmapFromBlob* aTask, |
1330 | | layers::Image* aImage, |
1331 | | nsresult aStatus) |
1332 | | : WorkerRunnable(aWorkerPrivate) |
1333 | | , mTask(aTask) |
1334 | | , mImage(aImage) |
1335 | | , mStatus(aStatus) |
1336 | 0 | {} |
1337 | | |
1338 | | bool |
1339 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
1340 | 0 | { |
1341 | 0 | mTask->DecodeAndCropBlobCompletedOwningThread(mImage, mStatus); |
1342 | 0 | return true; |
1343 | 0 | } |
1344 | | |
1345 | | private: |
1346 | | RefPtr<CreateImageBitmapFromBlob> mTask; |
1347 | | RefPtr<layers::Image> mImage; |
1348 | | nsresult mStatus; |
1349 | | }; |
1350 | | |
1351 | | static void |
1352 | | AsyncCreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal, |
1353 | | Blob& aBlob, const Maybe<IntRect>& aCropRect) |
1354 | 0 | { |
1355 | 0 | // Let's identify the main-thread event target. |
1356 | 0 | nsCOMPtr<nsIEventTarget> mainThreadEventTarget; |
1357 | 0 | if (NS_IsMainThread()) { |
1358 | 0 | mainThreadEventTarget = aGlobal->EventTargetFor(TaskCategory::Other); |
1359 | 0 | } else { |
1360 | 0 | WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); |
1361 | 0 | MOZ_ASSERT(workerPrivate); |
1362 | 0 | mainThreadEventTarget = workerPrivate->MainThreadEventTarget(); |
1363 | 0 | } |
1364 | 0 |
|
1365 | 0 | RefPtr<CreateImageBitmapFromBlob> task = |
1366 | 0 | CreateImageBitmapFromBlob::Create(aPromise, aGlobal, aBlob, aCropRect, |
1367 | 0 | mainThreadEventTarget); |
1368 | 0 | if (NS_WARN_IF(!task)) { |
1369 | 0 | aPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); |
1370 | 0 | return; |
1371 | 0 | } |
1372 | 0 | |
1373 | 0 | NS_DispatchToCurrentThread(task); |
1374 | 0 | } |
1375 | | |
1376 | | /* static */ already_AddRefed<Promise> |
1377 | | ImageBitmap::Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc, |
1378 | | const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv) |
1379 | 0 | { |
1380 | 0 | MOZ_ASSERT(aGlobal); |
1381 | 0 |
|
1382 | 0 | RefPtr<Promise> promise = Promise::Create(aGlobal, aRv); |
1383 | 0 |
|
1384 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
1385 | 0 | return nullptr; |
1386 | 0 | } |
1387 | 0 | |
1388 | 0 | if (aCropRect.isSome() && (aCropRect->Width() == 0 || aCropRect->Height() == 0)) { |
1389 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
1390 | 0 | return promise.forget(); |
1391 | 0 | } |
1392 | 0 | |
1393 | 0 | RefPtr<ImageBitmap> imageBitmap; |
1394 | 0 |
|
1395 | 0 | if (aSrc.IsHTMLImageElement()) { |
1396 | 0 | MOZ_ASSERT(NS_IsMainThread(), |
1397 | 0 | "Creating ImageBitmap from HTMLImageElement off the main thread."); |
1398 | 0 | imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLImageElement(), aCropRect, aRv); |
1399 | 0 | } else if (aSrc.IsHTMLVideoElement()) { |
1400 | 0 | MOZ_ASSERT(NS_IsMainThread(), |
1401 | 0 | "Creating ImageBitmap from HTMLVideoElement off the main thread."); |
1402 | 0 | imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLVideoElement(), aCropRect, aRv); |
1403 | 0 | } else if (aSrc.IsHTMLCanvasElement()) { |
1404 | 0 | MOZ_ASSERT(NS_IsMainThread(), |
1405 | 0 | "Creating ImageBitmap from HTMLCanvasElement off the main thread."); |
1406 | 0 | imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLCanvasElement(), aCropRect, aRv); |
1407 | 0 | } else if (aSrc.IsImageData()) { |
1408 | 0 | imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageData(), aCropRect, aRv); |
1409 | 0 | } else if (aSrc.IsCanvasRenderingContext2D()) { |
1410 | 0 | MOZ_ASSERT(NS_IsMainThread(), |
1411 | 0 | "Creating ImageBitmap from CanvasRenderingContext2D off the main thread."); |
1412 | 0 | imageBitmap = CreateInternal(aGlobal, aSrc.GetAsCanvasRenderingContext2D(), aCropRect, aRv); |
1413 | 0 | } else if (aSrc.IsImageBitmap()) { |
1414 | 0 | imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageBitmap(), aCropRect, aRv); |
1415 | 0 | } else if (aSrc.IsBlob()) { |
1416 | 0 | AsyncCreateImageBitmapFromBlob(promise, aGlobal, aSrc.GetAsBlob(), aCropRect); |
1417 | 0 | return promise.forget(); |
1418 | 0 | } else { |
1419 | 0 | aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); |
1420 | 0 | return nullptr; |
1421 | 0 | } |
1422 | 0 | |
1423 | 0 | if (!aRv.Failed()) { |
1424 | 0 | AsyncFulfillImageBitmapPromise(promise, imageBitmap); |
1425 | 0 | } |
1426 | 0 |
|
1427 | 0 | return promise.forget(); |
1428 | 0 | } |
1429 | | |
1430 | | /*static*/ JSObject* |
1431 | | ImageBitmap::ReadStructuredClone(JSContext* aCx, |
1432 | | JSStructuredCloneReader* aReader, |
1433 | | nsIGlobalObject* aParent, |
1434 | | const nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces, |
1435 | | uint32_t aIndex) |
1436 | 0 | { |
1437 | 0 | MOZ_ASSERT(aCx); |
1438 | 0 | MOZ_ASSERT(aReader); |
1439 | 0 | // aParent might be null. |
1440 | 0 |
|
1441 | 0 | uint32_t picRectX_; |
1442 | 0 | uint32_t picRectY_; |
1443 | 0 | uint32_t picRectWidth_; |
1444 | 0 | uint32_t picRectHeight_; |
1445 | 0 | uint32_t alphaType_; |
1446 | 0 | uint32_t isCroppingAreaOutSideOfSourceImage_; |
1447 | 0 |
|
1448 | 0 | if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) || |
1449 | 0 | !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) || |
1450 | 0 | !JS_ReadUint32Pair(aReader, &alphaType_, |
1451 | 0 | &isCroppingAreaOutSideOfSourceImage_)) { |
1452 | 0 | return nullptr; |
1453 | 0 | } |
1454 | 0 | |
1455 | 0 | int32_t picRectX = BitwiseCast<int32_t>(picRectX_); |
1456 | 0 | int32_t picRectY = BitwiseCast<int32_t>(picRectY_); |
1457 | 0 | int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_); |
1458 | 0 | int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_); |
1459 | 0 | const auto alphaType = BitwiseCast<gfxAlphaType>(alphaType_); |
1460 | 0 |
|
1461 | 0 | // Create a new ImageBitmap. |
1462 | 0 | MOZ_ASSERT(!aClonedSurfaces.IsEmpty()); |
1463 | 0 | MOZ_ASSERT(aIndex < aClonedSurfaces.Length()); |
1464 | 0 |
|
1465 | 0 | // RefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is |
1466 | 0 | // called because the static analysis thinks dereferencing XPCOM objects |
1467 | 0 | // can GC (because in some cases it can!), and a return statement with a |
1468 | 0 | // JSObject* type means that JSObject* is on the stack as a raw pointer |
1469 | 0 | // while destructors are running. |
1470 | 0 | JS::Rooted<JS::Value> value(aCx); |
1471 | 0 | { |
1472 | 0 | #ifdef FUZZING |
1473 | 0 | if (aIndex >= aClonedSurfaces.Length()) { |
1474 | 0 | return nullptr; |
1475 | 0 | } |
1476 | 0 | #endif |
1477 | 0 | RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]); |
1478 | 0 | RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aParent, img, alphaType); |
1479 | 0 |
|
1480 | 0 | imageBitmap->mIsCroppingAreaOutSideOfSourceImage = |
1481 | 0 | isCroppingAreaOutSideOfSourceImage_; |
1482 | 0 |
|
1483 | 0 | ErrorResult error; |
1484 | 0 | imageBitmap->SetPictureRect(IntRect(picRectX, picRectY, |
1485 | 0 | picRectWidth, picRectHeight), error); |
1486 | 0 | if (NS_WARN_IF(error.Failed())) { |
1487 | 0 | error.SuppressException(); |
1488 | 0 | return nullptr; |
1489 | 0 | } |
1490 | 0 | |
1491 | 0 | if (!GetOrCreateDOMReflector(aCx, imageBitmap, &value)) { |
1492 | 0 | return nullptr; |
1493 | 0 | } |
1494 | 0 | |
1495 | 0 | imageBitmap->mAllocatedImageData = true; |
1496 | 0 | } |
1497 | 0 |
|
1498 | 0 | return &(value.toObject()); |
1499 | 0 | } |
1500 | | |
1501 | | /*static*/ bool |
1502 | | ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter, |
1503 | | nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces, |
1504 | | ImageBitmap* aImageBitmap) |
1505 | 0 | { |
1506 | 0 | MOZ_ASSERT(aWriter); |
1507 | 0 | MOZ_ASSERT(aImageBitmap); |
1508 | 0 |
|
1509 | 0 | const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x); |
1510 | 0 | const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y); |
1511 | 0 | const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width); |
1512 | 0 | const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height); |
1513 | 0 | const uint32_t alphaType = BitwiseCast<uint32_t>(aImageBitmap->mAlphaType); |
1514 | 0 | const uint32_t isCroppingAreaOutSideOfSourceImage = aImageBitmap->mIsCroppingAreaOutSideOfSourceImage ? 1 : 0; |
1515 | 0 |
|
1516 | 0 | // Indexing the cloned surfaces and send the index to the receiver. |
1517 | 0 | uint32_t index = aClonedSurfaces.Length(); |
1518 | 0 |
|
1519 | 0 | if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) || |
1520 | 0 | NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) || |
1521 | 0 | NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight)) || |
1522 | 0 | NS_WARN_IF(!JS_WriteUint32Pair(aWriter, alphaType, |
1523 | 0 | isCroppingAreaOutSideOfSourceImage))) { |
1524 | 0 | return false; |
1525 | 0 | } |
1526 | 0 | |
1527 | 0 | RefPtr<SourceSurface> surface = |
1528 | 0 | aImageBitmap->mData->GetAsSourceSurface(); |
1529 | 0 | RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface(); |
1530 | 0 | RefPtr<DataSourceSurface> dstDataSurface; |
1531 | 0 | { |
1532 | 0 | // DataSourceSurfaceD2D1::GetStride() will call EnsureMapped implicitly and |
1533 | 0 | // won't Unmap after exiting function. So instead calling GetStride() |
1534 | 0 | // directly, using ScopedMap to get stride. |
1535 | 0 | DataSourceSurface::ScopedMap map(snapshot, DataSourceSurface::READ); |
1536 | 0 | dstDataSurface = |
1537 | 0 | Factory::CreateDataSourceSurfaceWithStride(snapshot->GetSize(), |
1538 | 0 | snapshot->GetFormat(), |
1539 | 0 | map.GetStride(), |
1540 | 0 | true); |
1541 | 0 | } |
1542 | 0 | if (NS_WARN_IF(!dstDataSurface)) { |
1543 | 0 | return false; |
1544 | 0 | } |
1545 | 0 | Factory::CopyDataSourceSurface(snapshot, dstDataSurface); |
1546 | 0 | aClonedSurfaces.AppendElement(dstDataSurface); |
1547 | 0 | return true; |
1548 | 0 | } |
1549 | | |
1550 | | // ImageBitmap extensions. |
1551 | | ImageBitmapFormat |
1552 | | ImageBitmap::FindOptimalFormat(const Optional<Sequence<ImageBitmapFormat>>& aPossibleFormats, |
1553 | | ErrorResult& aRv) |
1554 | 0 | { |
1555 | 0 | if (!mDataWrapper) { |
1556 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
1557 | 0 | return ImageBitmapFormat::EndGuard_; |
1558 | 0 | } |
1559 | 0 | |
1560 | 0 | ImageBitmapFormat platformFormat = mDataWrapper->GetFormat(); |
1561 | 0 |
|
1562 | 0 | if (!aPossibleFormats.WasPassed() || |
1563 | 0 | aPossibleFormats.Value().Contains(platformFormat)) { |
1564 | 0 | return platformFormat; |
1565 | 0 | } else { |
1566 | 0 | // If no matching is found, FindBestMatchingFromat() returns |
1567 | 0 | // ImageBitmapFormat::EndGuard_ and we throw an exception. |
1568 | 0 | ImageBitmapFormat optimalFormat = |
1569 | 0 | FindBestMatchingFromat(platformFormat, aPossibleFormats.Value()); |
1570 | 0 |
|
1571 | 0 | if (optimalFormat == ImageBitmapFormat::EndGuard_) { |
1572 | 0 | aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); |
1573 | 0 | } |
1574 | 0 |
|
1575 | 0 | return optimalFormat; |
1576 | 0 | } |
1577 | 0 | } |
1578 | | |
1579 | | int32_t |
1580 | | ImageBitmap::MappedDataLength(ImageBitmapFormat aFormat, ErrorResult& aRv) |
1581 | 0 | { |
1582 | 0 | if (!mDataWrapper) { |
1583 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
1584 | 0 | return 0; |
1585 | 0 | } |
1586 | 0 | |
1587 | 0 | if (aFormat == mDataWrapper->GetFormat()) { |
1588 | 0 | return mDataWrapper->GetBufferLength(); |
1589 | 0 | } else { |
1590 | 0 | return CalculateImageBufferSize(aFormat, Width(), Height()); |
1591 | 0 | } |
1592 | 0 | } |
1593 | | |
1594 | | template<typename T> |
1595 | | class MapDataIntoBufferSource |
1596 | | { |
1597 | | protected: |
1598 | | MapDataIntoBufferSource(JSContext* aCx, |
1599 | | Promise *aPromise, |
1600 | | ImageBitmap *aImageBitmap, |
1601 | | const T& aBuffer, |
1602 | | int32_t aOffset, |
1603 | | ImageBitmapFormat aFormat) |
1604 | | : mPromise(aPromise) |
1605 | | , mImageBitmap(aImageBitmap) |
1606 | | , mBuffer(aCx, aBuffer.Obj()) |
1607 | | , mOffset(aOffset) |
1608 | | , mFormat(aFormat) |
1609 | 0 | { |
1610 | 0 | MOZ_ASSERT(mPromise); |
1611 | 0 | MOZ_ASSERT(JS_IsArrayBufferObject(mBuffer) || |
1612 | 0 | JS_IsArrayBufferViewObject(mBuffer)); |
1613 | 0 | } Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::MapDataIntoBufferSource(JSContext*, mozilla::dom::Promise*, mozilla::dom::ImageBitmap*, mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> const&, int, mozilla::dom::ImageBitmapFormat) Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::MapDataIntoBufferSource(JSContext*, mozilla::dom::Promise*, mozilla::dom::ImageBitmap*, mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> const&, int, mozilla::dom::ImageBitmapFormat) |
1614 | | |
1615 | 0 | virtual ~MapDataIntoBufferSource() = default; Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::~MapDataIntoBufferSource() Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::~MapDataIntoBufferSource() |
1616 | | |
1617 | | void DoMapDataIntoBufferSource() |
1618 | 0 | { |
1619 | 0 | ErrorResult error; |
1620 | 0 |
|
1621 | 0 | auto rejectByDefault = |
1622 | 0 | MakeScopeExit([this, &error]() { |
1623 | 0 | this->mPromise->MaybeReject(error); |
1624 | 0 | }); Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::DoMapDataIntoBufferSource()::{lambda()#1}::operator()() const Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::DoMapDataIntoBufferSource()::{lambda()#1}::operator()() const |
1625 | 0 |
|
1626 | 0 | if (!mImageBitmap->mDataWrapper) { |
1627 | 0 | error.Throw(NS_ERROR_NOT_AVAILABLE); |
1628 | 0 | return; |
1629 | 0 | } |
1630 | 0 | |
1631 | 0 | // Prepare destination buffer. |
1632 | 0 | uint8_t* bufferData = nullptr; |
1633 | 0 | uint32_t bufferLength = 0; |
1634 | 0 | bool isSharedMemory = false; |
1635 | 0 | if (JS_IsArrayBufferObject(mBuffer)) { |
1636 | 0 | js::GetArrayBufferLengthAndData(mBuffer, &bufferLength, &isSharedMemory, &bufferData); |
1637 | 0 | } else if (JS_IsArrayBufferViewObject(mBuffer)) { |
1638 | 0 | js::GetArrayBufferViewLengthAndData(mBuffer, &bufferLength, &isSharedMemory, &bufferData); |
1639 | 0 | } else { |
1640 | 0 | error.Throw(NS_ERROR_NOT_IMPLEMENTED); |
1641 | 0 | return; |
1642 | 0 | } |
1643 | 0 | |
1644 | 0 | if (NS_WARN_IF(!bufferData) || NS_WARN_IF(!bufferLength)) { |
1645 | 0 | error.Throw(NS_ERROR_NOT_AVAILABLE); |
1646 | 0 | return; |
1647 | 0 | } |
1648 | 0 | |
1649 | 0 | // Check length. |
1650 | 0 | const int32_t neededBufferLength = |
1651 | 0 | mImageBitmap->MappedDataLength(mFormat, error); |
1652 | 0 |
|
1653 | 0 | if (((int32_t)bufferLength - mOffset) < neededBufferLength) { |
1654 | 0 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
1655 | 0 | return; |
1656 | 0 | } |
1657 | 0 | |
1658 | 0 | // Call ImageBitmapFormatUtils. |
1659 | 0 | UniquePtr<ImagePixelLayout> layout = |
1660 | 0 | mImageBitmap->mDataWrapper->MapDataInto(bufferData, |
1661 | 0 | mOffset, |
1662 | 0 | bufferLength, |
1663 | 0 | mFormat, |
1664 | 0 | error); |
1665 | 0 |
|
1666 | 0 | if (NS_WARN_IF(!layout)) { |
1667 | 0 | return; |
1668 | 0 | } |
1669 | 0 | |
1670 | 0 | rejectByDefault.release(); |
1671 | 0 | mPromise->MaybeResolve(*layout); |
1672 | 0 | } Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::DoMapDataIntoBufferSource() Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSource<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::DoMapDataIntoBufferSource() |
1673 | | |
1674 | | RefPtr<Promise> mPromise; |
1675 | | RefPtr<ImageBitmap> mImageBitmap; |
1676 | | JS::PersistentRooted<JSObject*> mBuffer; |
1677 | | int32_t mOffset; |
1678 | | ImageBitmapFormat mFormat; |
1679 | | }; |
1680 | | |
1681 | | template<typename T> |
1682 | | class MapDataIntoBufferSourceTask final : public Runnable, |
1683 | | public MapDataIntoBufferSource<T> |
1684 | | { |
1685 | | public: |
1686 | | MapDataIntoBufferSourceTask(JSContext* aCx, |
1687 | | Promise* aPromise, |
1688 | | ImageBitmap* aImageBitmap, |
1689 | | const T& aBuffer, |
1690 | | int32_t aOffset, |
1691 | | ImageBitmapFormat aFormat) |
1692 | | : Runnable("dom::MapDataIntoBufferSourceTask") |
1693 | | , MapDataIntoBufferSource<T>(aCx, |
1694 | | aPromise, |
1695 | | aImageBitmap, |
1696 | | aBuffer, |
1697 | | aOffset, |
1698 | | aFormat) |
1699 | 0 | { |
1700 | 0 | } Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceTask<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::MapDataIntoBufferSourceTask(JSContext*, mozilla::dom::Promise*, mozilla::dom::ImageBitmap*, mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> const&, int, mozilla::dom::ImageBitmapFormat) Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceTask<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::MapDataIntoBufferSourceTask(JSContext*, mozilla::dom::Promise*, mozilla::dom::ImageBitmap*, mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> const&, int, mozilla::dom::ImageBitmapFormat) |
1701 | | |
1702 | 0 | virtual ~MapDataIntoBufferSourceTask() = default; Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceTask<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::~MapDataIntoBufferSourceTask() Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceTask<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::~MapDataIntoBufferSourceTask() |
1703 | | |
1704 | | NS_IMETHOD Run() override |
1705 | 0 | { |
1706 | 0 | MapDataIntoBufferSource<T>::DoMapDataIntoBufferSource(); |
1707 | 0 | return NS_OK; |
1708 | 0 | } Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceTask<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::Run() Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceTask<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::Run() |
1709 | | }; |
1710 | | |
1711 | | template<typename T> |
1712 | | class MapDataIntoBufferSourceWorkerTask final : public WorkerSameThreadRunnable, |
1713 | | public MapDataIntoBufferSource<T> |
1714 | | { |
1715 | | public: |
1716 | | MapDataIntoBufferSourceWorkerTask(JSContext* aCx, |
1717 | | Promise *aPromise, |
1718 | | ImageBitmap *aImageBitmap, |
1719 | | const T& aBuffer, |
1720 | | int32_t aOffset, |
1721 | | ImageBitmapFormat aFormat) |
1722 | | : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()), |
1723 | | MapDataIntoBufferSource<T>(aCx, aPromise, aImageBitmap, aBuffer, aOffset, aFormat) |
1724 | 0 | { |
1725 | 0 | } Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceWorkerTask<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::MapDataIntoBufferSourceWorkerTask(JSContext*, mozilla::dom::Promise*, mozilla::dom::ImageBitmap*, mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> const&, int, mozilla::dom::ImageBitmapFormat) Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceWorkerTask<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::MapDataIntoBufferSourceWorkerTask(JSContext*, mozilla::dom::Promise*, mozilla::dom::ImageBitmap*, mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> const&, int, mozilla::dom::ImageBitmapFormat) |
1726 | | |
1727 | 0 | virtual ~MapDataIntoBufferSourceWorkerTask() = default; Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceWorkerTask<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::~MapDataIntoBufferSourceWorkerTask() Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceWorkerTask<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::~MapDataIntoBufferSourceWorkerTask() |
1728 | | |
1729 | | bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
1730 | 0 | { |
1731 | 0 | MapDataIntoBufferSource<T>::DoMapDataIntoBufferSource(); |
1732 | 0 | return true; |
1733 | 0 | } Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceWorkerTask<mozilla::dom::TypedArray<unsigned char, &js::UnwrapArrayBuffer, &(JS_GetArrayBufferData(JSObject*, bool*, JS::AutoRequireNoGC const&)), &js::GetArrayBufferLengthAndData, &(JS_NewArrayBuffer(JSContext*, unsigned int))> >::WorkerRun(JSContext*, mozilla::dom::WorkerPrivate*) Unexecuted instantiation: mozilla::dom::MapDataIntoBufferSourceWorkerTask<mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView, &js::GetArrayBufferViewLengthAndData, &(JS_GetArrayBufferViewType(JSObject*))> >::WorkerRun(JSContext*, mozilla::dom::WorkerPrivate*) |
1734 | | }; |
1735 | | |
1736 | | void AsyncMapDataIntoBufferSource(JSContext* aCx, |
1737 | | Promise *aPromise, |
1738 | | ImageBitmap *aImageBitmap, |
1739 | | const ArrayBufferViewOrArrayBuffer& aBuffer, |
1740 | | int32_t aOffset, |
1741 | | ImageBitmapFormat aFormat) |
1742 | 0 | { |
1743 | 0 | MOZ_ASSERT(aCx); |
1744 | 0 | MOZ_ASSERT(aPromise); |
1745 | 0 | MOZ_ASSERT(aImageBitmap); |
1746 | 0 |
|
1747 | 0 | if (NS_IsMainThread()) { |
1748 | 0 | nsCOMPtr<nsIRunnable> task; |
1749 | 0 |
|
1750 | 0 | if (aBuffer.IsArrayBuffer()) { |
1751 | 0 | const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer(); |
1752 | 0 | task = new MapDataIntoBufferSourceTask<ArrayBuffer>(aCx, aPromise, aImageBitmap, buffer, aOffset, aFormat); |
1753 | 0 | } else if (aBuffer.IsArrayBufferView()) { |
1754 | 0 | const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView(); |
1755 | 0 | task = new MapDataIntoBufferSourceTask<ArrayBufferView>(aCx, aPromise, aImageBitmap, bufferView, aOffset, aFormat); |
1756 | 0 | } |
1757 | 0 |
|
1758 | 0 | NS_DispatchToCurrentThread(task); // Actually, to the main-thread. |
1759 | 0 | } else { |
1760 | 0 | RefPtr<WorkerSameThreadRunnable> task; |
1761 | 0 |
|
1762 | 0 | if (aBuffer.IsArrayBuffer()) { |
1763 | 0 | const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer(); |
1764 | 0 | task = new MapDataIntoBufferSourceWorkerTask<ArrayBuffer>(aCx, aPromise, aImageBitmap, buffer, aOffset, aFormat); |
1765 | 0 | } else if (aBuffer.IsArrayBufferView()) { |
1766 | 0 | const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView(); |
1767 | 0 | task = new MapDataIntoBufferSourceWorkerTask<ArrayBufferView>(aCx, aPromise, aImageBitmap, bufferView, aOffset, aFormat); |
1768 | 0 | } |
1769 | 0 |
|
1770 | 0 | task->Dispatch(); // Actually, to the current worker-thread. |
1771 | 0 | } |
1772 | 0 | } |
1773 | | |
1774 | | already_AddRefed<Promise> |
1775 | | ImageBitmap::MapDataInto(JSContext* aCx, |
1776 | | ImageBitmapFormat aFormat, |
1777 | | const ArrayBufferViewOrArrayBuffer& aBuffer, |
1778 | | int32_t aOffset, ErrorResult& aRv) |
1779 | 0 | { |
1780 | 0 | MOZ_ASSERT(aCx, "No JSContext while calling ImageBitmap::MapDataInto()."); |
1781 | 0 |
|
1782 | 0 | RefPtr<Promise> promise = Promise::Create(mParent, aRv); |
1783 | 0 |
|
1784 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
1785 | 0 | return nullptr; |
1786 | 0 | } |
1787 | 0 | |
1788 | 0 | if (!mDataWrapper) { |
1789 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
1790 | 0 | return promise.forget(); |
1791 | 0 |
|
1792 | 0 | } |
1793 | 0 | |
1794 | 0 | // Check for cases that should throws. |
1795 | 0 | // Case 1: |
1796 | 0 | // If image bitmap was cropped to the source rectangle so that it contains any |
1797 | 0 | // transparent black pixels (cropping area is outside of the source image), |
1798 | 0 | // then reject promise with IndexSizeError and abort these steps. |
1799 | 0 | if (mIsCroppingAreaOutSideOfSourceImage) { |
1800 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
1801 | 0 | return promise.forget(); |
1802 | 0 | } |
1803 | 0 | |
1804 | 0 | // Case 2: |
1805 | 0 | // If the image bitmap is going to be accessed in YUV422/YUV422 series with a |
1806 | 0 | // cropping area starts at an odd x or y coordinate. |
1807 | 0 | if (aFormat == ImageBitmapFormat::YUV422P || |
1808 | 0 | aFormat == ImageBitmapFormat::YUV420P || |
1809 | 0 | aFormat == ImageBitmapFormat::YUV420SP_NV12 || |
1810 | 0 | aFormat == ImageBitmapFormat::YUV420SP_NV21) { |
1811 | 0 | if ((mPictureRect.x & 1) || (mPictureRect.y & 1)) { |
1812 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
1813 | 0 | return promise.forget(); |
1814 | 0 | } |
1815 | 0 | } |
1816 | 0 | |
1817 | 0 | AsyncMapDataIntoBufferSource(aCx, promise, this, aBuffer, aOffset, aFormat); |
1818 | 0 | return promise.forget(); |
1819 | 0 | } |
1820 | | |
1821 | | // ImageBitmapFactories extensions. |
1822 | | static SurfaceFormat |
1823 | | ImageFormatToSurfaceFromat(mozilla::dom::ImageBitmapFormat aFormat) |
1824 | | { |
1825 | | switch(aFormat) { |
1826 | | case ImageBitmapFormat::RGBA32: |
1827 | | return SurfaceFormat::R8G8B8A8; |
1828 | | case ImageBitmapFormat::BGRA32: |
1829 | | return SurfaceFormat::B8G8R8A8; |
1830 | | case ImageBitmapFormat::RGB24: |
1831 | | return SurfaceFormat::R8G8B8; |
1832 | | case ImageBitmapFormat::BGR24: |
1833 | | return SurfaceFormat::B8G8R8; |
1834 | | case ImageBitmapFormat::GRAY8: |
1835 | | return SurfaceFormat::A8; |
1836 | | case ImageBitmapFormat::HSV: |
1837 | | return SurfaceFormat::HSV; |
1838 | | case ImageBitmapFormat::Lab: |
1839 | | return SurfaceFormat::Lab; |
1840 | | case ImageBitmapFormat::DEPTH: |
1841 | | return SurfaceFormat::Depth; |
1842 | | default: |
1843 | | return SurfaceFormat::UNKNOWN; |
1844 | | } |
1845 | | } |
1846 | | |
1847 | | static already_AddRefed<layers::Image> |
1848 | | CreateImageFromBufferSourceRawData(const uint8_t*aBufferData, |
1849 | | uint32_t aBufferLength, |
1850 | | mozilla::dom::ImageBitmapFormat aFormat, |
1851 | | const Sequence<ChannelPixelLayout>& aLayout) |
1852 | 0 | { |
1853 | 0 | MOZ_ASSERT(aBufferData); |
1854 | 0 | MOZ_ASSERT(aBufferLength > 0); |
1855 | 0 |
|
1856 | 0 | switch(aFormat) { |
1857 | 0 | case ImageBitmapFormat::RGBA32: |
1858 | 0 | case ImageBitmapFormat::BGRA32: |
1859 | 0 | case ImageBitmapFormat::RGB24: |
1860 | 0 | case ImageBitmapFormat::BGR24: |
1861 | 0 | case ImageBitmapFormat::GRAY8: |
1862 | 0 | case ImageBitmapFormat::HSV: |
1863 | 0 | case ImageBitmapFormat::Lab: |
1864 | 0 | case ImageBitmapFormat::DEPTH: |
1865 | 0 | { |
1866 | 0 | const nsTArray<ChannelPixelLayout>& channels = aLayout; |
1867 | 0 | MOZ_ASSERT(channels.Length() != 0, "Empty Channels."); |
1868 | 0 |
|
1869 | 0 | const SurfaceFormat srcFormat = ImageFormatToSurfaceFromat(aFormat); |
1870 | 0 | const uint32_t srcStride = channels[0].mStride; |
1871 | 0 | const IntSize srcSize(channels[0].mWidth, channels[0].mHeight); |
1872 | 0 |
|
1873 | 0 | RefPtr<DataSourceSurface> dstDataSurface = |
1874 | 0 | Factory::CreateDataSourceSurfaceWithStride(srcSize, srcFormat, srcStride); |
1875 | 0 |
|
1876 | 0 | if (NS_WARN_IF(!dstDataSurface)) { |
1877 | 0 | return nullptr; |
1878 | 0 | } |
1879 | 0 | |
1880 | 0 | // Copy the raw data into the newly created DataSourceSurface. |
1881 | 0 | DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE); |
1882 | 0 | if (NS_WARN_IF(!dstMap.IsMapped())) { |
1883 | 0 | return nullptr; |
1884 | 0 | } |
1885 | 0 | |
1886 | 0 | const uint8_t* srcBufferPtr = aBufferData; |
1887 | 0 | uint8_t* dstBufferPtr = dstMap.GetData(); |
1888 | 0 |
|
1889 | 0 | for (int i = 0; i < srcSize.height; ++i) { |
1890 | 0 | memcpy(dstBufferPtr, srcBufferPtr, srcStride); |
1891 | 0 | srcBufferPtr += srcStride; |
1892 | 0 | dstBufferPtr += dstMap.GetStride(); |
1893 | 0 | } |
1894 | 0 |
|
1895 | 0 | // Create an Image from the BGRA SourceSurface. |
1896 | 0 | RefPtr<SourceSurface> surface = dstDataSurface; |
1897 | 0 | RefPtr<layers::Image> image = CreateImageFromSurface(surface); |
1898 | 0 |
|
1899 | 0 | if (NS_WARN_IF(!image)) { |
1900 | 0 | return nullptr; |
1901 | 0 | } |
1902 | 0 | |
1903 | 0 | return image.forget(); |
1904 | 0 | } |
1905 | 0 | case ImageBitmapFormat::YUV444P: |
1906 | 0 | case ImageBitmapFormat::YUV422P: |
1907 | 0 | case ImageBitmapFormat::YUV420P: |
1908 | 0 | case ImageBitmapFormat::YUV420SP_NV12: |
1909 | 0 | case ImageBitmapFormat::YUV420SP_NV21: |
1910 | 0 | { |
1911 | 0 | // Prepare the PlanarYCbCrData. |
1912 | 0 | const ChannelPixelLayout& yLayout = aLayout[0]; |
1913 | 0 | const ChannelPixelLayout& uLayout = aFormat != ImageBitmapFormat::YUV420SP_NV21 ? aLayout[1] : aLayout[2]; |
1914 | 0 | const ChannelPixelLayout& vLayout = aFormat != ImageBitmapFormat::YUV420SP_NV21 ? aLayout[2] : aLayout[1]; |
1915 | 0 |
|
1916 | 0 | layers::PlanarYCbCrData data; |
1917 | 0 |
|
1918 | 0 | // Luminance buffer |
1919 | 0 | data.mYChannel = const_cast<uint8_t*>(aBufferData + yLayout.mOffset); |
1920 | 0 | data.mYStride = yLayout.mStride; |
1921 | 0 | data.mYSize = gfx::IntSize(yLayout.mWidth, yLayout.mHeight); |
1922 | 0 | data.mYSkip = yLayout.mSkip; |
1923 | 0 |
|
1924 | 0 | // Chroma buffers |
1925 | 0 | data.mCbChannel = const_cast<uint8_t*>(aBufferData + uLayout.mOffset); |
1926 | 0 | data.mCrChannel = const_cast<uint8_t*>(aBufferData + vLayout.mOffset); |
1927 | 0 | data.mCbCrStride = uLayout.mStride; |
1928 | 0 | data.mCbCrSize = gfx::IntSize(uLayout.mWidth, uLayout.mHeight); |
1929 | 0 | data.mCbSkip = uLayout.mSkip; |
1930 | 0 | data.mCrSkip = vLayout.mSkip; |
1931 | 0 |
|
1932 | 0 | // Picture rectangle. |
1933 | 0 | // We set the picture rectangle to exactly the size of the source image to |
1934 | 0 | // keep the full original data. |
1935 | 0 | data.mPicX = 0; |
1936 | 0 | data.mPicY = 0; |
1937 | 0 | data.mPicSize = data.mYSize; |
1938 | 0 |
|
1939 | 0 | // Create a layers::Image and set data. |
1940 | 0 | if (aFormat == ImageBitmapFormat::YUV444P || |
1941 | 0 | aFormat == ImageBitmapFormat::YUV422P || |
1942 | 0 | aFormat == ImageBitmapFormat::YUV420P) { |
1943 | 0 | RefPtr<layers::PlanarYCbCrImage> image = |
1944 | 0 | new layers::RecyclingPlanarYCbCrImage(new layers::BufferRecycleBin()); |
1945 | 0 |
|
1946 | 0 | if (NS_WARN_IF(!image)) { |
1947 | 0 | return nullptr; |
1948 | 0 | } |
1949 | 0 | |
1950 | 0 | // Set Data. |
1951 | 0 | if (NS_WARN_IF(!image->CopyData(data))) { |
1952 | 0 | return nullptr; |
1953 | 0 | } |
1954 | 0 | |
1955 | 0 | return image.forget(); |
1956 | 0 | } else { |
1957 | 0 | RefPtr<layers::NVImage>image = new layers::NVImage(); |
1958 | 0 |
|
1959 | 0 | if (NS_WARN_IF(!image)) { |
1960 | 0 | return nullptr; |
1961 | 0 | } |
1962 | 0 | |
1963 | 0 | // Set Data. |
1964 | 0 | if (NS_WARN_IF(!image->SetData(data))) { |
1965 | 0 | return nullptr; |
1966 | 0 | } |
1967 | 0 | |
1968 | 0 | return image.forget(); |
1969 | 0 | } |
1970 | 0 | } |
1971 | 0 | default: |
1972 | 0 | return nullptr; |
1973 | 0 | } |
1974 | 0 | } |
1975 | | |
1976 | | /* |
1977 | | * This is a synchronous task. |
1978 | | * This class is used to create a layers::CairoImage from raw data in the main |
1979 | | * thread. While creating an ImageBitmap from an BufferSource, we need to create |
1980 | | * a SouceSurface from the BufferSource raw data and then set the SourceSurface |
1981 | | * into a layers::CairoImage. However, the layers::CairoImage asserts the |
1982 | | * setting operation in the main thread, so if we are going to create an |
1983 | | * ImageBitmap from an BufferSource off the main thread, we post an event to the |
1984 | | * main thread to create a layers::CairoImage from an BufferSource raw data. |
1985 | | * |
1986 | | * TODO: Once the layers::CairoImage is constructible off the main thread, which |
1987 | | * means the SouceSurface could be released anywhere, we do not need this |
1988 | | * task anymore. |
1989 | | */ |
1990 | | class CreateImageFromBufferSourceRawDataInMainThreadSyncTask final : |
1991 | | public WorkerMainThreadRunnable |
1992 | | { |
1993 | | public: |
1994 | | CreateImageFromBufferSourceRawDataInMainThreadSyncTask(const uint8_t* aBuffer, |
1995 | | uint32_t aBufferLength, |
1996 | | mozilla::dom::ImageBitmapFormat aFormat, |
1997 | | const Sequence<ChannelPixelLayout>& aLayout, |
1998 | | /*output*/ layers::Image** aImage) |
1999 | | : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(), |
2000 | | NS_LITERAL_CSTRING("ImageBitmap-extensions :: Create Image from BufferSource Raw Data")) |
2001 | | , mImage(aImage) |
2002 | | , mBuffer(aBuffer) |
2003 | | , mBufferLength(aBufferLength) |
2004 | | , mFormat(aFormat) |
2005 | | , mLayout(aLayout) |
2006 | 0 | { |
2007 | 0 | MOZ_ASSERT(!(*aImage), "Don't pass an existing Image into CreateImageFromBufferSourceRawDataInMainThreadSyncTask."); |
2008 | 0 | } |
2009 | | |
2010 | | bool MainThreadRun() override |
2011 | 0 | { |
2012 | 0 | RefPtr<layers::Image> image = |
2013 | 0 | CreateImageFromBufferSourceRawData(mBuffer, mBufferLength, mFormat, mLayout); |
2014 | 0 |
|
2015 | 0 | if (NS_WARN_IF(!image)) { |
2016 | 0 | return true; |
2017 | 0 | } |
2018 | 0 | |
2019 | 0 | image.forget(mImage); |
2020 | 0 |
|
2021 | 0 | return true; |
2022 | 0 | } |
2023 | | |
2024 | | private: |
2025 | | layers::Image** mImage; |
2026 | | const uint8_t* mBuffer; |
2027 | | uint32_t mBufferLength; |
2028 | | mozilla::dom::ImageBitmapFormat mFormat; |
2029 | | const Sequence<ChannelPixelLayout>& mLayout; |
2030 | | }; |
2031 | | |
2032 | | /*static*/ already_AddRefed<Promise> |
2033 | | ImageBitmap::Create(nsIGlobalObject* aGlobal, |
2034 | | const ImageBitmapSource& aBuffer, |
2035 | | int32_t aOffset, int32_t aLength, |
2036 | | mozilla::dom::ImageBitmapFormat aFormat, |
2037 | | const Sequence<ChannelPixelLayout>& aLayout, |
2038 | | ErrorResult& aRv) |
2039 | 0 | { |
2040 | 0 | MOZ_ASSERT(aGlobal); |
2041 | 0 |
|
2042 | 0 | RefPtr<Promise> promise = Promise::Create(aGlobal, aRv); |
2043 | 0 |
|
2044 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2045 | 0 | return nullptr; |
2046 | 0 | } |
2047 | 0 | |
2048 | 0 | uint8_t* bufferData = nullptr; |
2049 | 0 | uint32_t bufferLength = 0; |
2050 | 0 |
|
2051 | 0 | if (aBuffer.IsArrayBuffer()) { |
2052 | 0 | const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer(); |
2053 | 0 | buffer.ComputeLengthAndData(); |
2054 | 0 | bufferData = buffer.Data(); |
2055 | 0 | bufferLength = buffer.Length(); |
2056 | 0 | } else if (aBuffer.IsArrayBufferView()) { |
2057 | 0 | const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView(); |
2058 | 0 | bufferView.ComputeLengthAndData(); |
2059 | 0 | bufferData = bufferView.Data(); |
2060 | 0 | bufferLength = bufferView.Length(); |
2061 | 0 | } else { |
2062 | 0 | aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); |
2063 | 0 | return promise.forget(); |
2064 | 0 | } |
2065 | 0 | |
2066 | 0 | MOZ_ASSERT(bufferData && bufferLength > 0, "Cannot read data from BufferSource."); |
2067 | 0 |
|
2068 | 0 | // Check the buffer. |
2069 | 0 | if (((uint32_t)(aOffset + aLength) > bufferLength)) { |
2070 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
2071 | 0 | return promise.forget(); |
2072 | 0 | } |
2073 | 0 | |
2074 | 0 | // Create and Crop the raw data into a layers::Image |
2075 | 0 | RefPtr<layers::Image> data; |
2076 | 0 | if (NS_IsMainThread()) { |
2077 | 0 | data = CreateImageFromBufferSourceRawData(bufferData + aOffset, bufferLength, |
2078 | 0 | aFormat, aLayout); |
2079 | 0 | } else { |
2080 | 0 | RefPtr<CreateImageFromBufferSourceRawDataInMainThreadSyncTask> task = |
2081 | 0 | new CreateImageFromBufferSourceRawDataInMainThreadSyncTask(bufferData + aOffset, |
2082 | 0 | bufferLength, |
2083 | 0 | aFormat, |
2084 | 0 | aLayout, |
2085 | 0 | getter_AddRefs(data)); |
2086 | 0 | task->Dispatch(Canceling, aRv); |
2087 | 0 | if (aRv.Failed()) { |
2088 | 0 | return promise.forget(); |
2089 | 0 | } |
2090 | 0 | } |
2091 | 0 | |
2092 | 0 | if (NS_WARN_IF(!data)) { |
2093 | 0 | aRv.Throw(NS_ERROR_NOT_AVAILABLE); |
2094 | 0 | return promise.forget(); |
2095 | 0 | } |
2096 | 0 | |
2097 | 0 | // Create an ImageBimtap. |
2098 | 0 | // Assume the data from an external buffer is not alpha-premultiplied. |
2099 | 0 | RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data, |
2100 | 0 | gfxAlphaType::NonPremult); |
2101 | 0 |
|
2102 | 0 | imageBitmap->mAllocatedImageData = true; |
2103 | 0 |
|
2104 | 0 | // We don't need to call SetPictureRect() here because there is no cropping |
2105 | 0 | // supported and the ImageBitmap's mPictureRect is the size of the source |
2106 | 0 | // image in default |
2107 | 0 |
|
2108 | 0 | // We don't need to set mIsCroppingAreaOutSideOfSourceImage here because there |
2109 | 0 | // is no cropping supported and the mIsCroppingAreaOutSideOfSourceImage is |
2110 | 0 | // false in default. |
2111 | 0 |
|
2112 | 0 | AsyncFulfillImageBitmapPromise(promise, imageBitmap); |
2113 | 0 |
|
2114 | 0 | return promise.forget(); |
2115 | 0 | } |
2116 | | |
2117 | | size_t |
2118 | | ImageBitmap::GetAllocatedSize() const |
2119 | 0 | { |
2120 | 0 | if (!mAllocatedImageData) { |
2121 | 0 | return 0; |
2122 | 0 | } |
2123 | 0 | |
2124 | 0 | // Calculate how many bytes are used. |
2125 | 0 | if (mData->GetFormat() == mozilla::ImageFormat::PLANAR_YCBCR) { |
2126 | 0 | return mData->AsPlanarYCbCrImage()->GetDataSize(); |
2127 | 0 | } |
2128 | 0 | |
2129 | 0 | if (mData->GetFormat() == mozilla::ImageFormat::NV_IMAGE) { |
2130 | 0 | return mData->AsNVImage()->GetBufferSize(); |
2131 | 0 | } |
2132 | 0 | |
2133 | 0 | RefPtr<SourceSurface> surface = mData->GetAsSourceSurface(); |
2134 | 0 | const int bytesPerPixel = BytesPerPixel(surface->GetFormat()); |
2135 | 0 | return surface->GetSize().height * surface->GetSize().width * bytesPerPixel; |
2136 | 0 | } |
2137 | | |
2138 | | size_t |
2139 | | BindingJSObjectMallocBytes(ImageBitmap* aBitmap) |
2140 | 0 | { |
2141 | 0 | return aBitmap->GetAllocatedSize(); |
2142 | 0 | } |
2143 | | |
2144 | | /* static */ already_AddRefed<CreateImageBitmapFromBlob> |
2145 | | CreateImageBitmapFromBlob::Create(Promise* aPromise, |
2146 | | nsIGlobalObject* aGlobal, |
2147 | | Blob& aBlob, |
2148 | | const Maybe<IntRect>& aCropRect, |
2149 | | nsIEventTarget* aMainThreadEventTarget) |
2150 | 0 | { |
2151 | 0 | // Get the internal stream of the blob. |
2152 | 0 | nsCOMPtr<nsIInputStream> stream; |
2153 | 0 | ErrorResult error; |
2154 | 0 | aBlob.Impl()->CreateInputStream(getter_AddRefs(stream), error); |
2155 | 0 | if (NS_WARN_IF(error.Failed())) { |
2156 | 0 | return nullptr; |
2157 | 0 | } |
2158 | 0 | |
2159 | 0 | // Get the MIME type string of the blob. |
2160 | 0 | // The type will be checked in the DecodeImageAsync() method. |
2161 | 0 | nsAutoString mimeTypeUTF16; |
2162 | 0 | aBlob.Impl()->GetType(mimeTypeUTF16); |
2163 | 0 | NS_ConvertUTF16toUTF8 mimeType(mimeTypeUTF16); |
2164 | 0 |
|
2165 | 0 | RefPtr<CreateImageBitmapFromBlob> task = |
2166 | 0 | new CreateImageBitmapFromBlob(aPromise, aGlobal, stream.forget(), mimeType, |
2167 | 0 | aCropRect, aMainThreadEventTarget); |
2168 | 0 |
|
2169 | 0 | // Nothing to do for the main-thread. |
2170 | 0 | if (NS_IsMainThread()) { |
2171 | 0 | return task.forget(); |
2172 | 0 | } |
2173 | 0 | |
2174 | 0 | // Let's use a WorkerRef to keep the worker alive if this is not the |
2175 | 0 | // main-thread. |
2176 | 0 | WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); |
2177 | 0 | MOZ_ASSERT(workerPrivate); |
2178 | 0 |
|
2179 | 0 | RefPtr<StrongWorkerRef> workerRef = |
2180 | 0 | StrongWorkerRef::Create(workerPrivate, "CreateImageBitmapFromBlob", |
2181 | 0 | [task]() { |
2182 | 0 | task->WorkerShuttingDown(); |
2183 | 0 | }); |
2184 | 0 | if (NS_WARN_IF(!workerRef)) { |
2185 | 0 | return nullptr; |
2186 | 0 | } |
2187 | 0 | |
2188 | 0 | task->mWorkerRef = new ThreadSafeWorkerRef(workerRef); |
2189 | 0 | return task.forget(); |
2190 | 0 | } |
2191 | | |
2192 | | nsresult |
2193 | | CreateImageBitmapFromBlob::StartDecodeAndCropBlob() |
2194 | 0 | { |
2195 | 0 | MOZ_ASSERT(IsCurrentThread()); |
2196 | 0 |
|
2197 | 0 | // Workers. |
2198 | 0 | if (!NS_IsMainThread()) { |
2199 | 0 | RefPtr<CreateImageBitmapFromBlob> self = this; |
2200 | 0 | nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( |
2201 | 0 | "CreateImageBitmapFromBlob::DecodeAndCropBlob", |
2202 | 0 | [self]() { |
2203 | 0 | nsresult rv = self->DecodeAndCropBlob(); |
2204 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2205 | 0 | self->DecodeAndCropBlobCompletedMainThread(nullptr, rv); |
2206 | 0 | } |
2207 | 0 | }); |
2208 | 0 |
|
2209 | 0 | return mMainThreadEventTarget->Dispatch(r.forget()); |
2210 | 0 | } |
2211 | 0 |
|
2212 | 0 | // Main-thread. |
2213 | 0 | return DecodeAndCropBlob(); |
2214 | 0 | } |
2215 | | |
2216 | | nsresult |
2217 | | CreateImageBitmapFromBlob::DecodeAndCropBlob() |
2218 | 0 | { |
2219 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2220 | 0 |
|
2221 | 0 | // Get the Component object. |
2222 | 0 | nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID); |
2223 | 0 | if (NS_WARN_IF(!imgtool)) { |
2224 | 0 | return NS_ERROR_FAILURE; |
2225 | 0 | } |
2226 | 0 | |
2227 | 0 | // Decode image. |
2228 | 0 | nsCOMPtr<imgIContainer> imgContainer; |
2229 | 0 | nsresult rv = imgtool->DecodeImageAsync(mInputStream, mMimeType, this, |
2230 | 0 | mMainThreadEventTarget); |
2231 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2232 | 0 | return rv; |
2233 | 0 | } |
2234 | 0 | |
2235 | 0 | return NS_OK; |
2236 | 0 | } |
2237 | | |
2238 | | NS_IMETHODIMP |
2239 | | CreateImageBitmapFromBlob::OnImageReady(imgIContainer* aImgContainer, |
2240 | | nsresult aStatus) |
2241 | 0 | { |
2242 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2243 | 0 |
|
2244 | 0 | if (NS_FAILED(aStatus)) { |
2245 | 0 | DecodeAndCropBlobCompletedMainThread(nullptr, aStatus); |
2246 | 0 | return NS_OK; |
2247 | 0 | } |
2248 | 0 | |
2249 | 0 | MOZ_ASSERT(aImgContainer); |
2250 | 0 |
|
2251 | 0 | // Get the surface out. |
2252 | 0 | uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_WANT_DATA_SURFACE; |
2253 | 0 | uint32_t whichFrame = imgIContainer::FRAME_FIRST; |
2254 | 0 | RefPtr<SourceSurface> surface = aImgContainer->GetFrame(whichFrame, frameFlags); |
2255 | 0 |
|
2256 | 0 | if (NS_WARN_IF(!surface)) { |
2257 | 0 | DecodeAndCropBlobCompletedMainThread(nullptr, NS_ERROR_FAILURE); |
2258 | 0 | return NS_OK; |
2259 | 0 | } |
2260 | 0 | |
2261 | 0 | // Store the sourceSize value for the DecodeAndCropBlobCompletedMainThread call. |
2262 | 0 | mSourceSize = surface->GetSize(); |
2263 | 0 |
|
2264 | 0 | // Crop the source surface if needed. |
2265 | 0 | RefPtr<SourceSurface> croppedSurface = surface; |
2266 | 0 |
|
2267 | 0 | if (mCropRect.isSome()) { |
2268 | 0 | // The blob is just decoded into a RasterImage and not optimized yet, so the |
2269 | 0 | // _surface_ we get is a DataSourceSurface which wraps the RasterImage's |
2270 | 0 | // raw buffer. |
2271 | 0 | // |
2272 | 0 | // The _surface_ might already be optimized so that its type is not |
2273 | 0 | // SurfaceType::DATA. However, we could keep using the generic cropping and |
2274 | 0 | // copying since the decoded buffer is only used in this ImageBitmap so we |
2275 | 0 | // should crop it to save memory usage. |
2276 | 0 | // |
2277 | 0 | // TODO: Bug1189632 is going to refactor this create-from-blob part to |
2278 | 0 | // decode the blob off the main thread. Re-check if we should do |
2279 | 0 | // cropping at this moment again there. |
2280 | 0 | RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); |
2281 | 0 | croppedSurface = CropAndCopyDataSourceSurface(dataSurface, mCropRect.ref()); |
2282 | 0 | mCropRect->MoveTo(0, 0); |
2283 | 0 | } |
2284 | 0 |
|
2285 | 0 | if (NS_WARN_IF(!croppedSurface)) { |
2286 | 0 | DecodeAndCropBlobCompletedMainThread(nullptr, NS_ERROR_FAILURE); |
2287 | 0 | return NS_OK; |
2288 | 0 | } |
2289 | 0 | |
2290 | 0 | // Create an Image from the source surface. |
2291 | 0 | RefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface); |
2292 | 0 |
|
2293 | 0 | if (NS_WARN_IF(!image)) { |
2294 | 0 | DecodeAndCropBlobCompletedMainThread(nullptr, NS_ERROR_FAILURE); |
2295 | 0 | return NS_OK; |
2296 | 0 | } |
2297 | 0 | |
2298 | 0 | DecodeAndCropBlobCompletedMainThread(image, NS_OK); |
2299 | 0 | return NS_OK; |
2300 | 0 | } |
2301 | | |
2302 | | void |
2303 | | CreateImageBitmapFromBlob::DecodeAndCropBlobCompletedMainThread(layers::Image* aImage, |
2304 | | nsresult aStatus) |
2305 | 0 | { |
2306 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2307 | 0 |
|
2308 | 0 | if (!IsCurrentThread()) { |
2309 | 0 | MutexAutoLock lock(mMutex); |
2310 | 0 |
|
2311 | 0 | if (!mWorkerRef) { |
2312 | 0 | // The worker is already gone. |
2313 | 0 | return; |
2314 | 0 | } |
2315 | 0 | |
2316 | 0 | RefPtr<CreateImageBitmapFromBlobRunnable> r = |
2317 | 0 | new CreateImageBitmapFromBlobRunnable(mWorkerRef->Private(), |
2318 | 0 | this, aImage, aStatus); |
2319 | 0 | r->Dispatch(); |
2320 | 0 | return; |
2321 | 0 | } |
2322 | 0 | |
2323 | 0 | DecodeAndCropBlobCompletedOwningThread(aImage, aStatus); |
2324 | 0 | } |
2325 | | |
2326 | | void |
2327 | | CreateImageBitmapFromBlob::DecodeAndCropBlobCompletedOwningThread(layers::Image* aImage, |
2328 | | nsresult aStatus) |
2329 | 0 | { |
2330 | 0 | MOZ_ASSERT(IsCurrentThread()); |
2331 | 0 |
|
2332 | 0 | if (!mPromise) { |
2333 | 0 | // The worker is going to be released soon. No needs to continue. |
2334 | 0 | return; |
2335 | 0 | } |
2336 | 0 | |
2337 | 0 | // Let's release what has to be released on the owning thread. |
2338 | 0 | auto raii = MakeScopeExit([&] { |
2339 | 0 | // Doing this we also release the worker. |
2340 | 0 | mWorkerRef = nullptr; |
2341 | 0 |
|
2342 | 0 | mPromise = nullptr; |
2343 | 0 | mGlobalObject = nullptr; |
2344 | 0 | }); |
2345 | 0 |
|
2346 | 0 | if (NS_WARN_IF(NS_FAILED(aStatus))) { |
2347 | 0 | mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); |
2348 | 0 | return; |
2349 | 0 | } |
2350 | 0 | |
2351 | 0 | // Create ImageBitmap object. |
2352 | 0 | RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, aImage); |
2353 | 0 |
|
2354 | 0 | // Set mIsCroppingAreaOutSideOfSourceImage. |
2355 | 0 | imageBitmap->SetIsCroppingAreaOutSideOfSourceImage(mSourceSize, |
2356 | 0 | mOriginalCropRect); |
2357 | 0 |
|
2358 | 0 | if (mCropRect.isSome()) { |
2359 | 0 | ErrorResult rv; |
2360 | 0 | imageBitmap->SetPictureRect(mCropRect.ref(), rv); |
2361 | 0 |
|
2362 | 0 | if (rv.Failed()) { |
2363 | 0 | mPromise->MaybeReject(rv); |
2364 | 0 | return; |
2365 | 0 | } |
2366 | 0 | } |
2367 | 0 | |
2368 | 0 | imageBitmap->mAllocatedImageData = true; |
2369 | 0 |
|
2370 | 0 | mPromise->MaybeResolve(imageBitmap); |
2371 | 0 | } |
2372 | | |
2373 | | void |
2374 | | CreateImageBitmapFromBlob::WorkerShuttingDown() |
2375 | 0 | { |
2376 | 0 | MOZ_ASSERT(IsCurrentThread()); |
2377 | 0 |
|
2378 | 0 | MutexAutoLock lock(mMutex); |
2379 | 0 |
|
2380 | 0 | // Let's release all the non-thread-safe objects now. |
2381 | 0 | mWorkerRef = nullptr; |
2382 | 0 | mPromise = nullptr; |
2383 | 0 | mGlobalObject = nullptr; |
2384 | 0 | } |
2385 | | |
2386 | | } // namespace dom |
2387 | | } // namespace mozilla |