1 /* 2 * Copyright (c) 2013, Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "modules/imagebitmap/ImageBitmapFactories.h" 33 34 #include "bindings/v8/ExceptionState.h" 35 #include "bindings/v8/ScriptPromiseResolver.h" 36 #include "bindings/v8/ScriptPromiseResolverWithContext.h" 37 #include "core/dom/ExecutionContext.h" 38 #include "core/fileapi/Blob.h" 39 #include "core/html/HTMLCanvasElement.h" 40 #include "core/html/HTMLImageElement.h" 41 #include "core/html/HTMLVideoElement.h" 42 #include "core/html/ImageData.h" 43 #include "core/html/canvas/CanvasRenderingContext2D.h" 44 #include "core/frame/LocalDOMWindow.h" 45 #include "core/frame/ImageBitmap.h" 46 #include "core/workers/WorkerGlobalScope.h" 47 #include "platform/SharedBuffer.h" 48 #include "platform/graphics/BitmapImage.h" 49 #include "platform/graphics/ImageSource.h" 50 #include "platform/graphics/skia/NativeImageSkia.h" 51 #include <v8.h> 52 53 namespace WebCore { 54 55 static LayoutSize sizeFor(HTMLImageElement* image) 56 { 57 if (ImageResource* cachedImage = image->cachedImage()) 58 return cachedImage->imageSizeForRenderer(image->renderer(), 1.0f); // FIXME: Not sure about this. 59 return IntSize(); 60 } 61 62 static IntSize sizeFor(HTMLVideoElement* video) 63 { 64 if (blink::WebMediaPlayer* webMediaPlayer = video->webMediaPlayer()) 65 return webMediaPlayer->naturalSize(); 66 return IntSize(); 67 } 68 69 static ScriptPromise fulfillImageBitmap(ScriptState* scriptState, PassRefPtrWillBeRawPtr<ImageBitmap> imageBitmap) 70 { 71 RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState); 72 ScriptPromise promise = resolver->promise(); 73 if (imageBitmap) { 74 resolver->resolve(imageBitmap); 75 } else { 76 resolver->reject(ScriptValue(scriptState, v8::Null(scriptState->isolate()))); 77 } 78 return promise; 79 } 80 81 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, HTMLImageElement* image, ExceptionState& exceptionState) 82 { 83 LayoutSize s = sizeFor(image); 84 return createImageBitmap(scriptState, eventTarget, image, 0, 0, s.width(), s.height(), exceptionState); 85 } 86 87 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, HTMLImageElement* image, int sx, int sy, int sw, int sh, ExceptionState& exceptionState) 88 { 89 // This variant does not work in worker threads. 90 ASSERT(eventTarget.toDOMWindow()); 91 92 if (!image->cachedImage()) { 93 exceptionState.throwDOMException(InvalidStateError, "No image can be retrieved from the provided element."); 94 return ScriptPromise(); 95 } 96 if (image->cachedImage()->image()->isSVGImage()) { 97 exceptionState.throwDOMException(InvalidStateError, "The image element contains an SVG image, which is unsupported."); 98 return ScriptPromise(); 99 } 100 if (!sw || !sh) { 101 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s provided is 0.", sw ? "height" : "width")); 102 return ScriptPromise(); 103 } 104 if (!image->cachedImage()->image()->currentFrameHasSingleSecurityOrigin()) { 105 exceptionState.throwSecurityError("The source image contains image data from multiple origins."); 106 return ScriptPromise(); 107 } 108 if (!image->cachedImage()->passesAccessControlCheck(eventTarget.toDOMWindow()->document()->securityOrigin()) && eventTarget.toDOMWindow()->document()->securityOrigin()->taintsCanvas(image->src())) { 109 exceptionState.throwSecurityError("Cross-origin access to the source image is denied."); 110 return ScriptPromise(); 111 } 112 // FIXME: make ImageBitmap creation asynchronous crbug.com/258082 113 return fulfillImageBitmap(scriptState, ImageBitmap::create(image, IntRect(sx, sy, sw, sh))); 114 } 115 116 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, HTMLVideoElement* video, ExceptionState& exceptionState) 117 { 118 IntSize s = sizeFor(video); 119 return createImageBitmap(scriptState, eventTarget, video, 0, 0, s.width(), s.height(), exceptionState); 120 } 121 122 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, HTMLVideoElement* video, int sx, int sy, int sw, int sh, ExceptionState& exceptionState) 123 { 124 // This variant does not work in worker threads. 125 ASSERT(eventTarget.toDOMWindow()); 126 127 if (video->networkState() == HTMLMediaElement::NETWORK_EMPTY) { 128 exceptionState.throwDOMException(InvalidStateError, "The provided element has not retrieved data."); 129 return ScriptPromise(); 130 } 131 // FIXME: Remove the below null check once we fix the bug 382721 132 if (video->readyState() <= HTMLMediaElement::HAVE_METADATA || !video->webMediaPlayer()) { 133 exceptionState.throwDOMException(InvalidStateError, "The provided element's player has no current data."); 134 return ScriptPromise(); 135 } 136 if (!sw || !sh) { 137 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s provided is 0.", sw ? "height" : "width")); 138 return ScriptPromise(); 139 } 140 if (!video->hasSingleSecurityOrigin()) { 141 exceptionState.throwSecurityError("The source video contains image data from multiple origins."); 142 return ScriptPromise(); 143 } 144 if (!video->webMediaPlayer()->didPassCORSAccessCheck() 145 && eventTarget.toDOMWindow()->document()->securityOrigin()->taintsCanvas(video->currentSrc())) { 146 exceptionState.throwSecurityError("Cross-origin access to the source video is denied."); 147 return ScriptPromise(); 148 } 149 // FIXME: make ImageBitmap creation asynchronous crbug.com/258082 150 return fulfillImageBitmap(scriptState, ImageBitmap::create(video, IntRect(sx, sy, sw, sh))); 151 } 152 153 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, CanvasRenderingContext2D* context, ExceptionState& exceptionState) 154 { 155 return createImageBitmap(scriptState, eventTarget, context->canvas(), exceptionState); 156 } 157 158 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, CanvasRenderingContext2D* context, int sx, int sy, int sw, int sh, ExceptionState& exceptionState) 159 { 160 return createImageBitmap(scriptState, eventTarget, context->canvas(), sx, sy, sw, sh, exceptionState); 161 } 162 163 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, HTMLCanvasElement* canvas, ExceptionState& exceptionState) 164 { 165 return createImageBitmap(scriptState, eventTarget, canvas, 0, 0, canvas->width(), canvas->height(), exceptionState); 166 } 167 168 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, HTMLCanvasElement* canvas, int sx, int sy, int sw, int sh, ExceptionState& exceptionState) 169 { 170 // This variant does not work in worker threads. 171 ASSERT(eventTarget.toDOMWindow()); 172 173 if (!canvas->originClean()) { 174 exceptionState.throwSecurityError("The canvas element provided is tainted with cross-origin data."); 175 return ScriptPromise(); 176 } 177 if (!sw || !sh) { 178 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s provided is 0.", sw ? "height" : "width")); 179 return ScriptPromise(); 180 } 181 182 // FIXME: make ImageBitmap creation asynchronous crbug.com/258082 183 return fulfillImageBitmap(scriptState, canvas->buffer() ? ImageBitmap::create(canvas, IntRect(sx, sy, sw, sh)) : nullptr); 184 } 185 186 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, Blob* blob, ExceptionState& exceptionState) 187 { 188 ImageBitmapLoader* loader = ImageBitmapFactories::ImageBitmapLoader::create(from(eventTarget), IntRect(), scriptState); 189 ScriptPromise promise = loader->promise(); 190 from(eventTarget).addLoader(loader); 191 loader->loadBlobAsync(eventTarget.executionContext(), blob); 192 return promise; 193 } 194 195 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, Blob* blob, int sx, int sy, int sw, int sh, ExceptionState& exceptionState) 196 { 197 if (!sw || !sh) { 198 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s provided is 0.", sw ? "height" : "width")); 199 return ScriptPromise(); 200 } 201 ImageBitmapLoader* loader = ImageBitmapFactories::ImageBitmapLoader::create(from(eventTarget), IntRect(sx, sy, sw, sh), scriptState); 202 ScriptPromise promise = loader->promise(); 203 from(eventTarget).addLoader(loader); 204 loader->loadBlobAsync(eventTarget.executionContext(), blob); 205 return promise; 206 } 207 208 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, ImageData* data, ExceptionState& exceptionState) 209 { 210 return createImageBitmap(scriptState, eventTarget, data, 0, 0, data->width(), data->height(), exceptionState); 211 } 212 213 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, ImageData* data, int sx, int sy, int sw, int sh, ExceptionState& exceptionState) 214 { 215 if (!sw || !sh) { 216 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s provided is 0.", sw ? "height" : "width")); 217 return ScriptPromise(); 218 } 219 // FIXME: make ImageBitmap creation asynchronous crbug.com/258082 220 return fulfillImageBitmap(scriptState, ImageBitmap::create(data, IntRect(sx, sy, sw, sh))); 221 } 222 223 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, ImageBitmap* bitmap, ExceptionState& exceptionState) 224 { 225 return createImageBitmap(scriptState, eventTarget, bitmap, 0, 0, bitmap->width(), bitmap->height(), exceptionState); 226 } 227 228 ScriptPromise ImageBitmapFactories::createImageBitmap(ScriptState* scriptState, EventTarget& eventTarget, ImageBitmap* bitmap, int sx, int sy, int sw, int sh, ExceptionState& exceptionState) 229 { 230 if (!sw || !sh) { 231 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s provided is 0.", sw ? "height" : "width")); 232 return ScriptPromise(); 233 } 234 // FIXME: make ImageBitmap creation asynchronous crbug.com/258082 235 return fulfillImageBitmap(scriptState, ImageBitmap::create(bitmap, IntRect(sx, sy, sw, sh))); 236 } 237 238 const char* ImageBitmapFactories::supplementName() 239 { 240 return "ImageBitmapFactories"; 241 } 242 243 ImageBitmapFactories& ImageBitmapFactories::from(EventTarget& eventTarget) 244 { 245 if (LocalDOMWindow* window = eventTarget.toDOMWindow()) 246 return fromInternal(*window); 247 248 ASSERT(eventTarget.executionContext()->isWorkerGlobalScope()); 249 return ImageBitmapFactories::fromInternal(*toWorkerGlobalScope(eventTarget.executionContext())); 250 } 251 252 template<class GlobalObject> 253 ImageBitmapFactories& ImageBitmapFactories::fromInternal(GlobalObject& object) 254 { 255 ImageBitmapFactories* supplement = static_cast<ImageBitmapFactories*>(WillBeHeapSupplement<GlobalObject>::from(object, supplementName())); 256 if (!supplement) { 257 supplement = new ImageBitmapFactories(); 258 WillBeHeapSupplement<GlobalObject>::provideTo(object, supplementName(), adoptPtrWillBeNoop(supplement)); 259 } 260 return *supplement; 261 } 262 263 void ImageBitmapFactories::addLoader(ImageBitmapLoader* loader) 264 { 265 m_pendingLoaders.add(loader); 266 } 267 268 void ImageBitmapFactories::didFinishLoading(ImageBitmapLoader* loader) 269 { 270 ASSERT(m_pendingLoaders.contains(loader)); 271 m_pendingLoaders.remove(loader); 272 } 273 274 ImageBitmapFactories::ImageBitmapLoader::ImageBitmapLoader(ImageBitmapFactories& factory, const IntRect& cropRect, ScriptState* scriptState) 275 : m_loader(FileReaderLoader::ReadAsArrayBuffer, this) 276 , m_factory(&factory) 277 , m_resolver(ScriptPromiseResolverWithContext::create(scriptState)) 278 , m_cropRect(cropRect) 279 { 280 } 281 282 void ImageBitmapFactories::ImageBitmapLoader::loadBlobAsync(ExecutionContext* context, Blob* blob) 283 { 284 m_loader.start(context, blob->blobDataHandle()); 285 } 286 287 void ImageBitmapFactories::trace(Visitor* visitor) 288 { 289 visitor->trace(m_pendingLoaders); 290 WillBeHeapSupplement<LocalDOMWindow>::trace(visitor); 291 WillBeHeapSupplement<WorkerGlobalScope>::trace(visitor); 292 } 293 294 void ImageBitmapFactories::ImageBitmapLoader::rejectPromise() 295 { 296 m_resolver->reject(ScriptValue(m_resolver->scriptState(), v8::Null(m_resolver->scriptState()->isolate()))); 297 m_factory->didFinishLoading(this); 298 } 299 300 void ImageBitmapFactories::ImageBitmapLoader::didFinishLoading() 301 { 302 if (!m_loader.arrayBufferResult()) { 303 rejectPromise(); 304 return; 305 } 306 RefPtr<SharedBuffer> sharedBuffer = SharedBuffer::create((char*)m_loader.arrayBufferResult()->data(), m_loader.arrayBufferResult()->byteLength()); 307 308 OwnPtr<ImageSource> source = adoptPtr(new ImageSource()); 309 source->setData(*sharedBuffer, true); 310 RefPtr<NativeImageSkia> imageSkia = source->createFrameAtIndex(0); 311 if (!imageSkia) { 312 rejectPromise(); 313 return; 314 } 315 316 RefPtr<Image> image = BitmapImage::create(imageSkia); 317 if (!image->width() || !image->height()) { 318 rejectPromise(); 319 return; 320 } 321 if (!m_cropRect.width() && !m_cropRect.height()) { 322 // No cropping variant was called. 323 m_cropRect = IntRect(IntPoint(), image->size()); 324 } 325 326 RefPtrWillBeRawPtr<ImageBitmap> imageBitmap = ImageBitmap::create(image.get(), m_cropRect); 327 m_resolver->resolve(imageBitmap.release()); 328 m_factory->didFinishLoading(this); 329 } 330 331 void ImageBitmapFactories::ImageBitmapLoader::didFail(FileError::ErrorCode) 332 { 333 rejectPromise(); 334 } 335 336 void ImageBitmapFactories::ImageBitmapLoader::trace(Visitor* visitor) 337 { 338 visitor->trace(m_factory); 339 } 340 341 } // namespace WebCore 342