Hello world"));
  info("Check that captureScreenshot() captures as JPEG format");
  const imageDefault = await Page.captureScreenshot({ format: "jpeg" });
  ok(!!imageDefault, "Screenshot data with default quality is not empty");
  const image100 = await Page.captureScreenshot({
    format: "jpeg",
    quality: 100,
  });
  ok(!!image100, "Screenshot data with quality 100 is not empty");
  const image10 = await Page.captureScreenshot({
    format: "jpeg",
    quality: 10,
  });
  ok(!!image10, "Screenshot data with quality 10 is not empty");
  const infoDefault = await getImageDetails(imageDefault.data);
  const info100 = await getImageDetails(image100.data);
  const info10 = await getImageDetails(image10.data);
  // All screenshots are of mimeType JPEG
  is(
    infoDefault.mimeType,
    "image/jpeg",
    "Screenshot with default quality has correct MIME type"
  );
  is(
    info100.mimeType,
    "image/jpeg",
    "Screenshot with quality 100 has correct MIME type"
  );
  is(
    info10.mimeType,
    "image/jpeg",
    "Screenshot with quality 10 has correct MIME type"
  );
  const scale = await getDevicePixelRatio();
  const viewport = await getViewportSize();
  // Images are all of the same dimension
  is(infoDefault.width, (viewport.width - viewport.x) * scale);
  is(infoDefault.height, (viewport.height - viewport.y) * scale);
  is(info100.width, (viewport.width - viewport.x) * scale);
  is(info100.height, (viewport.height - viewport.y) * scale);
  is(info10.width, (viewport.width - viewport.x) * scale);
  is(info10.height, (viewport.height - viewport.y) * scale);
  // Images of different quality result in different content sizes
  ok(
    info100.length > infoDefault.length,
    "Size of quality 100 is larger than default"
  );
  ok(
    info10.length < infoDefault.length,
    "Size of quality 10 is smaller than default"
  );
});
add_task(async function clipMissingProperties({ client }) {
  const { Page } = client;
  const contentSize = await getContentSize();
  for (const prop of ["x", "y", "width", "height", "scale"]) {
    console.info(`Check for missing ${prop}`);
    const clip = {
      x: 0,
      y: 0,
      width: contentSize.width,
      height: contentSize.height,
    };
    clip[prop] = undefined;
    let errorThrown = false;
    try {
      await Page.captureScreenshot({ clip });
    } catch (e) {
      errorThrown = true;
    }
    ok(errorThrown, `raised error for missing clip.${prop} property`);
  }
});
add_task(async function clipOutOfBoundsXAndY({ client }) {
  const { Page } = client;
  const ratio = await getDevicePixelRatio();
  const size = 50;
  await loadURLWithElement();
  const contentSize = await getContentSize();
  var { data: refData } = await Page.captureScreenshot({
    clip: {
      x: 0,
      y: 0,
      width: size,
      height: size,
      scale: 1,
    },
  });
  for (const x of [-1, contentSize.width]) {
    console.info(`Check out-of-bounds x for ${x}`);
    const { data } = await Page.captureScreenshot({
      clip: {
        x,
        y: 0,
        width: size,
        height: size,
        scale: 1,
      },
    });
    const { width, height } = await getImageDetails(data);
    is(width, size * ratio, "Image has expected width");
    is(height, size * ratio, "Image has expected height");
    is(data, refData, "Image is equal");
  }
  for (const y of [-1, contentSize.height]) {
    console.info(`Check out-of-bounds y for ${y}`);
    const { data } = await Page.captureScreenshot({
      clip: {
        x: 0,
        y,
        width: size,
        height: size,
        scale: 1,
      },
    });
    const { width, height } = await getImageDetails(data);
    is(width, size * ratio, "Image has expected width");
    is(height, size * ratio, "Image has expected height");
    is(data, refData, "Image is equal");
  }
});
add_task(async function clipOutOfBoundsWidthAndHeight({ client }) {
  const { Page } = client;
  const ratio = await getDevicePixelRatio();
  await loadURL(toDataURL("
Hello world"));
  const contentSize = await getContentSize();
  var { data: refData } = await Page.captureScreenshot({
    clip: {
      x: 0,
      y: 0,
      width: contentSize.width,
      height: contentSize.height,
      scale: 1,
    },
  });
  for (const value of [-1, 0]) {
    console.info(`Check out-of-bounds width for ${value}`);
    const clip = {
      x: 0,
      y: 0,
      width: value,
      height: contentSize.height,
      scale: 1,
    };
    const { data } = await Page.captureScreenshot({ clip });
    const { width, height } = await getImageDetails(data);
    is(width, contentSize.width * ratio, "Image has expected width");
    is(height, contentSize.height * ratio, "Image has expected height");
    is(data, refData, "Image is equal");
  }
  for (const value of [-1, 0]) {
    console.info(`Check out-of-bounds height for ${value}`);
    const clip = {
      x: 0,
      y: 0,
      width: contentSize.width,
      height: value,
      scale: 1,
    };
    const { data } = await Page.captureScreenshot({ clip });
    const { width, height } = await getImageDetails(data);
    is(width, contentSize.width * ratio, "Image has expected width");
    is(height, contentSize.height * ratio, "Image has expected height");
    is(data, refData, "Image is equal");
  }
});
add_task(async function clipOutOfBoundsScale({ client }) {
  const { Page } = client;
  const ratio = await getDevicePixelRatio();
  await loadURLWithElement();
  const contentSize = await getContentSize();
  var { data: refData } = await Page.captureScreenshot({
    clip: {
      x: 0,
      y: 0,
      width: contentSize.width,
      height: contentSize.height,
      scale: 1,
    },
  });
  for (const value of [-1, 0]) {
    console.info(`Check out-of-bounds scale for ${value}`);
    var { data } = await Page.captureScreenshot({
      clip: {
        x: 0,
        y: 0,
        width: 50,
        height: 50,
        scale: value,
      },
    });
    const { width, height } = await getImageDetails(data);
    is(width, contentSize.width * ratio, "Image has expected width");
    is(height, contentSize.height * ratio, "Image has expected height");
    is(data, refData, "Image is equal");
  }
});
add_task(async function clipScale({ client }) {
  const { Page } = client;
  const ratio = await getDevicePixelRatio();
  for (const scale of [1.5, 2]) {
    console.info(`Check scale for ${scale}`);
    await loadURLWithElement({ width: 100 * scale, height: 100 * scale });
    var { data: refData } = await Page.captureScreenshot({
      clip: {
        x: 0,
        y: 0,
        width: 100 * scale,
        height: 100 * scale,
        scale: 1,
      },
    });
    await loadURLWithElement({ width: 100, height: 100 });
    var { data } = await Page.captureScreenshot({
      clip: {
        x: 0,
        y: 0,
        width: 100,
        height: 100,
        scale,
      },
    });
    const { width, height } = await getImageDetails(data);
    is(width, 100 * ratio * scale, "Image has expected width");
    is(height, 100 * ratio * scale, "Image has expected height");
    is(data, refData, "Image is equal");
  }
});
add_task(async function clipScaleAndDevicePixelRatio({ client }) {
  const { Page } = client;
  const originalRatio = await getDevicePixelRatio();
  const ratio = 2;
  const scale = 1.5;
  const size = 100;
  const expectedSize = size * ratio * scale;
  console.info(`Create reference screenshot: ${expectedSize}x${expectedSize}`);
  await loadURLWithElement({
    width: expectedSize,
    height: expectedSize,
  });
  var { data: refData } = await Page.captureScreenshot({
    clip: {
      x: 0,
      y: 0,
      width: expectedSize,
      height: expectedSize,
      scale: 1,
    },
  });
  await setDevicePixelRatio(originalRatio * ratio);
  await loadURLWithElement({ width: size, height: size });
  var { data } = await Page.captureScreenshot({
    clip: {
      x: 0,
      y: 0,
      width: size,
      height: size,
      scale,
    },
  });
  const { width, height } = await getImageDetails(data);
  is(width, expectedSize * originalRatio, "Image has expected width");
  is(height, expectedSize * originalRatio, "Image has expected height");
  is(data, refData, "Image is equal");
});
add_task(async function clipPosition({ client }) {
  const { Page } = client;
  const ratio = await getDevicePixelRatio();
  await loadURLWithElement();
  var { data: refData } = await Page.captureScreenshot({
    clip: {
      x: 0,
      y: 0,
      width: 100,
      height: 100,
      scale: 1,
    },
  });
  for (const [x, y] of [[10, 20], [20, 10], [20, 20]]) {
    console.info(`Check postion for ${x} and ${y}`);
    await loadURLWithElement({ x, y });
    var { data } = await Page.captureScreenshot({
      clip: {
        x,
        y,
        width: 100,
        height: 100,
        scale: 1,
      },
    });
    const { width, height } = await getImageDetails(data);
    is(width, 100 * ratio, "Image has expected width");
    is(height, 100 * ratio, "Image has expected height");
    is(data, refData, "Image is equal");
  }
});
add_task(async function clipDimension({ client }) {
  const { Page } = client;
  const ratio = await getDevicePixelRatio();
  for (const [width, height] of [[10, 20], [20, 10], [20, 20]]) {
    console.info(`Check width and height for ${width} and ${height}`);
    // Get reference image as section from a larger image
    await loadURLWithElement({ width: 50, height: 50 });
    var { data: refData } = await Page.captureScreenshot({
      clip: {
        x: 0,
        y: 0,
        width,
        height,
        scale: 1,
      },
    });
    await loadURLWithElement({ width, height });
    var { data } = await Page.captureScreenshot({
      clip: {
        x: 0,
        y: 0,
        width,
        height,
        scale: 1,
      },
    });
    const dimension = await getImageDetails(data);
    is(dimension.width, width * ratio, "Image has expected width");
    is(dimension.height, height * ratio, "Image has expected height");
    is(data, refData, "Image is equal");
  }
});
async function loadURLWithElement(options = {}) {
  const { x = 0, y = 0, width = 100, height = 100 } = options;
  const doc = `
    
    
    
  `;
  await loadURL(toDataURL(doc));
}
async function getDevicePixelRatio() {
  return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
    return content.devicePixelRatio;
  });
}
async function setDevicePixelRatio(dppx) {
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [dppx], function(dppx) {
    content.docShell.contentViewer.overrideDPPX = dppx;
    is(content.devicePixelRatio, dppx, "devicePixelRatio override set");
  });
}
async function getImageDetails(image) {
  const mimeType = getMimeType(image);
  return SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [{ mimeType, image }],
    async function({ mimeType, image }) {
      return new Promise(resolve => {
        const img = new content.Image();
        img.addEventListener(
          "load",
          () => {
            resolve({
              mimeType,
              width: img.width,
              height: img.height,
              length: image.length,
            });
          },
          { once: true }
        );
        img.src = `data:${mimeType};base64,${image}`;
      });
    }
  );
}
function getMimeType(image) {
  // Decode from base64 and convert the first 4 bytes to hex
  const raw = atob(image).slice(0, 4);
  let magicBytes = "";
  for (let i = 0; i < raw.length; i++) {
    magicBytes += raw
      .charCodeAt(i)
      .toString(16)
      .toUpperCase();
  }
  switch (magicBytes) {
    case "89504E47":
      return "image/png";
    case "FFD8FFDB":
    case "FFD8FFE0":
      return "image/jpeg";
    default:
      throw new Error("Unknown MIME type");
  }
}