"Tainted canvases may not be loaded" WebGL 纹理的跨域问题

"Tainted canvases may not be loaded" Cross domain issue with WebGL textures

在过去的 48 小时里,我学到了很多关于跨域策略的知识,但显然还不够。

问题开始。我的 HTML5 游戏支持 Facebook 登录。我正在尝试下载人们朋友的个人资料照片。在 HTML5 版本的游戏中,我在 Chrome.

中收到以下错误

detailMessage: "com.google.gwt.core.client.JavaScriptException: (SecurityError) ↵ stack: Error: Failed to execute 'texImage2D' on 'WebGLRenderingContext': Tainted canvases may not be loaded.

据我了解,出现此错误是因为我试图从不同的域加载图像,但这可以通过 Access-Control-Allow-Origin 解决header,详见 this 问题。

我要下载的URL是

https://graph.facebook.com/1387819034852828/picture?width=150&height=150

查看 Chrome 中的网络选项卡,我可以看到它具有所需的 access-control-allow-origin header 并以 302 重定向响应一个新的 URL。 URL 会有所不同,我猜这取决于负载平衡,但这里有一个例子 URL。

https://fbcdn-profile-a.akamaihd.net/hprofile-ak-xap1/v/t1.0-1/c0.0.160.160/p160x160/11046398_1413754142259317_606640341449680402_n.jpg?oh=6738b578bc134ff207679c832ecd5fe5&oe=562F72A4&gda=1445979187_2b0bf0ad3272047d64c7bfc2dbc09a29

这个URL还有access-control-allow-originheader。所以我不明白为什么会失败。

作为 Facebook,以及成千上万的应用程序、游戏和网站显示用户个人资料图片这一事实,我认为这是可能的。我知道我可以通过我自己的服务器跳转,但我不确定为什么我必须这样做。

回答

我最终使用以下代码在 libgdx 中实现了跨域图像加载(这很老套,我相信可以改进)。我还没有设法让它与 AssetDownloader 一起工作。我希望最终能解决这个问题。

public void downloadPixmap(final String url, final DownloadPixmapResponse response) {
    final RootPanel root = RootPanel.get("embed-html");
    final Image img = new Image(url);
    img.getElement().setAttribute("crossOrigin", "anonymous");
    img.addLoadHandler(new LoadHandler() {

        @Override
        public void onLoad(LoadEvent event) {
            HtmlLauncher.application.getPreloader().images.put(url, ImageElement.as(img.getElement()));
            response.downloadComplete(new Pixmap(Gdx.files.internal(url)));
            root.remove(img);
        }
    });
    root.add(img);
}

interface DownloadPixmapResponse {
    void downloadComplete(Pixmap pixmap);

    void downloadFailed(Throwable e);
}

您是否在请求 img 之前设置了 crossOrigin 属性?

var img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://graph.facebook.com/1387819034852828/picture?width=150&height=150"; 

当有人问这个问题时,它正在为我工​​作。不幸的是,上面的 URL 不再指向任何东西,所以我在下面的示例中更改了它

var img = new Image();
img.crossOrigin = "anonymous";   // COMMENT OUT TO SEE IT FAIL
img.onload = uploadTex;
img.src = "https://i.imgur.com/ZKMnXce.png"; 

function uploadTex() {
  var gl = document.createElement("canvas").getContext("webgl");
  var tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  try {
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
    log("DONE: ", gl.getError());
  } catch (e) {
    log("FAILED to use image because of security:", e);
  }
}

function log() {
  var div = document.createElement("div");
  div.innerHTML = Array.prototype.join.call(arguments, " ");
  document.body.appendChild(div);
}
<body></body>

如何检查您是否收到 headers

打开您的开发工具,选择网络选项卡,重新加载页面,select 有问题的图像,同时查看请求 header 和响应 header。

该请求应该显示您的浏览器发送了一个 Origin: header

回复应该显示你收到了

Access-Control-Allow-Methods: GET, OPTIONS, ...
Access-Control-Allow-Origin: *

请注意,响应 和请求 都必须显示以上条目。如果请求丢失 Origin: 那么你没有设置 img.crossOrigin 并且浏览器不会让你使用图像,即使响应说没问题。

如果您的请求有 Origin: header 而响应没有其他 header 则服务器未授予使用该图像显示它的权限。换句话说,它可以在图像标签中工作,你可以将它绘制到 canvas 但你不能在 WebGL 中使用它,你绘制它的任何 2d canvas 都会被污染并且 toDataURLgetImageData 将停止工作

您可以对纹理进行 base64 编码。

这是一个典型的跨域问题,在您进行本地开发时会发生。

我使用 python 的简单服务器作为对此的快速修复。

在终端中导航到您的目录,然后键入:

 $ python -m SimpleHTTPServer

你会得到

Serving HTTP on 0.0.0.0 port 8000 ...

所以转到 0.0.0.0:8000/,您应该会看到问题已解决。