HTML5 File API中的FileReader.readAsText如何工作?

sus*_*haP 11 javascript html5 textarea drag-and-drop fileapi

我编写了以下代码来检查上传的文件是否存在使用HTML5文件API.

<input type="file" id="myfile">
<button type="button" onclick="addDoc()">Add Document</button>
<p id="DisplayText"></p>
Run Code Online (Sandbox Code Playgroud)

以下JavaScript代码已映射到它如下:

function addDoc() {
  var file=document.getElementById("myFile").files[0]; //for input type=file
  var reader=new FileReader();
  reader.onload = function(e) {}
  reader.readAsText(file);
  var error = reader.error;
  var texte=reader.result;
  document.getElementById("DisplayText").innerText=reader.result; /*<p id="DisplayText>*/
}
Run Code Online (Sandbox Code Playgroud)

从本地系统浏览文件后,我尝试在单击之前从文件夹中删除"浏览"文档addDoc().单击按钮后我仍然可以看到Filereader.result不是null并且可以显示所有内容.

有人可以解释Filereader的工作原理吗?一旦浏览文件,FileReader就会被绑定吗?

我们也可以检查系统Readonly Attribute with FileReader是否与Java类似File.canread()

有人可以建议吗?我有IE11来测试代码.

gue*_*314 14

FileReader loadevent以.result异步方式设置值.访问.result使用loadloadend事件.

<input type="file"> Choose FileBrowse...UI 处选择文件时,删除本地文件系统中的文件不应影响通过调用返回的File对象.见2.9.2.可转移对象,6.7.3 DataTransfer接口.FileList.files

4. Blob接口和二进制数据

每个Blob必须具有内部快照状态,如果存在任何此类底层存储,则必须初始设置为底层存储的状态,并且必须通过该状态保留 structured clone.snapshot state可以找到Files的进一步规范性定义.

2.9.8 Blob和FileList对象的Monkey补丁

这个猴子补丁将在适当的时候删除.请参阅w3c/FileAPI问题32.

Blob对象是cloneable objects.

  1. 给定targetRealm并忽略内存的每个Blob对象的[[ Clone]]内部方法必须运行以下步骤:

  2. 如果是这样closed,那么扔一个."DataCloneError" DOMException

返回的新实例targetRealm,对应于同样的基础数据.

FileList对象是可克隆的对象.

给定targetRealm内存的每个FileList对象的[[Clone]]内部方法 必须运行以下步骤:

  1. output成为targetRealm中的新FileList对象.

  2. For each file in this, add ? [StructuredClone][15](_file, targetRealm, memory_) to the end of the list of File objects of output.

Return output.


Selecting read-only files or folders at webkit and firefox browsers

At chrome, chromium if read-only permission is set for file at local filesystem and user selects file at <input type="file"> element, where FileReader is used to read file, an error is thrown at FileReader, generated from FileReader progress event.

If a Blob URL is set to the same file object, the blob: URL will not return the the read-only file at request to the Blob URL.

Selection of folder where folder permission is set to read-only

Chrome, chromium

在铬,其中铬webkitdirectory属性被设置和文件夹被选择具有只读许可FileList .lengthevent.target.files返回0; event.target.files.webkitGetAsEntry()没有被调用,"No file chosen"被渲染<input type="file"> shadowDOM.删除文件夹<input type="file">droppable设置属性的元素时,将显示目录.name.path只读文件夹drop event.dataTransfer.

当用户在<textarea>元素上删除文件或文件夹时,没有drop附加beforeunload事件的事件被调用,并且UI处显示提示符

Do you want to leave this site?
Changes you made may not be saved.
<Stay><Leave> // <buttons>
Run Code Online (Sandbox Code Playgroud)

火狐

在firefox版本47.0b9上,allowdirs属性设置在<input type="file">元素,用户点击"Choose folder.." <input>,文件夹.name.path父文件夹可以.then()链接到event.target.getFilesAndDirectories().递归迭代Directory条目时,不返回所选文件夹中包含的文件或文件夹; 返回一个空字符串.

如果用户单击"Choose file..." <input>并且选择了没有设置只读权限的文件夹,则单击文件管理器中的文件夹时,将列出该文件夹中的文件.

如果选择了设置了只读权限的文件夹,则会alert()在UI显示时呈现通知

  Could not read the contents of <directory name>
  Permission denied
Run Code Online (Sandbox Code Playgroud)

错误,安全问题

*nix OS

When user drops folder at <textarea> element, where no drop event is attached, the full path to the folder at user filesystem file: protocol is exposed. The paths to the files contained within the folder are not also set as .value; e.g.,

"file:///home/user/Documents/Document/"
Run Code Online (Sandbox Code Playgroud)

When a file is dropped at <textarea> element, where not drop event is attached, the full path to the file at user filesystem is set as .value of <textarea>; that is,

"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue.txt"
Run Code Online (Sandbox Code Playgroud)

If multiple files are selected and dropped at <textarea> element, all of the full file paths are set as .value of <textarea>, delineated by new line character \n

"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue1.txt"
"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue2.txt"
..
Run Code Online (Sandbox Code Playgroud)

Where an XMLHttpRequest() is made for the file path and error is logged at console

NS_ERROR_DOM_BAD_URI: Access to restricted URI denied
Run Code Online (Sandbox Code Playgroud)

When set as .src of an <img> element with .crossOrigin set to "anonymous" the img error event handler is called

At call to window.open() with full path set at first parameter

Error: Access to '"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue.png"' from script denied
Run Code Online (Sandbox Code Playgroud)

Specification

4.10.5.1.18. File Upload state (type=file)

EXAMPLE 16

For historical reasons, the value IDL attribute prefixes the file name with the string "C:\fakepath\". Some legacy user agents actually included the full path (which was a security vulnerability). As a result of this, obtaining the file name from the value IDL attribute in a backwards-compatible way is non-trivial.

4.10.5.4. Common <input> element APIs

filename

On getting, it must return the string "C:\fakepath\" followed by the name of the first file in the list of selected files, if any, or the empty string if the list is empty. On setting, if the new value is the empty string, it must empty the list of selected files; otherwise, it must throw an "InvalidStateError" DOMException.

NOTE: This "fakepath" requirement is a sad accident of history. See the example in the File Upload state section for more information.

NOTE: Since path components are not permitted in file names in the list of selected files, the "\fakepath\" cannot be mistaken for a path component.

4.10.5.1.18. File Upload state (type=file)

Path components

When an <input> element’s type attribute is in the File Upload state, the rules in this section apply.

The <input> element represents a list of selected files, each file consisting of a file name, a file type, and a file body (the contents of the file).

File names must not contain path components, even in the case that a user has selected an entire directory hierarchy or multiple files with the same name from different directories. Path components, for the purposes of the File Upload state, are those parts of file names that are separated by U+005C REVERSE SOLIDUS character () characters.

Bug report https://bugzilla.mozilla.org/show_bug.cgi?id=1311823


Dropping file at <textarea> at data URI

Following comment by Neal Deakin at bug report

I think the steps referred to are:

  1. Open data:text/html,
  2. Drag a file from the desktop to the textarea

I can reproduce this on Linux, but not on Windows or Mac.

The hunch above is correct; Linux is including the data as a url and plaintext as well.

dropped files at data: prototcol data URI at firefox, and chrome, chromium

data:text/html,<textarea></textarea>
Run Code Online (Sandbox Code Playgroud)

Firefox

The full path name of file or folder set as .value of <textarea>.

Chrome, chromium

Dropping file at data URI having only textarea element at chrome, chromium replaces the data URI with dropped file path at address bar, and loads the dropped file at the same tab, replacing the data URI with the content of the dropped file.

plnkr http://plnkr.co/edit/ZfAGEAiyLLq8rGXD2ShE?p=preview


html, javascript to reproduce issue described above

<!DOCTYPE html>
<html>

<head>
  <style>
    body {
      height: 400px;
    }

    textarea {
      width: 95%;
      height: inherit;
    }
  </style>

  <script>
    window.onload = function() {
      var button = document.querySelector("#myfile + button");
      var input = document.getElementById("myfile");
      var display = document.getElementById("DisplayText");
      var text = null;

      function readFullPathToFileOnUserFileSystem(e) {
        var path = e.target.value;
        console.log(path);
        var w = window.open(path, "_blank");
        var img = new Image;
        img.crossOrigin = "anonymous";
        img.onload = function() {
          document.body.appendChild(this);
        }
        img.onerror = function(err) {
          console.log("img error", err.message)
        }
        img.src = path;
        var request = new XMLHttpRequest();
        request.open("GET", path.trim(), true);
        request.onload = function() {
          console.log(this.responseText)
        }
        request.error = function(err) {
          console.log(err.message)
        }
        request.send();

      }

      display.addEventListener("input", readFullPathToFileOnUserFileSystem);
      input.addEventListener("change", addDoc);
      input.addEventListener("progress", function(event) {
        console.log("progress", event)
      });
      button.addEventListener("click", handleText)

      function addDoc(event) {
        var mozResult = [];

        function mozReadDirectories(entries, path) {
          console.log("dir", entries, path);
          return [].reduce.call(entries, function(promise, entry) {
              return promise.then(function() {
                console.log("entry", entry);
                return Promise.resolve(entry.getFilesAndDirectories() || entry)
                  .then(function(dir) {
                    console.log("dir getFilesAndDirectories", dir)
                    return dir
                  })
              })
            }, Promise.resolve())
            .catch(function(err) {
              console.log(err, err.message)
            })
            .then(function(items) {
              console.log("items", items);
              var dir = items.filter(function(folder) {
                return folder instanceof Directory
              });
              var files = items.filter(function(file) {
                return file instanceof File
              });
              if (files.length) {
                console.log("files:", files, path);
                mozResult = mozResult.concat.apply(mozResult, files);

              }
              if (dir.length) {
                console.log(dir, dir[0] instanceof Directory, dir[0]);
                return mozReadDirectories(dir, dir[0].path || path);

              } else {
                if (!dir.length) {

                  return Promise.resolve(mozResult).then(function(complete) {
                    return complete
                  })

                }
              }

            })
            .catch(function(err) {
              console.log(err)
            })

        };

        console.log("files", event.target.files);
        if ("getFilesAndDirectories" in event.target) {
          return (event.type === "drop" ? event.dataTransfer : event.target)
          .getFilesAndDirectories()
            .then(function(dir) {
              if (dir[0] instanceof Directory) {
                console.log(dir)
                return mozReadDirectories(dir, dir[0].path || path)
                  .then(function(complete) {
                    console.log("complete:", complete);
                    event.target.value = null;
                  });
              } else {
                if (dir[0] instanceof File && dir[0].size > 0) {
                  return Promise.resolve(dir)
                    .then(function(complete) {
                      console.log("complete:", complete);
                    })
                } else {
                  if (dir[0].size == 0) {
                    throw new Error("could not process '" + dir[0].name + "' directory" + " at drop event at firefox, upload folders at 'Choose folder...' input");
                  }
                }
              }
            }).catch(function(err) {
              console.log(err)
            })
        }

        var reader = new FileReader();
        reader.onload = function(e) {
          text = reader.result;
          console.log("FileReader.result", text);
          button.removeAttribute("disabled");
        }

        reader.onerror = function(err) {
          console.log(err, err.loaded, err.loaded === 0, file);
          button.removeAttribute("disabled");
        }

        reader.onprogress = function(e) {
          console.log(e, e.lengthComputable, e.loaded, e.total);
        }

        reader.readAsArrayBuffer(file);

      }

      function handleText() {

        // do stuff with `text`: `reader.result` from `addDoc`
        display.textContent = text;
        button.setAttribute("disabled", "disabled");
        // set `text` to `null` if not needed or referenced again
        text = null;
      }
    }
  </script>
</head>

<body>
  <input type="file" id="myfile" webkitdirectory directory allowdirs>
  <button type="button" disabled>Add Document</button>
  <br>
  <br>
  <textarea id="DisplayText"></textarea>
</body>

</html>
Run Code Online (Sandbox Code Playgroud)

plnkr http://plnkr.co/edit/8Ovw3IlYKI8BYsLhzV88?p=preview


You can use change event attached to #myfile element to handle file selection action by user.

Substitute <textarea> element for <p> element to display result of load event from .readAsText() call.

To display .result of FileReader at click at button element, set variable text to reader.result within load event of FileReader at click event at button set .textContent of #DisplayText element to variable referencing previously set reader.result.

<!DOCTYPE html>
<html>
  <style>
    body {
      height: 400px;
    }
    textarea {
      width:95%;
      height: inherit;
    }
  </style>
<head>
  <script>
    window.onload = function() {
        var button = document.querySelector("#myfile + button");
        var input = document.getElementById("myfile");
        var display = document.getElementById("DisplayText");
        var text = null;
        input.addEventListener("change", addDoc);
        button.addEventListener("click", handleText)

        function addDoc(event) {
          var file = this.files[0]
          var reader = new FileReader();      
          reader.onload = function(e) {
            text = reader.result;
            button.removeAttribute("disabled");
          }

          reader.onerror = function(err) {
            console.log(err, err.loaded
                        , err.loaded === 0
                        , file);
            button.removeAttribute("disabled");
          }

          reader.readAsText(event.target.files[0]);
        }

        function handleText() {
          
          // do stuff with `text`: `reader.result` from `addDoc`
          display.textContent = text;
          button.setAttribute("disabled", "disabled");
          // set `text` to `null` if not needed or referenced again
          text = null; 
        }
    }
  </script>
</head>

<body>
  <input type="file" id="myfile" accept="text/*">
  <button type="button" disabled>Add Document</button><br><br>
  <textarea id="DisplayText"></textarea>
</body>

</html>
Run Code Online (Sandbox Code Playgroud)