File System Access API: דרך פשוטה יותר לגשת לקבצים מקומיים

File System Access API מאפשר לאפליקציות אינטרנט לקרוא או לשמור שינויים ישירות בקבצים ובתיקיות במכשיר של המשתמש.

מהו File System Access API?

File System Access API מאפשר למפתחים ליצור אפליקציות אינטרנט מתקדמות שמקיימות אינטראקציה עם קבצים במכשיר המקומי של המשתמש, כמו סביבת פיתוח משולבת (IDE), כלי עריכת תמונות וסרטונים, כלי עריכת טקסט ועוד. אחרי שמשתמש מעניק לאפליקציית אינטרנט גישה, ה-API הזה מאפשר לו לקרוא או לשמור שינויים ישירות בקבצים ובתיקיות במכשיר של המשתמש. בנוסף לקריאה וכתיבה של קבצים, File System Access API מאפשר לפתוח ספרייה ולספור את התוכן שלה.

אם עבדתם בעבר עם קריאה וכתיבה של קבצים, הרבה מהדברים שאשתף עכשיו יהיו מוכרים לכם. אני ממליץ לך לקרוא אותו בכל זאת, כי לא כל המערכות זהות.

File System Access API נתמך ברוב דפדפני Chromium ב-Windows, ב-macOS, ב-ChromeOS וב-Linux. יוצאת דופן בולטת היא Brave, שבה התכונה זמינה כרגע רק באמצעות דגל. אנחנו עובדים על תמיכה ב-Android בהקשר של crbug.com/1011535.

שימוש ב-File System Access API

כדי להראות את העוצמה והיעילות של File System Access API, כתבתי עורך טקסט יחיד לקובץ. היא מאפשרת לפתוח קובץ טקסט, לערוך אותו, לשמור את השינויים בחזרה בדיסק או להפעיל קובץ חדש ולשמור את השינויים בדיסק. זה לא משהו מיוחד, אבל הוא מספק מספיק מידע כדי לעזור לכם להבין את המושגים.

תמיכה בדפדפנים

תמיכה בדפדפן

  • Chrome:‏ 86.
  • קצה: 86.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

זיהוי תכונות

כדי לבדוק אם יש תמיכה ב-File System Access API, צריך לבדוק אם שיטת הבחירה הרצויה קיימת.

if ('showOpenFilePicker' in self) {
  // The `showOpenFilePicker()` method of the File System Access API is supported.
}

רוצה לנסות?

אפשר לראות את File System Access API בפעולה בדמו של כלי לעריכת טקסט.

קריאת קובץ ממערכת הקבצים המקומית

התרחיש הראשון שאני רוצה להתייחס אליו הוא בקשה מהמשתמש לבחור קובץ, ואז פתיחה וקריאה של הקובץ מהדיסק.

צריך לבקש מהמשתמש לבחור קובץ לקריאה

נקודת הכניסה ל-File System Access API היא window.showOpenFilePicker(). כשמפעילים אותו, מוצגת תיבת דו-שיח לבחירת קובץ, והמשתמש מתבקש לבחור קובץ. אחרי שבוחרים קובץ, ה-API מחזיר מערך של מזהים של קבצים. פרמטר options אופציונלי מאפשר לכם להשפיע על ההתנהגות של בורר הקבצים. לדוגמה, אתם יכולים לאפשר למשתמש לבחור כמה קבצים, ספריות או סוגי קבצים שונים. בלי לציין אפשרויות, בורר הקבצים מאפשר למשתמש לבחור קובץ אחד בלבד. זה מושלם לעורך טקסט.

בדומה להרבה ממשקי API חזקים אחרים, ההפעלה של showOpenFilePicker() חייבת להתבצע בהקשר מאובטח, והקריאה חייבת להתבצע מתוך תנועת משתמש.

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  // Destructure the one-element array.
  [fileHandle] = await window.showOpenFilePicker();
  // Do something with the file handle.
});

אחרי שהמשתמש בוחר קובץ, הפונקציה showOpenFilePicker() מחזירה מערך של מזהים, במקרה הזה מערך עם רכיב אחד עם FileSystemFileHandle אחד שמכיל את המאפיינים והשיטות הנדרשים כדי לבצע אינטראקציה עם הקובץ.

מומלץ לשמור הפניה לכינוי של הקובץ כדי שתוכלו להשתמש בו מאוחר יותר. תצטרכו להשתמש בו כדי לשמור את השינויים בקובץ או לבצע פעולות אחרות בקובץ.

קריאת קובץ ממערכת הקבצים

עכשיו, כשיש לכם כינוי לקובץ, תוכלו לקבל את המאפיינים של הקובץ או לגשת לקובץ עצמו. בינתיים, אקרא את התוכן שלו. קריאה ל-handle.getFile() מחזירה אובייקט File שמכיל blob. כדי לקבל את הנתונים מה-blob, צריך להפעיל את אחד מהשיטות שלו (slice(),‏ stream(),‏ text() או arrayBuffer()).

const file = await fileHandle.getFile();
const contents = await file.text();

אפשר לקרוא לאובייקט File שמוחזר על ידי FileSystemFileHandle.getFile() רק כל עוד הקובץ הבסיסי בדיסק לא השתנה. אם הקובץ בדיסק ישתנה, לא תהיה אפשרות לקרוא את האובייקט File ותצטרכו לבצע קריאה חוזרת ל-getFile() כדי לקבל אובייקט File חדש שיקרא את הנתונים שהשתנו.

סיכום של כל המידע

כשמשתמשים ילחצו על הלחצן פתיחה, יוצג בדפדפן כלי לבחירת קבצים. אחרי שבוחרים קובץ, האפליקציה קוראת את התוכן ומעבירה אותו ל-<textarea>.

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
  textArea.value = contents;
});

כתיבת הקובץ במערכת הקבצים המקומית

בעורך הטקסט יש שתי דרכים לשמור קובץ: שמירה ושמירה בשם. הפונקציה Save כותבת את השינויים בקובץ המקורי באמצעות מאחז הקובץ שאוחזר קודם. אבל האפשרות שמירה בשם יוצרת קובץ חדש, ולכן נדרשת כינוי חדש לקובץ.

יצירת קובץ חדש

כדי לשמור קובץ, קוראים ל-showSaveFilePicker(), שמציג את בורר הקבצים במצב 'שמירה', ומאפשר למשתמש לבחור קובץ חדש שבו ירצה להשתמש לשמירה. רציתי גם שהעורך יתווסף באופן אוטומטי תוסף .txt, ולכן ציינתי כמה פרמטרים נוספים.

async function getNewFileHandle() {
  const options = {
    types: [
      {
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
  };
  const handle = await window.showSaveFilePicker(options);
  return handle;
}

שמירת השינויים בדיסק

כל הקוד לשמירת שינויים בקובץ זמין בדמו של עורך הטקסט שלי ב-GitHub. האינטראקציות של הליבה של מערכת הקבצים נמצאות ב-fs-helpers.js. באופן הפשוט ביותר, התהליך נראה כמו הקוד הבא. אני אעבור על כל שלב ואסביר אותו.

// fileHandle is an instance of FileSystemFileHandle..
async function writeFile(fileHandle, contents) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Write the contents of the file to the stream.
  await writable.write(contents);
  // Close the file and write the contents to disk.
  await writable.close();
}

כתיבת נתונים בדיסק מתבצעת באמצעות אובייקט FileSystemWritableFileStream, שהוא תת-סוג של WritableStream. כדי ליצור את הסטרימינג, קוראים לפונקציה createWritable() באובייקט של הטיפול בקובץ. כשמתבצעת קריאה ל-createWritable(), הדפדפן בודק קודם אם המשתמש העניק הרשאת כתיבה לקובץ. אם לא ניתנה הרשאה לכתיבה, בדפדפן תוצג למשתמש בקשה להענקת הרשאה. אם לא תאושר הרשאה, הפונקציה createWritable() תשליך DOMException והאפליקציה לא תוכל לכתוב בקובץ. בעורך הטקסט, הטיפול באובייקטים מסוג DOMException מתבצע בשיטה saveFile().

השיטה write() מקבלת מחרוזת, והיא נחוצה לכלי לעריכת טקסט. אבל אפשר גם להשתמש ב-BufferSource או ב-Blob. לדוגמה, אפשר להעביר ישירות אליו מקור נתונים:

async function writeURLToFile(fileHandle, url) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Make an HTTP request for the contents.
  const response = await fetch(url);
  // Stream the response into the file.
  await response.body.pipeTo(writable);
  // pipeTo() closes the destination pipe by default, no need to close it.
}

אפשר גם seek() או truncate() בתוך השידור כדי לעדכן את הקובץ במיקום ספציפי או לשנות את גודל הקובץ.

ציון הצעה לשם קובץ ולספריית התחלה

במקרים רבים, כדאי שהאפליקציה תציע שם קובץ או מיקום ברירת מחדל. לדוגמה, עורך טקסט יכול להציע שם קובץ ברירת מחדל של Untitled Text.txt במקום Untitled. כדי לעשות זאת, מעבירים נכס suggestedName כחלק מהאפשרויות של showSaveFilePicker.

const fileHandle = await self.showSaveFilePicker({
  suggestedName: 'Untitled Text.txt',
  types: [{
    description: 'Text documents',
    accept: {
      'text/plain': ['.txt'],
    },
  }],
});

אותו הדבר לגבי ספריית ברירת המחדל להתחלה. אם אתם מפתחים כלי לעריכת טקסט, כדאי להתחיל את תיבת הדו-שיח לשמירת קובץ או לפתיחת קובץ בתיקיית ברירת המחדל documents. לעומת זאת, אם אתם מפתחים כלי לעריכת תמונות, כדאי להתחיל בתיקיית ברירת המחדל pictures. כדי להציע ספריית ברירת מחדל להתחלה, מעבירים את המאפיין startIn לשיטות showSaveFilePicker, ‏ showDirectoryPicker() או showOpenFilePicker באופן הבא.

const fileHandle = await self.showOpenFilePicker({
  startIn: 'pictures'
});

רשימת הספריות הידועות של המערכת היא:

  • desktop: ספריית שולחן העבודה של המשתמש, אם קיים כזה.
  • documents: הספרייה שבה מאוחסנים בדרך כלל מסמכים שנוצרו על ידי המשתמש.
  • downloads: ספרייה שבה קבצים שהורדתם מאוחסנים בדרך כלל.
  • music: ספרייה שבה קובצי האודיו מאוחסנים בדרך כלל.
  • pictures: ספרייה שבה יאוחסנו בדרך כלל תמונות ותמונות סטילס אחרות.
  • videos: ספרייה שבה בדרך כלל מאוחסנים סרטונים או סרטים.

מלבד ספריות מערכת מוכרות, אפשר גם להעביר כינוי קיים של קובץ או ספרייה כערך של startIn. תיבת הדו-שיח תיפתח באותה ספרייה.

// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
  startIn: directoryHandle
});

ציון המטרה של בוררי קבצים שונים

לפעמים יש אפליקציות עם כלי בחירה שונים למטרות שונות. לדוגמה, עורך טקסט עשיר עשוי לאפשר למשתמש לפתוח קובצי טקסט, אבל גם לייבא תמונות. כברירת מחדל, כל בוחר קבצים נפתח במיקום האחרון שזוכר. כדי לעקוף את הבעיה הזו, אפשר לאחסן ערכים של id לכל סוג של בורר. אם מציינים id, ההטמעה של הכלי לבחירת קבצים שומרת ספרייה נפרדת שהיו בשימוש לאחרונה לאותו id.

const fileHandle1 = await self.showSaveFilePicker({
  id: 'openText',
});

const fileHandle2 = await self.showSaveFilePicker({
  id: 'importImage',
});

אחסון של מפתחות קבצים או מפתחות ספריות ב-IndexedDB

אפשר לשנות את הכינויים של הקבצים ואת הכינויים של הספריות כך שניתן יהיה לשמור כינוי של קובץ או ספרייה ב-IndexedDB או לקרוא ל-postMessage() כדי לשלוח אותם מאותו מקור ברמה עליונה.

שמירת מפתחות של קבצים או ספריות ב-IndexedDB מאפשרת לכם לשמור מצב או לזכור אילו קבצים או ספריות המשתמש עבד עליהם. כך ניתן לשמור רשימה של קבצים שנפתחו או נערכו לאחרונה, להציע לפתוח מחדש את הקובץ האחרון כשהאפליקציה נפתחת, לשחזר את ספריית העבודה הקודמת ועוד. עורך הטקסט שומר רשימה של חמשת הקבצים האחרונים שהמשתמש פתח, כך שאפשר לגשת שוב לקבצים האלה.

בדוגמת הקוד הבאה מוצג אחסון ואחזור של מאחז קובץ ומאחז ספרייה. אתם יכולים לראות את זה בפעולה ב-Glitch. (כדי לקצר את הקוד, השתמשתי בספרייה idb-keyval).

import { get, set } from 'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';

const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');

// File handle
button1.addEventListener('click', async () => {
  try {
    const fileHandleOrUndefined = await get('file');
    if (fileHandleOrUndefined) {
      pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const [fileHandle] = await window.showOpenFilePicker();
    await set('file', fileHandle);
    pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

// Directory handle
button2.addEventListener('click', async () => {
  try {
    const directoryHandleOrUndefined = await get('directory');
    if (directoryHandleOrUndefined) {
      pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const directoryHandle = await window.showDirectoryPicker();
    await set('directory', directoryHandle);
    pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

כינויים והרשאות של קבצים או ספריות מאוחסנים

מאחר שהרשאות לא תמיד נשמרות בין סשנים, צריך לוודא שהמשתמש העניק הרשאה לקובץ או לספרייה באמצעות queryPermission(). אם לא, צריך להתקשר למספר requestPermission() כדי לבקש אותו (מחדש). אותו הדבר קורה למזהי קבצים ומזהי תיקיות. צריך להריץ את fileOrDirectoryHandle.requestPermission(descriptor) או את fileOrDirectoryHandle.queryPermission(descriptor) בהתאמה.

בעורך הטקסט, יצרתי שיטה verifyPermission() שבודקת אם המשתמש כבר העניק הרשאה, ואם צריך, שולחת את הבקשה.

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  }
  // Check if permission was already granted. If so, return true.
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    return true;
  }
  // Request permission. If the user grants permission, return true.
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // The user didn't grant permission, so return false.
  return false;
}

ביקשתי הרשאת כתיבה עם בקשת הקריאה, וכך הפחתתי את מספר הבקשות להרשאה. המשתמש רואה בקשה אחת כשפותח את הקובץ, ומעניק הרשאה גם לקריאה וגם לכתיבה בו.

פתיחת ספרייה ורישום התוכן שלה

כדי להכין רשימה של כל הקבצים בספרייה, קוראים לפונקציה showDirectoryPicker(). המשתמש בוחר ספרייה בבורר, ולאחר מכן מוחזר FileSystemDirectoryHandle שמאפשר לכם למנות את הקבצים בספרייה ולגשת אליהם. כברירת מחדל, תהיה לכם גישת קריאה לקבצים בספרייה, אבל אם צריך גישת כתיבה, תוכלו להעביר את { mode: 'readwrite' } ל-method.

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  for await (const entry of dirHandle.values()) {
    console.log(entry.kind, entry.name);
  }
});

אם אתם צריכים לגשת לכל קובץ באמצעות getFile() כדי, למשל, לקבל את גודל הקובץ הספציפי, אל תשתמשו ב-await על כל תוצאה ברצף, אלא תעבדו את כל הקבצים במקביל, למשל באמצעות Promise.all().

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  const promises = [];
  for await (const entry of dirHandle.values()) {
    if (entry.kind !== 'file') {
      continue;
    }
    promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
  }
  console.log(await Promise.all(promises));
});

יצירת קבצים ותיקיות בספרייה או גישה אליהם

בספרייה, אפשר ליצור קבצים ותיקיות או לגשת אליהם באמצעות השיטה getFileHandle() או השיטה getDirectoryHandle(), בהתאמה. כדי לקבוע אם צריך ליצור קובץ או תיקייה חדשים אם הם לא קיימים, אפשר להעביר אובייקט options אופציונלי עם מפתח create וערך בוליאני של true או false.

// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
  create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });

פתרון נתיב של פריט בספרייה

כשעובדים עם קבצים או תיקיות בספרייה, כדאי לפתור את הנתיב של הפריט הרלוונטי. אפשר לעשות זאת באמצעות ה-method resolve(), עם השם המדויק. לצורך פתרון, הפריט יכול להיות צאצא ישיר או עקיף של הספרייה.

// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]

מחיקת קבצים ותיקיות בספרייה

אם יש לכם גישה לספרייה, תוכלו למחוק את הקבצים והתיקיות שבה באמצעות השיטה removeEntry(). לתיקיות, המחיקה יכולה להיות רקורסיבית ולכלול את כל תיקיות המשנה והקבצים שבתוכה.

// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });

מחיקה ישירה של קובץ או תיקייה

אם יש לכם גישה למטמון של קובץ או ספרייה, תוכלו להפעיל את remove() ב-FileSystemFileHandle או ב-FileSystemDirectoryHandle כדי להסיר אותו.

// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();

שינוי השם והעברה של קבצים ותיקיות

אפשר לשנות את השמות של הקבצים והתיקיות או להעביר אותם למיקום חדש באמצעות קריאה ל-move() בממשק FileSystemHandle. ל-FileSystemHandle יש את ממשקי הצאצאים FileSystemFileHandle ו-FileSystemDirectoryHandle. השיטה move() מקבלת פרמטר אחד או שניים. הערך הראשון יכול להיות מחרוזת עם השם החדש או FileSystemDirectoryHandle לתיקיית היעד. במקרה השני, הפרמטר השני האופציונלי הוא מחרוזת עם השם החדש, כך שאפשר להעביר ולשנות את השם בשלב אחד.

// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');

שילוב גרירה ושחרור

ממשקי ה-HTML לגרירה ושחרור מאפשרים לאפליקציות אינטרנט לקבל קבצים שנגררו ושולבו בדף אינטרנט. במהלך פעולת גרירה ושחרור, פריטים של קבצים וספריות שנגררים משויכים לרשומות של קבצים ורשומות של ספריות, בהתאמה. ה-method DataTransferItem.getAsFileSystemHandle() מחזירה הבטחה עם אובייקט FileSystemFileHandle אם הפריט שגורר הוא קובץ, והבטחה עם אובייקט FileSystemDirectoryHandle אם הפריט שגורר הוא ספרייה. ברשימה הבאה אפשר לראות את זה בפעולה. חשוב לזכור ש-DataTransferItem.kind בממשק הגרירה והשחרור הוא "file" גם לקבצים וגם לספריות, ואילו FileSystemHandle.kind ב-File System Access API הוא "file" לקבצים ו-"directory" לספריות.

elem.addEventListener('dragover', (e) => {
  // Prevent navigation.
  e.preventDefault();
});

elem.addEventListener('drop', async (e) => {
  e.preventDefault();

  const fileHandlesPromises = [...e.dataTransfer.items]
    .filter((item) => item.kind === 'file')
    .map((item) => item.getAsFileSystemHandle());

  for await (const handle of fileHandlesPromises) {
    if (handle.kind === 'directory') {
      console.log(`Directory: ${handle.name}`);
    } else {
      console.log(`File: ${handle.name}`);
    }
  }
});

גישה למערכת הקבצים הפרטית של המקור

מערכת הקבצים הפרטית של המקור היא נקודת קצה לאחסון, שכפי שמרמז השם, היא פרטית למקור הדף. בדרך כלל, הדפדפנים מטמיעים את זה על ידי שמירת התוכן של מערכת הקבצים הפרטית של המקור בדיסק במקום כלשהו, אבל לא נועד שהמשתמשים יוכלו לגשת לתוכן. באופן דומה, אין ציפייה לקיומם של קבצים או ספריות עם שמות שתואמים לשמות של צאצאים של מערכת הקבצים הפרטית המקורית. יכול להיות שהדפדפן ייראה כאילו יש קבצים, אבל בפנים – מכיוון שזו מערכת קבצים פרטית של המקור – יכול להיות שהדפדפן יאחסן את "הקבצים" האלה במסד נתונים או בכל מבנה נתונים אחר. בעיקרון, אם משתמשים ב-API הזה, לא צפויים למצוא את הקבצים שנוצרו בהתאמה אחת לאחת במקום כלשהו בדיסק הקשיח. אחרי שתקבלו גישה ל-FileSystemDirectoryHandle ברמה הבסיסית, תוכלו לפעול כרגיל במערכת הקבצים הפרטית של המקור.

const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });

תמיכה בדפדפנים

  • Chrome: 86.
  • Edge:‏ 86.
  • Firefox:‏ 111.
  • Safari: 15.2.

מקור

גישה לקבצים שעברו אופטימיזציה לביצועים ממערכת הקבצים הפרטית של המקור

מערכת הקבצים הפרטית של המקור מספקת גישה אופציונלית לסוג מיוחד של קובץ שמותאם במיוחד לביצועים. לדוגמה, היא מספקת גישת כתיבה בלעדית במקום לתוכן של הקובץ. ב-Chromium 102 ואילך, יש שיטה נוספת במערכת הקבצים הפרטית של המקור שמפשטת את הגישה לקבצים: createSyncAccessHandle() (לפעולות קריאה וכתיבה סינכרוניות). הוא מוצג ב-FileSystemFileHandle, אבל רק ב-Web Workers.

// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });

השלמה של תכונות חסרות (polyfilling)

לא ניתן לבצע פוליפוילינג מלא של השיטות של File System Access API.

  • אפשר לקבל הערכה של השיטה showOpenFilePicker() באמצעות רכיב <input type="file">.
  • אפשר לדמות את השיטה showSaveFilePicker() באמצעות רכיב <a download="file_name">, אבל הפעולה הזו מפעילה הורדה פרוגרמטית ולא מאפשרת להחליף קבצים קיימים.
  • אפשר לחקות את השיטה showDirectoryPicker() באופן חלקי באמצעות האלמנט הלא סטנדרטי <input type="file" webkitdirectory>.

פיתחנו ספרייה בשם browser-fs-access שמשתמשת ב-File System Access API בכל מקרה אפשרי, ובמקרים אחרים עוברת לאפשרויות החלופיות הטובות ביותר.

אבטחה והרשאות

צוות Chrome תכנן ויישם את File System Access API בהתאם לעקרונות המרכזיים שמוגדרים במאמר בקרת הגישה לתכונות עוצמתיות של פלטפורמת אינטרנט, כולל שליטה ושקיפות של המשתמש וארגונומיה של המשתמש.

פתיחת קובץ או שמירת קובץ חדש

כלי לבחירת קבצים לפתיחת קובץ לקריאה
כלי לבחירת קבצים שמשמש לפתיחת קובץ קיים לקריאה.

כשפותחים קובץ, המשתמש מספק הרשאה לקרוא קובץ או ספרייה באמצעות בורר הקבצים. אפשר להציג את בורר הקבצים הפתוח רק באמצעות מחווה של המשתמש כשהוא מוצג מהקשר מאובטח. אם המשתמשים ישנו את דעתם, הם יוכלו לבטל את הבחירה בבורר הקבצים והאתר לא יקבל גישה לשום דבר. זו אותה התנהגות כמו זו של הרכיב <input type="file">.

בורר קבצים לשמירת קובץ בדיסק.
בורר קבצים שמשמש לשמירת קובץ בדיסק.

באופן דומה, כשאפליקציית אינטרנט מבקשת לשמור קובץ חדש, הדפדפן מציג את בוחר הקבצים לשמירה, ומאפשר למשתמש לציין את השם והמיקום של הקובץ החדש. מכיוון שהם שומרים קובץ חדש במכשיר (ולא מחליפים קובץ קיים), הכלי לבחירת קבצים מעניק לאפליקציה הרשאה לכתוב בקובץ.

תיקיות מוגבלות

כדי להגן על המשתמשים ועל הנתונים שלהם, יכול להיות שהדפדפן יגביל את היכולת שלהם לשמור בתיקיות מסוימות, למשל תיקיות ליבה של מערכת ההפעלה כמו Windows, התיקיות של ספריית macOS. במקרה כזה, בדפדפן תוצג בקשה למשתמש לבחור תיקייה אחרת.

שינוי קובץ או ספרייה קיימים

אפליקציית אינטרנט לא יכולה לשנות קובץ בדיסק בלי לקבל הרשאה מפורשת מהמשתמש.

בקשת הרשאה

אם משתמש רוצה לשמור שינויים בקובץ שכבר העניק לו גישה לקריאה, בדפדפן תוצג בקשה להרשאה, שבה יתבקש מהמשתמש לאשר לאתר לכתוב את השינויים בדיסק. אפשר להפעיל את בקשת ההרשאה רק באמצעות תנועת משתמש, למשל, בלחיצה על לחצן שמירה.

בקשה להרשאה שמוצגת לפני שמירת קובץ.
הנחיה שמוצגת למשתמשים לפני שהדפדפן מקבל הרשאת כתיבה בקובץ קיים.

לחלופין, אפליקציית אינטרנט שמאפשרת לערוך כמה קבצים, כמו סביבת פיתוח משולבת (IDE), יכולה גם לבקש הרשאה לשמור את השינויים בזמן הפתיחה.

אם המשתמש בוחר באפשרות 'ביטול' ולא מעניק הרשאת כתיבה, אפליקציית האינטרנט לא תוכל לשמור שינויים בקובץ המקומי. היא צריכה לספק למשתמשים שיטה חלופית לשמירת הנתונים, למשל, לספק דרך "להוריד" את הקובץ או לשמור נתונים בענן.

שקיפות

סמל של סרגל הכתובות
סמל בסרגל הכתובות שמציין שהמשתמש העניק לאתר הרשאה לשמור קובץ מקומי.

אחרי שמשתמש מעניק לאפליקציית אינטרנט הרשאה לשמור קובץ מקומי, מופיע סמל בדפדפן בסרגל הכתובות. לחיצה על הסמל פותחת חלון קופץ שבו מוצגת רשימת הקבצים שהמשתמש העניק גישה אליהם. המשתמש תמיד יכול לבטל את הגישה הזו אם הוא רוצה.

קבועות של הרשאות

אפליקציית האינטרנט יכולה להמשיך לשמור שינויים בקובץ בלי לבקש אישור עד שכל הכרטיסיות של המקור שלו ייסגרו. אחרי שסוגרים כרטיסייה, לאתר אין יותר גישה אליה. בפעם הבאה שהמשתמש ישתמש באפליקציית האינטרנט, הוא יתבקש שוב להעניק גישה לקבצים.

משוב

נשמח לשמוע על החוויה שלכם עם File System Access API.

תיאור של עיצוב ה-API

האם יש משהו ב-API שלא פועל כצפוי? או אולי חסרות שיטות או מאפיינים שאתם צריכים כדי להטמיע את הרעיון? יש לכם שאלות או הערות לגבי מודל האבטחה?

בעיה בהטמעה?

מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט?

  • שולחים דיווח על באג בכתובת https://new.crbug.com. חשוב לכלול כמה שיותר פרטים, הוראות לשחזור הבעיה ולהגדיר את Components ל-Blink>Storage>FileSystem. Glitch הוא כלי מצוין לשיתוף של ריפרודוקציות מהירות.

מתכננים להשתמש ב-API?

אתם מתכוונים להשתמש ב-File System Access API באתר שלכם? התמיכה הציבורית שלכם עוזרת לנו לקבוע את סדר העדיפויות של התכונות, ומראה ליצרני דפדפנים אחרים כמה חשובה התמיכה בהם.

קישורים שימושיים

אישורים

המפרט של File System Access API נכתב על ידי Marijn Kruisselbrink.