chromium/ios/chrome/browser/web/model/image_fetch/resources/image_fetch.ts

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Add functionality related to getting image data.
 */

import {gCrWeb} from '//ios/web/public/js_messaging/resources/gcrweb.js';
import {sendWebKitMessage} from '//ios/web/public/js_messaging/resources/utils.js';

/**
 * Returns image data as base64 string, because WKWebView does not support BLOB
 * on messages to native code.
 * @param id The ID for current call. It should be attached to the message
 *    sent back.
 * @param url The URL of the requested image.
 */
function getImageData(id: number, url: string): void {
  // `from` indicates where the `data` is fetched from.
  const onData = function(data: string, from: string) {
    sendWebKitMessage('ImageFetchMessageHandler',
        {'id': id,
         'data': data,
         'from': from
        });
  };
  const onError = function() {
    sendWebKitMessage('ImageFetchMessageHandler', {'id': id});
  };

  // Try getting data directly from <img> first.
  // If unsuccessful, download the data via XMLHttpRequest.
  const data = getImageDataByCanvas(url);
  if (data) {
    onData(data, 'canvas');
    return;
  }

  getImageDataByXMLHttpRequest(url, 100, onData, onError);
};

/**
 * Returns image data directly from <img> by drawing it to <canvas> and export
 * it. If the <img> is cross-origin without "crossorigin=anonymous", this would
 * be prevented by the browser. The exported image is in a resolution of 96 dpi.
 * The function returns image data in base64 string, or null in these cases:
 *   1. Image is a GIF because GIFs will become static after drawn to <canvas>;
 *   2. No <img> with "src=`url`" is found;
 *   3. Exporting data from <img> failed.
 */
function getImageDataByCanvas(url: string): string | null {
  const extension = url.split('.').pop();
  if (!extension || extension.toLowerCase() === 'gif')
    return null;

  for (const img of document.images) {
    if (img && img.src == url) {
      const canvas = document.createElement('canvas');
      canvas.width = img.naturalWidth;
      canvas.height = img.naturalHeight;
      const ctx = canvas.getContext('2d');
      if (!ctx) continue;
      ctx.drawImage(img, 0, 0);
      let data;
      try {
        // If the <img> is cross-domain without "crossorigin=anonymous", an
        // exception will be thrown.
        data = canvas.toDataURL('image/' + extension);
      } catch (error) {
        return null;
      }
      // Remove the "data:type/subtype;base64," header.
      return data.split(',')[1] as string;
    }
  }
  return null;
};

/**
 * Returns image data by downloading it using XMLHttpRequest.
 *
 * @param url The URL of the requested image.
 * @param timeout The timeout in milliseconds for XMLHttpRequest.
 * @param onData Callback when fetching image data succeeded.
 * @param onError Callback when fetching image data failed.
 */
function getImageDataByXMLHttpRequest(
    url: string, timeout: number,
    onData: Function, onError: (() => void)): void {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.timeout = timeout;
  xhr.responseType = 'blob';

  xhr.onload = function() {
    if (xhr.status != 200) {
      onError();
      return;
    }
    const fr = new FileReader();

    fr.onload = function() {
      onData(btoa(fr.result as string), 'xhr');
    };
    fr.onabort = onError;
    fr.onerror = onError;

    fr.readAsBinaryString(xhr.response);
  };
  xhr.onabort = onError;
  xhr.onerror = onError;
  xhr.ontimeout = onError;

  xhr.send();
};

gCrWeb.imageFetch = {getImageData}