910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Entry point for the Image Loader's service worker.
 */

import {assert} from 'chrome://resources/js/assert.js';

import type {LoadImageRequest, LoadImageResponse} from './load_image_request.js';
import type {PrivateApi} from './sw_od_messages.js';

const EXTENSION_ID = 'pmfjbimdmchhbnneeidfognadeopoehp';

const ALLOW_LISTED_FILE_MANAGER_SWA = 'chrome://file-manager';
const ALLOW_LISTED_NATIVE = 'com.google.ash_thumbnail_loader';

// setupOffscreenDocument is based on
// https://developer.chrome.com/docs/extensions/reference/api/offscreen#maintain_the_lifecycle_of_an_offscreen_document
let creatingOffscreenDocument: Promise<void>|undefined;
async function setupOffscreenDocument() {
  const url = chrome.runtime.getURL('offscreen.html');
  const existingContexts = await chrome.runtime.getContexts({
    contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT],
    documentUrls: [url],
  });
  if (existingContexts.length > 0) {
    return;
  }

  if (creatingOffscreenDocument) {
    await creatingOffscreenDocument;
    return;
  }

  creatingOffscreenDocument = chrome.offscreen.createDocument({
    url: url,
    // 'USER_MEDIA' isn't quite right, since we never call getUserMedia, but
    // it's the closest listed reason from
    // https://developer.chrome.com/docs/extensions/reference/api/offscreen#type-Reason
    reasons: [chrome.offscreen.Reason.USER_MEDIA],
    justification: 'Lets us use <img>, <canvas> and XMLHttpRequest',
  });
  await creatingOffscreenDocument;
  creatingOffscreenDocument = undefined;
}

// Forwards a message from the service worker to the offscreen document.
//
// sendResponse will not be called if msg.cancel is truthy. See the
// ImageLoader.handle method.
async function sendToOffscreenDocument(
    msg: LoadImageRequest, senderOrigin: string,
    sendResponse: (r: LoadImageResponse) => void) {
  assert(msg);
  try {
    await setupOffscreenDocument();
  } catch (e) {
    console.error('setupOffscreenDocument:', e);
    return;
  }
  msg.imageLoaderRequestId = senderOrigin + ':' + msg.taskId;
  chrome.runtime.sendMessage(
      null, msg, undefined, (response: LoadImageResponse) => {
        sendResponse(response);
      });
}

// Compare the hard-coded-at-compile-or-package-time script version with the
// loaded-at-runtime manifest version. Chrome has a service worker script
// cache, which is usually flushed whenever a Chrome extension is upgraded to a
// new version. But the ChromeOS Files App (and its image loader Chrome
// extension) is unusual (for a Chrome extension) in that it's built in to the
// OS image and does not go through an extension's typical "uninstall the old;
// install the new" mechanisms. Even when the OS image ships a newer version,
// the service worker script cache will serve the older, stale version. This
// conditional chrome.runtime.reload() call works around that.
//
// https://groups.google.com/a/google.com/g/chromeos-files-syd/c/dBhXhPGGkg0/m/pw3Gv3TLAAAJ
//
// When updating this '0.5' string, keep it synchronized with what's in
// manifest.json near the §.
if (chrome.runtime.getManifest().version !== '0.5') {
  chrome.runtime.reload();

} else {
  // Handle imageLoaderPrivate calls on behalf of the offscreen document.
  chrome.runtime.onMessage.addListener(
      (msg: PrivateApi, sender: chrome.runtime.MessageSender,
       sendResponse: (thumbnailDataUrl: string) => void) => {
        if (sender.id !== EXTENSION_ID) {
          return false;
        } else if (msg.apiMethod === 'getArcDocumentsProviderThumbnail') {
          chrome.imageLoaderPrivate.getArcDocumentsProviderThumbnail(
              msg.params.url,
              msg.params.widthHint,
              msg.params.heightHint,
              sendResponse,
          );
          return true;
        } else if (msg.apiMethod === 'getDriveThumbnail') {
          chrome.imageLoaderPrivate.getDriveThumbnail(
              msg.params.url,
              msg.params.cropToSquare,
              sendResponse,
          );
          return true;
        } else if (msg.apiMethod === 'getPdfThumbnail') {
          chrome.imageLoaderPrivate.getPdfThumbnail(
              msg.params.url,
              msg.params.width,
              msg.params.height,
              sendResponse,
          );
          return true;
        }
        return false;
      });

  // Listen to messages from the Files app SWA.
  chrome.runtime.onMessageExternal.addListener(
      (msg: LoadImageRequest, sender: chrome.runtime.MessageSender,
       sendResponse: (r: LoadImageResponse) => void) => {
        if (!msg || (sender.origin !== ALLOW_LISTED_FILE_MANAGER_SWA)) {
          return false;
        }
        sendToOffscreenDocument(
            msg, ALLOW_LISTED_FILE_MANAGER_SWA, sendResponse);
        // Return true (or false) if the runtime should keep sendResponse
        // valid-to-call (or invalid) after this closure returns.
        //
        // If msg.cancel is truthy then sendToOffscreenDocument will not call
        // sendResponse, so we can return false.
        //
        // If msg.cancel is falsy then sendToOffscreenDocument will call it,
        // but also asynchronously, so return true.
        return !msg.cancel;
      });

  // Listen to messages from ash-chrome itself, e.g. thumbnails in the "tote"
  // (also known as the "holding space"), after taking a screenshot.
  //
  // Each native connection is expected to send a single request only, so we
  // disconnect after handling a message.
  chrome.runtime.onConnectNative.addListener((port: chrome.runtime.Port) => {
    assert(port.sender);
    if (port.sender.nativeApplication !== ALLOW_LISTED_NATIVE) {
      port.disconnect();
      return;
    }

    port.onMessage.addListener((msg: LoadImageRequest) => {
      if (!msg) {
        port.disconnect();
        return;
      } else if (msg.cancel) {
        port.disconnect();
      }
      sendToOffscreenDocument(
          msg, ALLOW_LISTED_NATIVE, (response: LoadImageResponse) => {
            assert(!msg.cancel);
            port.postMessage(response);
            port.disconnect();
          });
    });
  });
}