summaryrefslogtreecommitdiff
path: root/devdocs/html/cors_enabled_image.html
blob: da89c8b1568503f347ae387cbfc4013d5240015b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<header><h1>Allowing cross-origin use of images and canvas</h1></header><div class="section-content">
<p>HTML provides a <a href="element/img#crossorigin"><code>crossorigin</code></a> attribute for images that, in combination with an appropriate <a href="https://developer.mozilla.org/en-US/docs/Glossary/CORS">CORS</a> header, allows images defined by the <a href="element/img"><code>&lt;img&gt;</code></a> element that are loaded from foreign origins to be used in a <a href="element/canvas"><code>&lt;canvas&gt;</code></a> as if they had been loaded from the current origin.</p> <p>See <a href="attributes/crossorigin">CORS settings attributes</a> for details on how the <code>crossorigin</code> attribute is used.</p>
</div>
<h2 id="security_and_tainted_canvases">Security and tainted canvases</h2>
<div class="section-content">
<p>Because the pixels in a canvas's bitmap can come from a variety of sources, including images or videos retrieved from other hosts, it's inevitable that security problems may arise.</p> <p>As soon as you draw into a canvas any data that was loaded from another origin without CORS approval, the canvas becomes <strong>tainted</strong>. A tainted canvas is one which is no longer considered secure, and any attempts to retrieve image data back from the canvas will cause an exception to be thrown.</p> <p>If the source of the foreign content is an HTML <a href="element/img"><code>&lt;img&gt;</code></a> or SVG <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg"><code>&lt;svg&gt;</code></a> element, attempting to retrieve the contents of the canvas isn't allowed.</p> <p>If the foreign content comes from an image obtained from either as <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement"><code>HTMLCanvasElement</code></a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap"><code>ImageBitMap</code></a>, and the image source doesn't meet the same origin rules, attempts to read the canvas's contents are blocked.</p> <p>Calling any of the following on a tainted canvas will result in an error:</p> <ul> <li>Calling <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData"><code>getImageData()</code></a> on the canvas's context</li> <li>Calling <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob"><code>toBlob()</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL"><code>toDataURL()</code></a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream"><code>captureStream()</code></a> on the <a href="element/canvas"><code>&lt;canvas&gt;</code></a> element itself</li> </ul> <p>Attempting any of these when the canvas is tainted will cause a <code>SecurityError</code> to be thrown. This protects users from having private data exposed by using images to pull information from remote websites without permission.</p>
</div>
<h2 id="storing_an_image_from_a_foreign_origin">Storing an image from a foreign origin</h2>
<div class="section-content"><p>In this example, we wish to permit images from a foreign origin to be retrieved and saved to local storage. Implementing this requires configuring the server as well as writing code for the website itself.</p></div>
<h3 id="web_server_configuration">Web server configuration</h3>
<div class="section-content">
<p>The first thing we need is a server that's configured to host images with the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"><code>Access-Control-Allow-Origin</code></a> header configured to permit cross-origin access to image files.</p> <p>Let's assume we're serving our site using <a href="https://httpd.apache.org/" target="_blank">Apache</a>. Consider the HTML5 Boilerplate <a href="https://github.com/h5bp/server-configs-apache/blob/main/h5bp/cross-origin/images.conf" target="_blank">Apache server configuration file for CORS images</a>, shown below:</p> <div class="code-example">
<p class="example-header"><span class="language-name">xml</span></p>
<pre data-signature="KtljV8NlonBpWlni1/DGLAm1J9TsYGMd6vGgrW6na5U=" data-language="xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>IfModule</span> <span class="token attr-name">mod_setenvif.c</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>IfModule</span> <span class="token attr-name">mod_headers.c</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FilesMatch</span> <span class="token attr-name">"\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$"</span><span class="token punctuation">&gt;</span></span>
      SetEnvIf Origin ":" IS_CORS
      Header set Access-Control-Allow-Origin "*" env=IS_CORS
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>FilesMatch</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>IfModule</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>IfModule</span><span class="token punctuation">&gt;</span></span>
</pre>
</div> <p>In short, this configures the server to allow graphic files (those with the extensions ".bmp", ".cur", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".svg", ".svgz", and ".webp") to be accessed cross-origin from anywhere on the internet.</p>
</div>
<h3 id="implementing_the_save_feature">Implementing the save feature</h3>
<div class="section-content">
<p>Now that the server has been configured to allow retrieval of the images cross-origin, we can write the code that allows the user to save them to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">local storage</a>, just as if they were being served from the same domain the code is running on.</p> <p>The key is to use the <a href="element/image#crossorigin"><code>crossorigin</code></a> attribute by setting <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin"><code>crossOrigin</code></a> on the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement"><code>HTMLImageElement</code></a> into which the image will be loaded. This tells the browser to request cross-origin access when downloading the image data.</p> <h4 id="starting_the_download">Starting the download</h4> <p>The code that starts the download (say, when the user clicks a "Download" button), looks like this:</p> <div class="code-example">
<p class="example-header"><span class="language-name">js</span></p>
<pre data-signature="8ZWVlQdjM6pUzGpr9gtGZfci22u22gUZKr2lJscagDY=" data-language="js"><span class="token keyword">function</span> <span class="token function">startDownload</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> imageURL <span class="token operator">=</span>
    <span class="token string">"https://cdn.glitch.com/4c9ebeb9-8b9a-4adc-ad0a-238d9ae00bb5%2Fmdn_logo-only_color.svg?1535749917189"</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> imageDescription <span class="token operator">=</span> <span class="token string">"The Mozilla logo"</span><span class="token punctuation">;</span>

  downloadedImg <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Image</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  downloadedImg<span class="token punctuation">.</span>crossOrigin <span class="token operator">=</span> <span class="token string">"anonymous"</span><span class="token punctuation">;</span>
  downloadedImg<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"load"</span><span class="token punctuation">,</span> imageReceived<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  downloadedImg<span class="token punctuation">.</span>alt <span class="token operator">=</span> imageDescription<span class="token punctuation">;</span>
  downloadedImg<span class="token punctuation">.</span>src <span class="token operator">=</span> imageURL<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</pre>
</div> <p>We're using a hard-coded URL (<code>imageURL</code>) and associated descriptive text (<code>imageDescription</code>) here, but that could easily come from anywhere. To begin downloading the image, we create a new <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement"><code>HTMLImageElement</code></a> object by using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image"><code>Image()</code></a> constructor. The image is then configured to allow cross-origin downloading by setting its <code>crossOrigin</code> attribute to <code>"Anonymous"</code> (that is, allow non-authenticated downloading of the image cross-origin). An event listener is added for the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event"><code>load</code></a> event being fired on the image element, which means the image data has been received. Alternative text is added to the image; while <code>&lt;canvas&gt;</code> does not support the <code>alt</code> attribute, the value can be used to set an <code>aria-label</code> or the canvas's inner content.</p> <p>Finally, the image's <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/src"><code>src</code></a> attribute is set to the URL of the image to download; this triggers the download to begin.</p> <h4 id="receiving_and_saving_the_image">Receiving and saving the image</h4> <p>The code that handles the newly-downloaded image is found in the <code>imageReceived()</code> method:</p> <div class="code-example">
<p class="example-header"><span class="language-name">js</span></p>
<pre data-signature="/oVzaUfYCcVv86f2dn5CLZYfWQ2sYAfpnGWQHOJ5Sc8=" data-language="js"><span class="token keyword">function</span> <span class="token function">imageReceived</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> canvas <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"canvas"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> context <span class="token operator">=</span> canvas<span class="token punctuation">.</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token string">"2d"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  canvas<span class="token punctuation">.</span>width <span class="token operator">=</span> downloadedImg<span class="token punctuation">.</span>width<span class="token punctuation">;</span>
  canvas<span class="token punctuation">.</span>height <span class="token operator">=</span> downloadedImg<span class="token punctuation">.</span>height<span class="token punctuation">;</span>
  canvas<span class="token punctuation">.</span>innerText <span class="token operator">=</span> downloadedImg<span class="token punctuation">.</span>alt<span class="token punctuation">;</span>

  context<span class="token punctuation">.</span><span class="token function">drawImage</span><span class="token punctuation">(</span>downloadedImg<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  imageBox<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>canvas<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token string">"saved-image-example"</span><span class="token punctuation">,</span> canvas<span class="token punctuation">.</span><span class="token function">toDataURL</span><span class="token punctuation">(</span><span class="token string">"image/png"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Error: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>err<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</pre>
</div> <p><code>imageReceived()</code> is called to handle the <code>"load"</code> event on the <code>HTMLImageElement</code> that receives the downloaded image. This event is triggered once the downloaded data is all available. It begins by creating a new <a href="element/canvas"><code>&lt;canvas&gt;</code></a> element that we'll use to convert the image into a data URL, and by getting access to the canvas's 2D drawing context (<a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D"><code>CanvasRenderingContext2D</code></a>) in the variable <code>context</code>.</p> <p>The canvas's size is adjusted to match the received image, the inner text is set to the image description, then the image is drawn into the canvas using <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage"><code>drawImage()</code></a>. The canvas is then inserted into the document so the image is visible.</p> <p>Now it's time to actually save the image locally. To do this, we use the Web Storage API's local storage mechanism, which is accessed through the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage"><code>localStorage</code></a> global. The canvas method <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL"><code>toDataURL()</code></a> is used to convert the image into a data:// URL representing a PNG image, which is then saved into local storage using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem"><code>setItem()</code></a>.</p>
</div>
<h2 id="see_also">See also</h2>
<div class="section-content"><ul> <li><a href="https://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html" target="_blank">Using Cross-domain images in WebGL and Chrome 13</a></li> <li><a href="https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-crossorigin" target="_blank">HTML Specification - the <code>crossorigin</code> attribute</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a></li> </ul></div><div class="_attribution">
  <p class="_attribution-p">
    &copy; 2005&ndash;2023 MDN contributors.<br>Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.<br>
    <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image" class="_attribution-link">https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image</a>
  </p>
</div>