Firefox 4 – FormData 和新的 File.url 对象

这是一篇来自 Jonas Sicking 的客座文章,他在 Gecko 中负责面向内容的功能开发。他介绍了我们之前讨论过的 FormData,并展示了它如何与我们在 Firefox 4 中为 File API 添加的重要部分 File.url 相连。

在 Firefox 4 中,我们继续添加对更轻松、更强大的文件处理的支持。在 Firefox 4 Beta 1 中可用的两个功能是 File.urlFormData。在这篇文章中,我将简要介绍这两个功能。

从 Firefox 3.6 开始,我们支持使用 FileReader 对象来读取文件的标准化方法。这个对象允许你将文件的内容读入内存,以分析其内容或将内容显示给用户。例如,要将图像的预览显示给用户,你可以使用以下脚本

var reader = new FileReader();
reader.onload = function() {
  previewImage.src = reader.result;
}
reader.readAsDataURL(myFile);

这里有两个需要注意的地方。首先,reader.result 是一个包含整个文件内容的数据 URL。也就是说,整个文件内容都保存在内存中。不仅如此,数据 URL 通常是 Base64 编码的,每个 Base64 编码的字符都存储在 JavaScript 字符中,通常使用 2 字节的内存。结果是,如果上面的代码用于读取一个 10MB 的图像文件,reader.result 将是一个 26.7MB 的字符串。

另一个不幸的是,上面的代码有点复杂,因为它需要使用异步事件从磁盘读取数据。

在 Firefox 4 Beta 1 中,你可以使用以下代码

previewImage.src = myFile.url;

这使用的是 File API 规范中定义的 File.url 属性。该属性返回一个短 URL,大约 40 个字符。你通常不需要关心这个 URL 的内容,但对于感兴趣的人来说,它包含一个由特殊方案前缀的随机生成的标识符。

这个 URL 可以用在任何使用通用 URL 的地方,从该 URL 读取直接读取文件。上面的示例使图像元素直接从文件中读取并向用户显示生成的图像。加载方式与从 HTTP URL 加载完全相同,正常的“加载”事件和“错误”事件将根据需要触发。

你也可以通过使用一个 <iframe> 并将其 src 设置为 File.url 返回的值来显示 HTML 文件。但是,你必须注意 HTML 文件中的相对 URL 不会生效,因为相对 URL 是相对于 File.url 返回的生成的 URL 解析的。这是有意为之的,因为用户可能只授予了对 HTML 文件的访问权限,而没有授予对图像文件的访问权限。

这个 URL 在其他地方也很有用,例如 CSS 背景图像,可以将元素的背景设置为使用本地文件。或者,如果你有使用 XMLHttpRequest 的现有代码,并且不想将其转换为使用 FileReader,则可以使用 XMLHttpRequest 从该 URL 读取。

我们还在 Firefox 4 Beta 1 中支持的另一个功能是 FormData 对象。如果你有使用 multipart/form-data 编码接收文件的现有服务器基础设施,这个对象就很有用。

在 Firefox 3.6 中,使用 XMLHttpRequest 将文件发送到使用 multipart/form-data 编码的服务器需要一些手工操作。你必须使用 FileReader 将文件的内容读入内存,然后手动进行 multipart/form-data 编码,最后将其发送到服务器。这不仅需要更多代码,还需要将整个文件内容读入内存。

在 Firefox 4 中,你可以使用 XMLHttpRequest Level 2 规范中的 FormData 对象。这允许使用以下简洁的代码

var fd = new FormData();
fd.append("fileField", myFile);
var xhr = new XMLHttpRequest();
xhr.open("POST", "file_handler.php");
xhr.send(fd);

这将自动对文件进行 multipart/form-data 编码并将其发送到服务器。文件的内容将以小块的形式读取,因此不会占用大量内存。它将发送与以下标记的表单相同的内容

如果你想发送多个文件,只需对要提交的每个文件调用 fd.append,所有文件都将通过单个请求发送。当然,你仍然可以使用 XMLHttpRequest 一直提供的正常进度事件,包括上传和下载进度事件。

但是,FormData 还有另一个不错的功能。你还可以发送正常的非文件 multipart/form-data 值。例如

var fd = new FormData();
fd.append("author", "Jonas Sicking");
fd.append("name", "New File APIs");
fd.append("attachment1", file1);
fd.append("attachment2", file2);
var xhr = new XMLHttpRequest();
xhr.open("POST", "file_handler.php");
xhr.send(fd);

你甚至可以获得一个包含 <form> 中所有信息的 FormData 对象。(但是请注意,这种语法在最终发布之前可能会发生变化)

var fd = myFormElement.getFormData();
var xhr = new XMLHttpRequest();
xhr.open("POST", "file_handler.php");
xhr.send(fd);

这里,fd 将包含表单中所有表单字段的数据,包括单选按钮和文件字段。

像往常一样,我们乐于听取你对这些功能的反馈。请告诉我们你的想法,尤其是如果你已经测试过它们,并且它们似乎没有按照你的预期工作。你可以使用 http://www.mozilla.com/en-US/firefox/beta/feedback/ 向我们提供反馈,或者使用右上角的反馈按钮(见下图)。


20 条评论

  1. David

    尝试使用 Firefox 4 Beta 1、jQuery 和 File.url 规范设置图像的源。File.url 属性返回一个字符串(moz-filedata:xxxxx…),但设置为图像的 src 后,什么都没有返回。

    有什么想法吗?谢谢。

    2010 年 7 月 8 日 下午 9:56

    1. Jonas Sicking

      我很想看到一个测试用例。需要注意的是,我们对同源检查非常严格。因此,域 A 上的一个页面无法使用域 B 上的一个页面生成的 File.url 值。

      你是否偶然使用 document.domain 或 postMessage 在不同域的页面之间传输 fileurl?

      如果上述情况不是问题,请在 bugzilla 中提交一个 bug,或者向我发送一个测试用例(通过谷歌或其他搜索很容易找到我的邮箱地址)。

      2010 年 7 月 12 日 上午 2:00

    2. Paul Rouget

      可能是这个 bug:https://bugzilla.mozilla.org/show_bug.cgi?id=572139

      2010 年 7 月 15 日 下午 9:28

  2. srigi

    你能发布一些关于

    “读取文件的内容到内存中,然后手动进行 multipart/form-data 编码,最后将其发送到服务器”

    的信息吗?我想为 FF v3.6 编写一些 Ajax 文件上传的解决方法。由于 v3.6 不支持 FormData,我需要实现这种“hack”。

    2010 年 7 月 13 日 上午 9:18

  3. Anshuman

    你能提供一下 FF3.6 中“手动 multipart/form-data 编码”的信息吗?我正在使用这段代码,但它不起作用。

    var data = event.dataTransfer; var boundary = '------multipartformboundary' + (new Date).getTime(); var dashdash = '--'; var crlf = 'rn'; /* 构建 RFC2388 字符串。*/ var builder = ''; builder += dashdash; builder += boundary; builder += crlf; var xhr = new XMLHttpRequest(); /* 对于每个拖放的文件。*/ for (var i = 0; i < data.files.length; i++) { var file = data.files[i]; /* 生成头信息。*/ builder += 'Content-Disposition: form-data; name="user_file[]"'; if (file.fileName) { builder += '; filename="' + file.fileName + '"'; } builder += crlf; builder += 'Content-Type: application/octet-stream'; builder += crlf; builder += crlf; /* 附加二进制数据。*/ builder += file.getAsBinary(); builder += crlf; /* 编写边界。*/ builder += dashdash; builder += boundary; builder += crlf; } /* 标记请求的结尾。*/ builder += dashdash; builder += boundary; builder += dashdash; builder += crlf; try{ xhr.open("POST", "fu.php", true); xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary); xhr.sendAsBinary(builder); return "Uploaded"; } catch(e) { return e; }

    2010 年 7 月 15 日 上午 12:28

  4. tobiistgut

    File.url 提供的 URL 将有效多久?

    2010 年 7 月 16 日 下午 3:20

    1. Jonas Sicking

      它将在用户离开页面之前一直有效。

      2010 年 7 月 24 日 下午 9:14

  5. Firenze

    使用这种 XMLHttpRequest Level 2 表单方法将打开通往地狱的大门,
    恶意软件编写者将拥有一个前所未有的游乐场。

    你为什么在编写这种垃圾代码之前不先思考一下?

    2010 年 7 月 25 日 下午 6:17

    1. Paul Rouget

      需要更多细节。你指的是什么?

      2010 年 7 月 26 日 上午 1:41

  6. Werner Punz

    不错的东西,但到目前为止,FormData 对象始终强制使用 multipart xhr 提交,将 FormData 对象用于正常的非 multipart 提交情况不是更好吗?如果将 FormData 对象公开给所有可能的提交情况,而不是仅仅提供一个 multipart 提交的辅助工具,会更容易很多。

    2010 年 8 月 1 日 上午 8:29

  7. Werner Punz

    好吧,我尝试了一下,不幸的是(Firefox 4 Beta2)它仍然有 bug,虽然它将表单数据作为 multipart 请求发送,但它缺少大多数 multipart 解码器依赖的一些定义。发送操作会生成 multipart 请求,但没有分隔符定义,也没有长度定义。
    由于无法访问生成的提交内容的长度,而且分隔符也是在发送时生成的,因此手动设置这两个选项也是不可能的。

    这对我来说似乎是一个 bug,你知道在哪里可以报告它吗?

    2010 年 8 月 1 日 下午 1:03

  8. Kensaku KOMATSU

    感谢您支持 FormData!我理解了它的意义:)

    然而,在使用 FormData 方法将文件上传到跨域服务器的情况下,我发现 Firefox4 beta 版(我尝试了 beta3 和 4)发生了预检请求。在 CORS 规范中,如果方法为“POST”并且内容类型为“multipart/form-data”,则不应发生预检请求。因此,我认为这种行为不符合 CORS 规范。

    附注:Chrome 6 beta 版也支持 FormData 功能,但预检请求不会生效。

    2010 年 8 月 24 日 下午 11:39

  9. mawrya

    问题:如果我想通过 JavaScript 自动将一个可访问的网页文件添加到表单中,该怎么办?这个例子和其他类似例子只涵盖用户选择的文件 - 可能是通过表单控件或拖放操作。对于用户电脑上的文件来说,这是有意义的,因为存在安全问题。如果我想添加已经存在且仅可通过网页地址访问的文件,该怎么办?无法通过拖放界面或文件选择器界面来完成。我希望使用一个脚本获取 http://localhost/blah.xml 中的文件,并将它与表单中其他用户选择的文件一起包含。我想使用 FormData.append() 方法将文件添加到表单中,因为它处理了所有混乱的 multipart/form-data 编码,但似乎我需要先将 blah.xml 文件转换成一个 FileAPI 文件对象。似乎没有办法做到这一点。我是否被迫编写 JavaScript 代码手动读取和 multipart/form-data 编码文件,然后将其以某种方式附加到表单中?

    感谢您精彩的文章!

    2010 年 8 月 29 日 下午 8:49

  10. Shiv Kumar

    在您的示例中
    var fd = new FormData();
    fd.append(“fileField”, myFile);
    var xhr = new XMLHttpRequest();
    xhr.open(“POST”, “file_handler.php”);
    xhr.send(fd);

    myFile 是什么?
    我尝试了 document.getElementById(“someformelement”);

    其中 someformelement 是一个

    但它没有将文件发送到服务器。如果附加到 FormData 中,它会发送额外的表单字段。

    使用下面的例子发送整个表单,可以按预期工作。

    2010 年 9 月 19 日 下午 10:08

  11. Shiv Kumar

    回答我自己的问题……

    如果您有一个 ID 为 fileToUpload 的表单字段,并且此字段是一个类型为文件的输入元素,那么

    var myFile = document.getElementById(‘fileToUpload’).Files[0];

    2010 年 9 月 22 日 下午 11:09

  12. Michel

    谁能帮助我正确地在 Fx 3.6 中执行预检 CORS POST 请求?
    http://stackoverflow.com/questions/4034223/correct-way-to-do-cors-preflight-post-using-jquery
    相同的代码是否适用于 Fx4?

    2010 年 10 月 27 日 上午 9:17

  13. Sol

    File.url 究竟是如何工作的?我尝试获取它时得到 undefined。

    2010 年 11 月 22 日 上午 1:02

  14. Radek Tetík

    FormData 是一个很棒的 API。我刚刚实现了它,它在 FF4b11 和 Chrome 10 中运行良好。遗憾的是,它在 IE9 中无法运行。

    2011 年 2 月 15 日 上午 6:04

  15. CopyKat

    感谢您对 FormData 的解释,我一直对此感到困惑。您的解释清晰易懂,帮助了我这个不太懂技术的人。

    2012 年 3 月 16 日 上午 4:02

  16. Stephanie Manley

    这些信息非常重要。
    非常感谢您。

    2012 年 3 月 28 日 上午 3:56

本文评论已关闭。