You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用toUri()在iframe中加载Google Picker API时回调不触发

问题原因

当使用picker.toUri()将Google Picker嵌入iframe时,Picker的回调机制依赖跨窗口消息通信(postMessage),而非模态框版本的同上下文直接调用。默认情况下,iframe内的Picker页面无法直接触发父页面的pickerCallback函数,必须手动配置消息接收逻辑并指定消息发送的目标域名。

解决方案

需要完成两个关键修改:

  • 给Picker实例设置父页面的源域名,确保消息能正确发送到父页面
  • 在父页面监听message事件,接收Picker的回调数据并转发到原回调函数

修改后的完整代码

<!DOCTYPE html>
<html>

<head>
  <title>Picker API Quickstart</title>
  <meta charset="utf-8" />
</head>

<body>
  <p>Picker API API Quickstart</p>

  <!--Add buttons to initiate auth sequence and sign out-->
  <button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
  <button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>

  <pre id="content" style="white-space: pre-wrap;"></pre>
  <iframe id="pickerFrame" style="display: none;width:500px;height:500px"></iframe>
  <script type="text/javascript">
    /* exported gapiLoaded */
    /* exported gisLoaded */
    /* exported handleAuthClick */
    /* exported handleSignoutClick */

    // Authorization scopes required by the API; multiple scopes can be
    // included, separated by spaces.
    const SCOPES = 'https://www.googleapis.com/auth/drive.file';

    // TODO(developer): Set to client ID and API key from the Developer Console
    const CLIENT_ID = '******';
    const API_KEY = '******';
    // TODO(developer): Replace with your own project number from console.developers.google.com.
    const APP_ID = '******';
    // 设置父页面的源(当前页面域名,比如http://localhost:8080)
    const PARENT_ORIGIN = window.location.origin;

    let tokenClient;
    let accessToken = null;
    let pickerInited = false;
    let gisInited = false;


    document.getElementById('authorize_button').style.visibility = 'hidden';
    document.getElementById('signout_button').style.visibility = 'hidden';

    // 监听message事件,接收Picker的回调数据
    window.addEventListener('message', (event) => {
      // 验证消息来源,防止恶意消息
      if (event.origin.startsWith('https://docs.google.com')) {
        pickerCallback(event.data);
      }
    });

    /**
     * Callback after api.js is loaded.
     */
    function gapiLoaded() {
      gapi.load('client:picker', initializePicker);
    }

    /**
     * Callback after the API client is loaded. Loads the
     * discovery doc to initialize the API.
     */
    async function initializePicker() {
      await gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest');
      pickerInited = true;
      maybeEnableButtons();
    }

    /**
     * Callback after Google Identity Services are loaded.
     */
    function gisLoaded() {
      tokenClient = google.accounts.oauth2.initTokenClient({
        client_id: CLIENT_ID,
        scope: SCOPES,
        callback: '', // defined later
      });
      gisInited = true;
      maybeEnableButtons();
    }

    /**
     * Enables user interaction after all libraries are loaded.
     */
    function maybeEnableButtons() {
      if (pickerInited && gisInited) {
        document.getElementById('authorize_button').style.visibility = 'visible';
      }
    }

    /**
     *  Sign in the user upon button click.
     */
    function handleAuthClick() {
      tokenClient.callback = async (response) => {
        if (response.error !== undefined) {
          throw (response);
        }
        accessToken = response.access_token;
        document.getElementById('signout_button').style.visibility = 'visible';
        document.getElementById('authorize_button').innerText = 'Refresh';
        await createPicker();
      };

      if (accessToken === null) {
        // Prompt the user to select a Google Account and ask for consent to share their data
        // when establishing a new session.
        tokenClient.requestAccessToken({ prompt: 'consent' });
      } else {
        // Skip display of account chooser and consent dialog for an existing session.
        tokenClient.requestAccessToken({ prompt: '' });
      }
    }

    /**
     *  Sign out the user upon button click.
     */
    function handleSignoutClick() {
      if (accessToken) {
        accessToken = null;
        google.accounts.oauth2.revoke(accessToken);
        document.getElementById('content').innerText = '';
        document.getElementById('authorize_button').innerText = 'Authorize';
        document.getElementById('signout_button').style.visibility = 'hidden';
      }
    }

    /**
     *  Create and render a Picker object for searching images.
     */
    function createPicker() {
      const view = new google.picker.View(google.picker.ViewId.DOCS);
      view.setMimeTypes('image/png,image/jpeg,image/jpg');
      const picker = new google.picker.PickerBuilder()
        .enableFeature(google.picker.Feature.NAV_HIDDEN)
        .setAppId(APP_ID)
        .setDeveloperKey(API_KEY)
        .setOAuthToken(accessToken)
        .addView(view)
        .setCallback(pickerCallback)
        // 设置父页面的源,让Picker知道要把消息发送到哪里
        .setOrigin(PARENT_ORIGIN);
      picker.build();

      document.getElementById('pickerFrame').src = picker.toUri();
      document.getElementById('pickerFrame').style.display = 'block';
      //picker.setVisible(true);
    }

    /**
     * Displays the file details of the user's selection.
     * @param {object} data - Containers the user selection from the picker
     */
    async function pickerCallback(data) {
      if (data.action === google.picker.Action.PICKED) {
        let text = `Picker response: \n${JSON.stringify(data, null, 2)}\n`;
        const document = data[google.picker.Response.DOCUMENTS][0];
        const fileId = document[google.picker.Document.ID];
        console.log(fileId);
        const res = await gapi.client.drive.files.get({
          'fileId': fileId,
          'fields': '*',
        });
        text += `Drive API response for first document: \n${JSON.stringify(res.result, null, 2)}\n`;
        window.document.getElementById('content').innerText = text;
      }
    }
  </script>
  <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
  <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
</body>

</html>

关键修改点说明

  1. 添加PARENT_ORIGIN常量:获取当前页面的域名(window.location.origin),确保Picker能将消息发送到正确的父页面。
  2. 监听message事件:在父页面接收来自Picker iframe的消息,验证来源为Google Docs域名后,调用原pickerCallback函数处理数据。
  3. PickerBuilder添加setOrigin:明确告诉Picker要把回调消息发送到哪个域名,这是跨窗口通信的必要配置。

注意事项

  • 确保你的页面使用HTTPS(本地开发localhost除外),Google Picker的postMessage在非安全域名下可能受限。
  • 验证消息来源(event.origin.startsWith('https://docs.google.com'))是安全最佳实践,避免接收恶意网站的消息。

内容的提问来源于stack exchange,提问作者BiggeekTX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 21:27:10