6b0c7a3d67cdc7a5bb5de2984cd2ba55df302707
[WebKit-https.git] / Source / WebCore / html / ImageBitmap.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ImageBitmap.h"
28
29 #include "BitmapImage.h"
30 #include "Blob.h"
31 #include "CachedImage.h"
32 #include "ExceptionOr.h"
33 #include "FileReaderLoader.h"
34 #include "FileReaderLoaderClient.h"
35 #include "GraphicsContext.h"
36 #include "HTMLCanvasElement.h"
37 #include "HTMLImageElement.h"
38 #include "HTMLVideoElement.h"
39 #include "ImageBitmapOptions.h"
40 #include "ImageBuffer.h"
41 #include "ImageData.h"
42 #include "IntRect.h"
43 #include "JSImageBitmap.h"
44 #include "LayoutSize.h"
45 #include "RenderElement.h"
46 #include "SharedBuffer.h"
47 #include "TypedOMCSSImageValue.h"
48 #include <wtf/IsoMallocInlines.h>
49 #include <wtf/Optional.h>
50 #include <wtf/StdLibExtras.h>
51 #include <wtf/Variant.h>
52
53 namespace WebCore {
54
55 WTF_MAKE_ISO_ALLOCATED_IMPL(ImageBitmap);
56
57 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
58 static RenderingMode bufferRenderingMode = Accelerated;
59 #else
60 static RenderingMode bufferRenderingMode = Unaccelerated;
61 #endif
62
63 Ref<ImageBitmap> ImageBitmap::create(IntSize size)
64 {
65     return create(ImageBuffer::create(FloatSize(size.width(), size.height()), bufferRenderingMode));
66 }
67
68 Ref<ImageBitmap> ImageBitmap::create(std::pair<std::unique_ptr<ImageBuffer>, bool>&& buffer)
69 {
70     auto imageBitmap = create(WTFMove(buffer.first));
71     imageBitmap->m_originClean = buffer.second;
72     return imageBitmap;
73 }
74
75 Ref<ImageBitmap> ImageBitmap::create(std::unique_ptr<ImageBuffer>&& buffer)
76 {
77     return adoptRef(*new ImageBitmap(WTFMove(buffer)));
78 }
79
80 void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, ImageBitmap::Source&& source, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise)
81 {
82     WTF::switchOn(source,
83         [&] (auto& specificSource) {
84             createPromise(scriptExecutionContext, specificSource, WTFMove(options), WTF::nullopt, WTFMove(promise));
85         }
86     );
87 }
88
89 Vector<std::pair<std::unique_ptr<ImageBuffer>, bool>> ImageBitmap::detachBitmaps(Vector<RefPtr<ImageBitmap>>&& bitmaps)
90 {
91     Vector<std::pair<std::unique_ptr<ImageBuffer>, bool>> buffers;
92     for (auto& bitmap : bitmaps)
93         buffers.append(std::make_pair(bitmap->transferOwnershipAndClose(), bitmap->originClean()));
94     return buffers;
95 }
96
97
98 void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, ImageBitmap::Source&& source, ImageBitmapOptions&& options, int sx, int sy, int sw, int sh, ImageBitmap::Promise&& promise)
99 {
100     // 1. If either the sw or sh arguments are specified but zero, return a promise
101     //    rejected with an "RangeError" DOMException and abort these steps.
102     if (!sw || !sh) {
103         promise.reject(RangeError, "Cannot create ImageBitmap with a width or height of 0");
104         return;
105     }
106
107     auto left = sw >= 0 ? sx : sx + sw;
108     auto top = sh >= 0 ? sy : sy + sh;
109     auto width = std::abs(sw);
110     auto height = std::abs(sh);
111
112     WTF::switchOn(source,
113         [&] (auto& specificSource) {
114             createPromise(scriptExecutionContext, specificSource, WTFMove(options), IntRect { left, top, width, height }, WTFMove(promise));
115         }
116     );
117 }
118
119 static bool taintsOrigin(CachedImage& cachedImage)
120 {
121     auto* image = cachedImage.image();
122     if (!image)
123         return false;
124
125     if (image->sourceURL().protocolIsData())
126         return false;
127
128     if (!image->hasSingleSecurityOrigin())
129         return true;
130
131     if (!cachedImage.isCORSSameOrigin())
132         return true;
133
134     return false;
135 }
136
137 #if ENABLE(VIDEO)
138 static bool taintsOrigin(SecurityOrigin* origin, HTMLVideoElement& video)
139 {
140     if (!video.hasSingleSecurityOrigin())
141         return true;
142
143     if (video.player()->didPassCORSAccessCheck())
144         return false;
145
146     auto url = video.currentSrc();
147     if (url.protocolIsData())
148         return false;
149
150     return !origin->canRequest(url);
151 }
152 #endif
153
154 // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting
155 static ExceptionOr<IntRect> croppedSourceRectangleWithFormatting(IntSize inputSize, ImageBitmapOptions& options, Optional<IntRect> rect)
156 {
157     // 2. If either or both of resizeWidth and resizeHeight members of options are less
158     //    than or equal to 0, then return a promise rejected with "InvalidStateError"
159     //    DOMException and abort these steps.
160     if ((options.resizeWidth && options.resizeWidth.value() <= 0) || (options.resizeHeight && options.resizeHeight.value() <= 0))
161         return Exception { InvalidStateError, "Invalid resize dimensions" };
162
163     // 3. If sx, sy, sw and sh are specified, let sourceRectangle be a rectangle whose
164     //    corners are the four points (sx, sy), (sx+sw, sy),(sx+sw, sy+sh), (sx,sy+sh).
165     //    Otherwise let sourceRectangle be a rectangle whose corners are the four points
166     //    (0,0), (width of input, 0), (width of input, height of input), (0, height of
167     //    input).
168     auto sourceRectangle = rect.valueOr(IntRect { 0, 0, inputSize.width(), inputSize.height() });
169
170     // 4. Clip sourceRectangle to the dimensions of input.
171     sourceRectangle.intersect(IntRect { 0, 0, inputSize.width(), inputSize.height() });
172
173     return { WTFMove(sourceRectangle) };
174 }
175
176 static IntSize outputSizeForSourceRectangle(IntRect sourceRectangle, ImageBitmapOptions& options)
177 {
178     // 5. Let outputWidth be determined as follows:
179     auto outputWidth = [&] () -> int {
180         if (options.resizeWidth)
181             return options.resizeWidth.value();
182         if (options.resizeHeight)
183             return ceil(sourceRectangle.width() * static_cast<double>(options.resizeHeight.value()) / sourceRectangle.height());
184         return sourceRectangle.width();
185     }();
186
187     // 6. Let outputHeight be determined as follows:
188     auto outputHeight = [&] () -> int {
189         if (options.resizeHeight)
190             return options.resizeHeight.value();
191         if (options.resizeWidth)
192             return ceil(sourceRectangle.height() * static_cast<double>(options.resizeWidth.value()) / sourceRectangle.width());
193         return sourceRectangle.height();
194     }();
195
196     return { outputWidth, outputHeight };
197 }
198
199 static InterpolationQuality interpolationQualityForResizeQuality(ImageBitmapOptions::ResizeQuality resizeQuality)
200 {
201     switch (resizeQuality) {
202     case ImageBitmapOptions::ResizeQuality::Pixelated:
203         return InterpolationNone;
204     case ImageBitmapOptions::ResizeQuality::Low:
205         return InterpolationDefault; // Low is the default.
206     case ImageBitmapOptions::ResizeQuality::Medium:
207         return InterpolationMedium;
208     case ImageBitmapOptions::ResizeQuality::High:
209         return InterpolationHigh;
210     }
211     ASSERT_NOT_REACHED();
212     return InterpolationDefault;
213 }
214
215 // FIXME: More steps from https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting
216
217 // 7. Place input on an infinite transparent black grid plane, positioned so that its
218 //    top left corner is at the origin of the plane, with the x-coordinate increasing
219 //    to the right, and the y-coordinate increasing down, and with each pixel in the
220 //    input image data occupying a cell on the plane's grid.
221
222 // 8. Let output be the rectangle on the plane denoted by sourceRectangle.
223
224 // 9. Scale output to the size specified by outputWidth and outputHeight. The user
225 //    agent should use the value of the resizeQuality option to guide the choice of
226 //    scaling algorithm.
227
228 // 10. If the value of the imageOrientation member of options is "flipY", output must
229 //     be flipped vertically, disregarding any image orientation metadata of the source
230 //     (such as EXIF metadata), if any.
231
232 // 11. If image is an img element or a Blob object, let val be the value of the
233 //     colorSpaceConversion member of options, and then run these substeps:
234 //
235 //     1. If val is "default", the color space conversion behavior is implementation-specific,
236 //        and should be chosen according to the color space that the implementation uses for
237 //        drawing images onto the canvas.
238 //
239 //     2. If val is "none", output must be decoded without performing any color space
240 //        conversions. This means that the image decoding algorithm must ignore color profile
241 //        metadata embedded in the source data as well as the display device color profile.
242
243 // 12. Let val be the value of premultiplyAlpha member of options, and then run these substeps:
244 //
245 //     1. If val is "default", the alpha premultiplication behavior is implementation-specific,
246 //        and should be chosen according to implementation deems optimal for drawing images
247 //        onto the canvas.
248 //
249 //     2. If val is "premultiply", the output that is not premultiplied by alpha must have its
250 //        color components multiplied by alpha and that is premultiplied by alpha must be left
251 //        untouched.
252 //
253 //     3. If val is "none", the output that is not premultiplied by alpha must be left untouched
254 //        and that is premultiplied by alpha must have its color components divided by alpha.
255
256 // 13. Return output.
257
258 void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<HTMLImageElement>& imageElement, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
259 {
260     // 2. If image is not completely available, then return a promise rejected with
261     // an "InvalidStateError" DOMException and abort these steps.
262
263     auto* cachedImage = imageElement->cachedImage();
264     if (!cachedImage || !imageElement->complete()) {
265         promise.reject(InvalidStateError, "Cannot create ImageBitmap that is not completely available");
266         return;
267     }
268
269     // 3. If image's media data has no intrinsic dimensions (e.g. it's a vector graphic
270     //    with no specified content size), and both or either of the resizeWidth and
271     //    resizeHeight options are not specified, then return a promise rejected with
272     //    an "InvalidStateError" DOMException and abort these steps.
273
274     auto imageSize = cachedImage->imageSizeForRenderer(imageElement->renderer(), 1.0f);
275     if ((!imageSize.width() || !imageSize.height()) && (!options.resizeWidth || !options.resizeHeight)) {
276         promise.reject(InvalidStateError, "Cannot create ImageBitmap from a source with no intrinsic size without providing resize dimensions");
277         return;
278     }
279
280     // 4. If image's media data has no intrinsic dimensions (e.g. it's a vector graphics
281     //    with no specified content size), it should be rendered to a bitmap of the size
282     //    specified by the resizeWidth and the resizeHeight options.
283
284     if (!imageSize.width() && !imageSize.height()) {
285         imageSize.setWidth(options.resizeWidth.value());
286         imageSize.setHeight(options.resizeHeight.value());
287     }
288
289     // 5. If the sw and sh arguments are not specified and image's media data has both or
290     //    either of its intrinsic width and intrinsic height values equal to 0, then return
291     //    a promise rejected with an "InvalidStateError" DOMException and abort these steps.
292     // 6. If the sh argument is not specified and image's media data has an intrinsic height
293     //    of 0, then return a promise rejected with an "InvalidStateError" DOMException and
294     //    abort these steps.
295
296     // FIXME: It's unclear how these steps can happen, since step 4 required setting a
297     // width and height for the image.
298
299     if (!rect && (!imageSize.width() || !imageSize.height())) {
300         promise.reject(InvalidStateError, "Cannot create ImageBitmap from a source with no intrinsic size without providing dimensions");
301         return;
302     }
303
304     // 8. Let the ImageBitmap object's bitmap data be a copy of image's media data, cropped to
305     //    the source rectangle with formatting. If this is an animated image, the ImageBitmap
306     //    object's bitmap data must only be taken from the default image of the animation (the
307     //    one that the format defines is to be used when animation is not supported or is disabled),
308     //    or, if there is no such image, the first frame of the animation.
309
310     auto sourceRectangle = croppedSourceRectangleWithFormatting(roundedIntSize(imageSize), options, WTFMove(rect));
311     if (sourceRectangle.hasException()) {
312         promise.reject(sourceRectangle.releaseException());
313         return;
314     }
315
316     auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
317     auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
318
319     auto imageForRender = cachedImage->imageForRenderer(imageElement->renderer());
320     if (!imageForRender) {
321         promise.reject(InvalidStateError, "Cannot create ImageBitmap from image that can't be rendered");
322         return;
323     }
324
325     FloatRect destRect(FloatPoint(), outputSize);
326     bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), { interpolationQualityForResizeQuality(options.resizeQuality) });
327
328     // 7. Create a new ImageBitmap object.
329     auto imageBitmap = create(WTFMove(bitmapData));
330
331     // 9. If the origin of image's image is not the same origin as the origin specified by the
332     //    entry settings object, then set the origin-clean flag of the ImageBitmap object's
333     //    bitmap to false.
334
335     imageBitmap->m_originClean = !taintsOrigin(*cachedImage);
336
337     // 10. Return a new promise, but continue running these steps in parallel.
338     // 11. Resolve the promise with the new ImageBitmap object as the value.
339
340     promise.resolve(WTFMove(imageBitmap));
341 }
342
343 void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<HTMLCanvasElement>& canvasElement, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
344 {
345     // 2. If the canvas element's bitmap has either a horizontal dimension or a vertical
346     //    dimension equal to zero, then return a promise rejected with an "InvalidStateError"
347     //    DOMException and abort these steps.
348     auto size = canvasElement->size();
349     if (!size.width() || !size.height()) {
350         promise.reject(InvalidStateError, "Cannot create ImageBitmap from a canvas that has zero width or height");
351         return;
352     }
353
354     // 4. Let the ImageBitmap object's bitmap data be a copy of the canvas element's bitmap
355     //    data, cropped to the source rectangle with formatting.
356
357     auto sourceRectangle = croppedSourceRectangleWithFormatting(size, options, WTFMove(rect));
358     if (sourceRectangle.hasException()) {
359         promise.reject(sourceRectangle.releaseException());
360         return;
361     }
362
363     auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
364     auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
365
366     auto imageForRender = canvasElement->copiedImage();
367     if (!imageForRender) {
368         promise.reject(InvalidStateError, "Cannot create ImageBitmap from canvas that can't be rendered");
369         return;
370     }
371
372     FloatRect destRect(FloatPoint(), outputSize);
373     bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), { interpolationQualityForResizeQuality(options.resizeQuality) });
374
375     // 3. Create a new ImageBitmap object.
376     auto imageBitmap = create(WTFMove(bitmapData));
377
378     // 5. Set the origin-clean flag of the ImageBitmap object's bitmap to the same value as
379     //    the origin-clean flag of the canvas element's bitmap.
380
381     imageBitmap->m_originClean = canvasElement->originClean();
382
383     // 6. Return a new promise, but continue running these steps in parallel.
384     // 7. Resolve the promise with the new ImageBitmap object as the value.
385
386     promise.resolve(WTFMove(imageBitmap));
387 }
388
389 #if ENABLE(VIDEO)
390 void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, RefPtr<HTMLVideoElement>& video, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
391 {
392     // https://html.spec.whatwg.org/multipage/#dom-createimagebitmap
393     // WHATWG HTML 2102913b313078cd8eeac7e81e6a8756cbd3e773
394     // Steps 3-7.
395     // (Step 3 is handled in croppedSourceRectangleWithFormatting.)
396
397     // 4. Check the usability of the image argument. If this throws an exception
398     //    or returns bad, then return p rejected with an "InvalidStateError"
399     //    DOMException.
400     if (video->readyState() == HTMLMediaElement::HAVE_NOTHING || video->readyState() == HTMLMediaElement::HAVE_METADATA) {
401         promise.reject(InvalidStateError, "Cannot create ImageBitmap before the HTMLVideoElement has data");
402         return;
403     }
404
405     // 6.1. If image's networkState attribute is NETWORK_EMPTY, then return p
406     //      rejected with an "InvalidStateError" DOMException.
407     if (video->networkState() == HTMLMediaElement::NETWORK_EMPTY) {
408         promise.reject(InvalidStateError, "Cannot create ImageBitmap before the HTMLVideoElement has data");
409         return;
410     }
411
412     // 6.2. Set imageBitmap's bitmap data to a copy of the frame at the current
413     //      playback position, at the media resource's intrinsic width and
414     //      intrinsic height (i.e., after any aspect-ratio correction has been
415     //      applied), cropped to the source rectangle with formatting.
416     auto size = video->player() ? roundedIntSize(video->player()->naturalSize()) : IntSize();
417     auto maybeSourceRectangle = croppedSourceRectangleWithFormatting(size, options, WTFMove(rect));
418     if (maybeSourceRectangle.hasException()) {
419         promise.reject(maybeSourceRectangle.releaseException());
420         return;
421     }
422     auto sourceRectangle = maybeSourceRectangle.releaseReturnValue();
423
424     auto outputSize = outputSizeForSourceRectangle(sourceRectangle, options);
425     auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
426
427     {
428         GraphicsContext& c = bitmapData->context();
429         GraphicsContextStateSaver stateSaver(c);
430         c.clip(FloatRect(FloatPoint(), outputSize));
431         auto scaleX = float(outputSize.width()) / float(sourceRectangle.width());
432         auto scaleY = float(outputSize.height()) / float(sourceRectangle.height());
433         c.scale(FloatSize(scaleX, scaleY));
434         c.translate(-sourceRectangle.location());
435         video->paintCurrentFrameInContext(c, FloatRect(FloatPoint(), size));
436     }
437
438     // 5. Let imageBitmap be a new ImageBitmap object.
439     auto imageBitmap = create(WTFMove(bitmapData));
440
441     // 6.3. If the origin of image's video is not same origin with entry
442     //      settings object's origin, then set the origin-clean flag of
443     //      image's bitmap to false.
444     imageBitmap->m_originClean = !taintsOrigin(scriptExecutionContext.securityOrigin(), *video);
445
446     // 6.4.1. Resolve p with imageBitmap.
447     promise.resolve(WTFMove(imageBitmap));
448 }
449 #endif
450
451 #if ENABLE(CSS_TYPED_OM)
452 void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<TypedOMCSSImageValue>&, ImageBitmapOptions&&, Optional<IntRect>, ImageBitmap::Promise&& promise)
453 {
454     promise.reject(InvalidStateError, "Not implemented");
455 }
456 #endif
457
458 void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<ImageBitmap>& existingImageBitmap, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
459 {
460     // 2. If image's [[Detached]] internal slot value is true, return a promise
461     //    rejected with an "InvalidStateError" DOMException and abort these steps.
462     if (existingImageBitmap->isDetached() || !existingImageBitmap->buffer()) {
463         promise.reject(InvalidStateError, "Cannot create ImageBitmap from a detached ImageBitmap");
464         return;
465     }
466
467     // 4. Let the ImageBitmap object's bitmap data be a copy of the image argument's
468     //    bitmap data, cropped to the source rectangle with formatting.
469     auto sourceRectangle = croppedSourceRectangleWithFormatting(existingImageBitmap->buffer()->logicalSize(), options, WTFMove(rect));
470     if (sourceRectangle.hasException()) {
471         promise.reject(sourceRectangle.releaseException());
472         return;
473     }
474
475     auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
476     auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
477
478     auto imageForRender = existingImageBitmap->buffer()->copyImage();
479
480     FloatRect destRect(FloatPoint(), outputSize);
481     bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), { interpolationQualityForResizeQuality(options.resizeQuality) });
482
483     // 3. Create a new ImageBitmap object.
484     auto imageBitmap = create(WTFMove(bitmapData));
485
486     // 5. Set the origin-clean flag of the ImageBitmap object's bitmap to the same
487     //    value as the origin-clean flag of the bitmap of the image argument.
488     imageBitmap->m_originClean = existingImageBitmap->originClean();
489
490     // 6. Return a new promise, but continue running these steps in parallel.
491     // 7. Resolve the promise with the new ImageBitmap object as the value.
492     promise.resolve(WTFMove(imageBitmap));
493 }
494
495 class ImageBitmapImageObserver final : public RefCounted<ImageBitmapImageObserver>, public ImageObserver {
496 public:
497     static Ref<ImageBitmapImageObserver> create(String mimeType, long long expectedContentLength, const URL& sourceUrl)
498     {
499         return adoptRef(*new ImageBitmapImageObserver(mimeType, expectedContentLength, sourceUrl));
500     }
501
502     URL sourceUrl() const override { return m_sourceUrl; }
503     String mimeType() const override { return m_mimeType; }
504     long long expectedContentLength() const override { return m_expectedContentLength; }
505
506     void decodedSizeChanged(const Image&, long long) override { }
507
508     void didDraw(const Image&) override { }
509
510     bool canDestroyDecodedData(const Image&) override { return true; }
511     void imageFrameAvailable(const Image&, ImageAnimatingState, const IntRect* = nullptr, DecodingStatus = DecodingStatus::Invalid) override { }
512     void changedInRect(const Image&, const IntRect* = nullptr) override { }
513
514 private:
515     ImageBitmapImageObserver(String mimeType, long long expectedContentLength, const URL& sourceUrl)
516         : m_mimeType(mimeType)
517         , m_expectedContentLength(expectedContentLength)
518         , m_sourceUrl(sourceUrl)
519     { }
520
521     String m_mimeType;
522     long long m_expectedContentLength;
523     URL m_sourceUrl;
524 };
525
526 class PendingImageBitmap final : public ActiveDOMObject, public FileReaderLoaderClient {
527 public:
528     static void fetch(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>&& blob, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
529     {
530         auto pendingImageBitmap = new PendingImageBitmap(scriptExecutionContext, WTFMove(blob), WTFMove(options), WTFMove(rect), WTFMove(promise));
531         pendingImageBitmap->start(scriptExecutionContext);
532     }
533
534 private:
535     PendingImageBitmap(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>&& blob, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
536         : ActiveDOMObject(&scriptExecutionContext)
537         , m_blobLoader(FileReaderLoader::ReadAsArrayBuffer, this)
538         , m_blob(WTFMove(blob))
539         , m_options(WTFMove(options))
540         , m_rect(WTFMove(rect))
541         , m_promise(WTFMove(promise))
542     {
543         suspendIfNeeded();
544     }
545
546     void start(ScriptExecutionContext& scriptExecutionContext)
547     {
548         m_blobLoader.start(&scriptExecutionContext, *m_blob);
549     }
550
551     // ActiveDOMObject
552
553     const char* activeDOMObjectName() const override
554     {
555         return "PendingImageBitmap";
556     }
557
558     bool canSuspendForDocumentSuspension() const override
559     {
560         // FIXME: Deal with suspension.
561         return false;
562     }
563
564     // FileReaderLoaderClient
565
566     void didStartLoading() override
567     {
568     }
569
570     void didReceiveData() override
571     {
572     }
573
574     void didFinishLoading() override
575     {
576         createImageBitmap(m_blobLoader.arrayBufferResult());
577         delete this;
578     }
579
580     void didFail(int) override
581     {
582         createImageBitmap(nullptr);
583         delete this;
584     }
585
586     void createImageBitmap(RefPtr<ArrayBuffer>&& arrayBuffer)
587     {
588         if (!arrayBuffer) {
589             m_promise.reject(InvalidStateError, "An error occured reading the Blob argument to createImageBitmap");
590             return;
591         }
592
593         ImageBitmap::createFromBuffer(arrayBuffer.releaseNonNull(), m_blob->type(), m_blob->size(), m_blobLoader.url(), WTFMove(m_options), WTFMove(m_rect), WTFMove(m_promise));
594     }
595
596     FileReaderLoader m_blobLoader;
597     RefPtr<Blob> m_blob;
598     ImageBitmapOptions m_options;
599     Optional<IntRect> m_rect;
600     ImageBitmap::Promise m_promise;
601 };
602
603 void ImageBitmap::createFromBuffer(
604     Ref<ArrayBuffer>&& arrayBuffer,
605     String mimeType,
606     long long expectedContentLength,
607     const URL& sourceUrl,
608     ImageBitmapOptions&& options,
609     Optional<IntRect> rect,
610     ImageBitmap::Promise&& promise)
611 {
612     if (!arrayBuffer->byteLength()) {
613         promise.reject(InvalidStateError, "Cannot create an ImageBitmap from an empty buffer");
614         return;
615     }
616
617     auto sharedBuffer = SharedBuffer::create(static_cast<const char*>(arrayBuffer->data()), arrayBuffer->byteLength());
618     auto observer = ImageBitmapImageObserver::create(mimeType, expectedContentLength, sourceUrl);
619     auto image = Image::create(observer.get());
620     if (!image) {
621         promise.reject(InvalidStateError, "The type of the argument to createImageBitmap is not supported");
622         return;
623     }
624
625     auto result = image->setData(sharedBuffer.copyRef(), true);
626     if (result != EncodedDataStatus::Complete) {
627         promise.reject(InvalidStateError, "Cannot decode the data in the argument to createImageBitmap");
628         return;
629     }
630
631     auto sourceRectangle = croppedSourceRectangleWithFormatting(roundedIntSize(image->size()), options, rect);
632     if (sourceRectangle.hasException()) {
633         promise.reject(sourceRectangle.releaseException());
634         return;
635     }
636
637     auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
638     auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
639     if (!bitmapData) {
640         promise.reject(InvalidStateError, "Cannot create an image buffer from the argument to createImageBitmap");
641         return;
642     }
643
644     FloatRect destRect(FloatPoint(), outputSize);
645     bitmapData->context().drawImage(*image, destRect, sourceRectangle.releaseReturnValue(), { interpolationQualityForResizeQuality(options.resizeQuality) });
646
647     auto imageBitmap = create(WTFMove(bitmapData));
648
649     promise.resolve(WTFMove(imageBitmap));
650 }
651
652 void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>& blob, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
653 {
654     // 2. Return a new promise, but continue running these steps in parallel.
655     PendingImageBitmap::fetch(scriptExecutionContext, WTFMove(blob), WTFMove(options), WTFMove(rect), WTFMove(promise));
656 }
657
658 void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<ImageData>& imageData, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
659 {
660     UNUSED_PARAM(imageData);
661     UNUSED_PARAM(options);
662     UNUSED_PARAM(rect);
663
664     // 2. If the image object's data attribute value's [[Detached]] internal slot value
665     //    is true, return a promise rejected with an "InvalidStateError" DOMException
666     //    and abort these steps.
667
668     // 3. Create a new ImageBitmap object.
669
670     // 4. Let the ImageBitmap object's bitmap data be the image data given by the ImageData
671     //    object, cropped to the source rectangle with formatting.
672
673     // 5. Return a new promise, but continue running these steps in parallel.
674     // 6. Resolve the promise with the new ImageBitmap object as the value.
675     promise.reject(TypeError, "createImageBitmap with ImageData is not implemented");
676 }
677
678 ImageBitmap::ImageBitmap(std::unique_ptr<ImageBuffer>&& buffer)
679     : m_bitmapData(WTFMove(buffer))
680 {
681     ASSERT(m_bitmapData);
682 }
683
684 ImageBitmap::~ImageBitmap() = default;
685
686 unsigned ImageBitmap::width() const
687 {
688     if (m_detached || !m_bitmapData)
689         return 0;
690
691     // FIXME: Is this the right width?
692     return m_bitmapData->logicalSize().width();
693 }
694
695 unsigned ImageBitmap::height() const
696 {
697     if (m_detached || !m_bitmapData)
698         return 0;
699
700     // FIXME: Is this the right height?
701     return m_bitmapData->logicalSize().height();
702 }
703
704 void ImageBitmap::close()
705 {
706     m_detached = true;
707     m_bitmapData = nullptr;
708 }
709
710 std::unique_ptr<ImageBuffer> ImageBitmap::transferOwnershipAndClose()
711 {
712     m_detached = true;
713     return WTFMove(m_bitmapData);
714 }
715
716 }