قالب انتخابی برای خواندن: پیش‌فرض بهتری برای API کلیپ‌بورد Async

پراشانت سینگ
Prashant Singh

منتشر شده: ۳ ژوئیه ۲۰۲۶

اگر تا به حال از ویرایشگر سند، برنامه صفحه گسترده یا هر برنامه‌ای که چسباندن محتوا از کلیپ‌بورد تعامل اصلی آن است استفاده کرده‌اید، انتظار کاربر را می‌دانید: چسباندن باید فوری باشد.

متأسفانه، در بسیاری از موارد، برنامه‌های وب داده‌های کلیپ‌بورد بیشتری نسبت به نیاز خود می‌خوانند، که می‌تواند باعث شود چسباندن محتوا کندتر از آنچه که باید باشد به نظر برسد، چرخه‌های CPU را هدر دهد و از حافظه برای داده‌هایی استفاده کند که برنامه هرگز به آنها نیاز ندارد.

قابلیت خواندن قالب انتخابی، با اصلاح API کلیپ‌بورد Async، این مشکل عملکرد را برطرف می‌کند تا انتظارات کاربران را بهتر برآورده کند و آنچه توسعه‌دهندگان نیاز دارند را فراهم کند - یعنی فقط قالب‌هایی را که برنامه واقعاً به آنها نیاز دارد، بخواند.

مشکل: خواندن تمام فرمت‌های داده از قبل

وقتی کاربر از هر برنامه‌ای - مثلاً اکسل، گوگل داکز یا فتوشاپ - کپی می‌کند، برنامه معمولاً چندین نمایش از همان داده‌ها را در کلیپ‌بورد سیستم می‌نویسد: متن ساده، HTML، تصویر و گاهی اوقات فرمت‌های سفارشی. برنامه وب که داده‌ها را می‌خواند (وقتی کاربر از کلیپ‌بورد پیست می‌کند) به ندرت به همه این نمایش‌ها نیاز دارد. یک ویرایشگر صفحه گسترده ممکن است HTML بخواهد. یک ویرایشگر متن ممکن است بخواهد دستور "چسباندن به عنوان متن ساده" را نمایش دهد. یک ویرایشگر کد ممکن است فقط متن را بخواهد و نه چیز دیگری.

تاکنون، navigator.clipboard.read() تمام نمایش‌های موجود از داده‌ها را از کلیپ‌بورد سیستم دریافت می‌کرد، پاکسازی را اجرا می‌کرد (که می‌تواند زمان‌بر باشد، به‌خصوص با فایل‌های HTML یا تصاویر حجیم)، و بایت‌ها را در حافظه نگه می‌داشت، حتی برای فرمت‌هایی که برنامه هرگز از آنها استفاده نمی‌کرد. هرچه فایل‌های استفاده نشده بزرگتر باشند، کاربر مجبور است مدت زمان بیشتری منتظر بماند و تب حافظه بیشتری را مصرف می‌کرد، درست زمانی که پاسخگویی به چسباندن بیشترین اهمیت را دارد.

راه حل: فقط شمارش کنید، سپس در صورت تقاضا بخوانید

از کروم و اج ۱۴۹، اشیاء ClipboardItem که توسط متد navigator.clipboard.read() برگردانده می‌شوند، فقط آنچه را که در کلیپ‌بورد است توصیف می‌کنند، به جای اینکه فوراً به داده‌ها دسترسی بدهند. بایت‌های واقعی برای یک فرمت مشخص فقط زمانی از کلیپ‌بورد خوانده می‌شوند که ClipboardItem.getType(mimeType) را فراخوانی کنید. سطح API بدون تغییر باقی مانده است. همان کدی که دیروز نوشتید، امروز هم کار می‌کند و مزایای عملکردی فوری را به شما ارائه می‌دهد:

// Enumerate the available MIME types.
// No payload is fetched yet.
const items = await navigator.clipboard.read();

for (const item of items) {
  if (item.types.includes('text/html')) {
    // Only read the HTML data.
    // The data is fetched (and sanitized) here only.
     const blob = await item.getType('text/html');
     useThePastedHtml(await blob.text());
     break;
   }

   // text/plain, image/png, or any other custom formats
   // stored in the clipboard are never read.
}

یکی دیگر از مزایای عملکرد در اینجا این است که ذخیره سازی خودکار است. فراخوانی getType('text/html') دو بار در همان ClipboardItem فقط یک بار از کلیپ بورد سیستم خوانده می‌شود و Blob ذخیره شده را در فراخوانی دوم برمی‌گرداند.

مزایا

تأخیر. کاری که باید هنگام فراخوانی navigator.clipboard.read() انجام می‌شد، مانند کپی کردن بایت‌ها در فرآیندها، پاکسازی HTML یا رمزگشایی تصاویر، اکنون فقط برای فرمت‌هایی که برنامه شما واقعاً به آنها نیاز دارد، انجام می‌شود. برنامه‌هایی که فقط یک فرمت واحد را می‌خوانند، بیشترین صرفه‌جویی را دریافت می‌کنند. هرچه فرمت‌های بزرگتری را رد کنید، سود بیشتری خواهید برد. HTML بیشترین سود را می‌برد زیرا پاکسازی HTML پرهزینه است.

حافظه. پیش از این، فراخوانی navigator.clipboard.read() هنگامی که کلیپ‌بورد حاوی یک تصویر ۵۰ مگابایتی بود، به این معنی بود که تا زمان انتشار ClipboardItem ۵۰ مگابایت از حافظه رندر صفحه وب مورد نیاز بود، حتی اگر برنامه فقط به نمایش text/plain داده‌ها نیاز داشت. اکنون، فقط فرمت‌هایی که برنامه شما هنگام فراخوانی ClipboardItem.getType() می‌خواند، در فرآیند رندر از حافظه استفاده می‌کنند. برای برنامه‌هایی که چسباندن (paste) در آنها رایج است، مانند ویرایشگرهای سند، آهنگسازان ایمیل یا ابزارهای طراحی، این امر به طور معناداری استفاده از حافظه را در طول چسباندن کاهش می‌دهد، به خصوص در جلسات چند زبانه که ممکن است چندین ویرایشگر به طور موازی کلیپ‌بورد را بخوانند.

بدون نیاز به ثبت‌نام. هر سایتی در وب به طور خودکار از این مزایا بهره‌مند می‌شود. نیازی به انتقال کد یا گیت کردن کد پشت منطق تشخیص ویژگی نیست.

یک تغییر رفتار که باید از آن آگاه باشید

خواندن داده‌ها بعداً تغییر جزئی در رفتار ایجاد می‌کند: یک شیء ClipboardItem دیگر یک تصویر فوری از کلیپ‌بورد در لحظه‌ای که فراخوانی navigator.clipboard.read() حل شده است، نیست.

وقتی تابع read() فراخوانی می‌شود و یک ClipboardItem برمی‌گرداند، این آیتم فقط یک مدیریت کند برای داده‌های موجود در کلیپ‌بورد سیستم است. اگر محتویات کلیپ‌بورد بین فراخوانی read() و فراخوانی بعدی getType() تغییر کند، آن فراخوانی با یک InvalidStateError رد می‌شود. این موضوع حتی برای فرمتی که قبلاً با موفقیت در همان ClipboardItem خوانده‌اید نیز صادق است.

// Read from the clipboard.
const items = await navigator.clipboard.read();
// Get the first ClipboardItem.
const item = items[0];

// Actually fetch the data now, which works fine.
const text = await item.getType('text/plain');

// The user writes something new to the clipboard
// by copying from another app, or your own code writes to
// the clipboard again.
await navigator.clipboard.writeText('something new');

// Fetching the data from the earlier ClipboardItem
// rejects with InvalidStateError, because the contents
// of the clipboard changed.
const html = await item.getType('text/html');

این اتفاق می‌افتد زیرا برگرداندن داده‌هایی که دیگر کلیپ‌بورد را منعکس نمی‌کنند، گیج‌کننده خواهد بود و در غیر این صورت منجر به مشکلاتی می‌شود که اشکال‌زدایی آنها دشوار است.

در عمل، این تغییر برای اکثر کدها قابل مشاهده نیست، زیرا برنامه‌ها معمولاً read() و getType() را پشت سر هم درون یک کنترل‌کننده‌ی چسباندن (paste handler) فراخوانی می‌کنند. با این حال، ممکن است بخواهید مواردی را که کدی دارید که یک ClipboardItem را در مرزهای ناهمگام (async) نگه می‌دارد، زیر نظر داشته باشید.

در اینجا مثالی آورده شده است که در آن این تغییر ممکن است به مشکل تبدیل شود: ذخیره اشیاء ClipboardItem در window ، نمایش آنها در رابط کاربری clipboard-history، یا انتظار برای تعامل کاربر قبل از فراخوانی getType() . برای جلوگیری از مشکلات با سناریویی مانند این:

  • فراخوانی‌های getType() را در بلوک‌های try / catch قرار دهید و InvalidStateError به عنوان سیگنالی مبنی بر اینکه کلیپ‌بورد به جای دیگری منتقل شده و کد باید دوباره خوانده شود، در نظر بگیرید.
  • اگر نیاز به حفظ داده‌های کلیپ‌بورد دارید، به محض اینکه getType() را اجرا کردید، Blob در ساختار خود کپی کنید. با ClipboardItem به عنوان یک حافظه بلندمدت رفتار نکنید.
  • از رویداد clipboardChange برای بی‌اعتبار کردن دستگیره‌های قدیمی به صورت پیشگیرانه استفاده کنید.

استانداردها و پشتیبانی مرورگرها

قابلیت خواندن با فرمت انتخابی، تغییری در مشخصات رابط برنامه‌نویسی کاربردی کلیپ‌بورد است که توسط گروه کاری ویرایش وب و تیم پلتفرم وب مایکروسافت اج ایجاد شده است.

سافاری نیز از این رفتار پشتیبانی می‌کند و فایرفاکس نیز موضع مثبتی در قبال استانداردها و قصد خود برای همسوسازی را نشان داده است.

یک بهبود بی‌سروصدا اما معنادار در عملکرد و قابلیت همکاری برای رابط برنامه‌نویسی کلیپ‌بورد، که اکنون کرومیوم نیز به همان مدل همگرا می‌شود.