الوصول إلى النصوص والصور في الحافظة بشكل أكثر أمانًا بدون حظرها
كانت الطريقة التقليدية للوصول إلى حافظة النظام هي استخدام رمز
document.execCommand()
لتفاعلات الحافظة. على الرغم من أن هذه الطريقة متاحة على نطاق واسع،
فإن اللصق أصبح تكلفة كبيرة، حيث كان الوصول إلى الحافظة متزامنًا، وكان يمكن فقط قراءة
وأكتب إلى DOM.
هذا أمر جيد لأجزاء النص الصغيرة، ولكن هناك العديد من الحالات التي يؤدي فيها حظر
الصفحة لنقل الحافظة إلى تقديم تجربة سيئة. يستغرق التعقيم أو
قد تكون هناك حاجة إلى فك ترميز الصور قبل أن يتم لصق المحتوى بأمان. قد يحتاج المتصفّح
إلى تحميل الموارد المرتبطة أو تضمينها من مستند تم لصقه. سيؤدي ذلك إلى
سيحظر الصفحة أثناء الانتظار على القرص أو الشبكة. تخيل إضافة أذونات
إلى المزيج، ما يتطلب من المتصفّح حظر الصفحة أثناء طلب
الوصول إلى الحافظة. في الوقت نفسه، إنّ الأذونات التي تم وضعها حول
document.execCommand()
للتفاعل مع الحافظة غير محدّدة بوضوح وتختلف
بين المتصفّحات.
تعالج واجهة برمجة التطبيقات Async Clipboard API هذه المشاكل، إذ تقدّم نموذج أذونات محدّدًا جيدًا لا يؤدي إلى حظر الصفحة. يمكن فقط معالجة النصوص والصور في Async Clipboard API. في أغلب المتصفحات، غير أن هذه الميزة متاحة عبر الإنترنت. احرص على دراسة المتصفح بعناية نظرة عامة على التوافق لكل قسم من الأقسام التالية.
النسخ: كتابة البيانات في الحافظة
writeText()
لنسخ نص إلى الحافظة، اضغط على writeText()
. بما أنّ واجهة برمجة التطبيقات هذه
غير متزامنة، تعرض الدالة writeText()
عملية غير مكتملة يتم حلّها أو
رفضها استنادًا إلى ما إذا تم نسخ النص الذي تم تمريره بنجاح:
async function copyPageUrl() {
try {
await navigator.clipboard.writeText(location.href);
console.log('Page URL copied to clipboard');
} catch (err) {
console.error('Failed to copy: ', err);
}
}
كتابة()
في الواقع، writeText()
هي طريقة سهلة لاستخدام الطريقة العامة write()
، والتي تتيح لك أيضًا نسخ الصور إلى الحافظة. مثل writeText()
، هو
غير متزامن ويعرض وعدًا.
لكتابة صورة إلى الحافظة، ستحتاج إلى الصورة
blob
إحدى الطرق لتنفيذ
هذا هي طلب الصورة من خادم باستخدام fetch()
، ثمّ استدعاء
blob()
في
الاستجابة.
قد يكون طلب إحدى الصور من الخادم غير مرغوب فيه أو ربما يكون
مجموعة متنوعة من الأسباب. لحسن الحظ، يمكنك أيضًا رسم الصورة على لوحة و
استدعاء toBlob()
طريقة اللوحة.
بعد ذلك، مرِّر مصفوفة من عناصر ClipboardItem
كمَعلمة إلى write()
الدالة. في الوقت الحالي، يمكنك تمرير صورة واحدة فقط في كل مرة، لكننا نأمل أن نضيف إليها
دعم صور متعددة في المستقبل. يأخذ ClipboardItem
عنصرًا يحتوي على
نوع MIME للصورة كمفتاح وblob كقيمة. بالنسبة إلى كائنات تخزين البيانات الثنائية الكبيرة
المُستحَصلة من fetch()
أو canvas.toBlob()
، يحتوي الحقل blob.type
تلقائيًا على نوع MIME الصحيح للصورة.
try {
const imgURL = '/images/generic/file.png';
const data = await fetch(imgURL);
const blob = await data.blob();
await navigator.clipboard.write([
new ClipboardItem({
// The key is determined dynamically based on the blob's type.
[blob.type]: blob
})
]);
console.log('Image copied.');
} catch (err) {
console.error(err.name, err.message);
}
بدلاً من ذلك، يمكنك كتابة وعد للعنصر ClipboardItem
.
بالنسبة إلى هذا النمط، عليك معرفة نوع MIME للبيانات مسبقًا.
try {
const imgURL = '/images/generic/file.png';
await navigator.clipboard.write([
new ClipboardItem({
// Set the key beforehand and write a promise as the value.
'image/png': fetch(imgURL).then(response => response.blob()),
})
]);
console.log('Image copied.');
} catch (err) {
console.error(err.name, err.message);
}
حدث النسخ
في حال بدأ المستخدم عملية نسخ إلى الحافظة
ولم يُطلِب preventDefault()
، يتضمّن
حدث copy
خاصية clipboardData
تتضمّن العناصر بالتنسيق الصحيح.
إذا كنت تريد تنفيذ منطقك الخاص، عليك استدعاء preventDefault()
لمنع السلوك التلقائي لصالح التنفيذ الخاص بك.
في هذه الحالة، سيكون الحقل clipboardData
فارغًا.
لنفترض أنّ هناك صفحة تحتوي على نص وصورة، وعندما يختار المستخدم الكل ويشغّل ميزة "نسخ إلى الحافظة"، يجب أن يتخلّص الحل المخصّص من النص ويقتصر على
نسخ الصورة فقط. يمكنك تحقيق ذلك كما هو موضّح في نموذج التعليمات البرمجية أدناه.
ما لم يتم تناوله في هذا المثال هو كيفية الرجوع إلى واجهات برمجة التطبيقات الأقدم
في حال عدم توفّر واجهة برمجة التطبيقات Clipboard API.
<!-- The image we want on the clipboard. -->
<img src="kitten.webp" alt="Cute kitten.">
<!-- Some text we're not interested in. -->
<p>Lorem ipsum</p>
document.addEventListener("copy", async (e) => {
// Prevent the default behavior.
e.preventDefault();
try {
// Prepare an array for the clipboard items.
let clipboardItems = [];
// Assume `blob` is the blob representation of `kitten.webp`.
clipboardItems.push(
new ClipboardItem({
[blob.type]: blob,
})
);
await navigator.clipboard.write(clipboardItems);
console.log("Image copied, text ignored.");
} catch (err) {
console.error(err.name, err.message);
}
});
بالنسبة إلى فعالية copy
:
بالنسبة إلى ClipboardItem
:
لصق: قراءة البيانات من الحافظة
readText()
لقراءة نص من الحافظة، اتصل بالرقم navigator.clipboard.readText()
وانتظِر
إلى أن يتم حلّ المشكلة التي تم عرضها:
async function getClipboardContents() {
try {
const text = await navigator.clipboard.readText();
console.log('Pasted content: ', text);
} catch (err) {
console.error('Failed to read clipboard contents: ', err);
}
}
read()
طريقة navigator.clipboard.read()
هي أيضًا غير متزامنة وتعرض
وعدك. لقراءة صورة من الحافظة، احصل على قائمة بمواد
ClipboardItem
العرض، ثمّ كرِّر قراءتها.
يمكن أن تحتفظ كل "ClipboardItem
" بمحتواها بأنواع مختلفة، لذا يجب اتّباع الخطوات التالية:
تكرار قائمة الأنواع، وتكرارها مرة أخرى باستخدام التكرار الحلقي for...of
. لكل نوع،
يمكنك استدعاء طريقة getType()
مع النوع الحالي كوسيطة للحصول على
الكائن الثنائي الكبير المقابل. وكما في السابق، لن تكون هذه التعليمة البرمجية مرتبطة بالصور،
والعمل مع أنواع الملفات المستقبلية الأخرى.
async function getClipboardContents() {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
console.log(URL.createObjectURL(blob));
}
}
} catch (err) {
console.error(err.name, err.message);
}
}
العمل مع الملفات التي تم لصقها
من المفيد أن يتمكّن المستخدمون من استخدام اختصارات لوحة المفاتيح للحافظة، مثل ctrl+c وctrl+v. يعرض Chromium الملفات القابلة للقراءة فقط في الحافظة على النحو الموضّح أدناه. يتم تشغيل هذا الإجراء عندما يضغط المستخدم على اختصار اللصق التلقائي لنظام التشغيل أو عندما ينقر المستخدم على تعديل ثم لصق في شريط قوائم المتصفّح. ما مِن حاجة إلى رمز إضافي للصرف الصحي.
document.addEventListener("paste", async e => {
e.preventDefault();
if (!e.clipboardData.files.length) {
return;
}
const file = e.clipboardData.files[0];
// Read the file's contents, assuming it's a text file.
// There is no way to write back to it.
console.log(await file.text());
});
حدث اللصق
كما أشرنا سابقًا، هناك خطط لتقديم أحداث للعمل باستخدام واجهة برمجة التطبيقات Clipboard API،
ولكن في الوقت الحالي، يمكنك استخدام حدث "paste
" الحالي. وهي تعمل بشكل جيد مع METHODS الجديدة
غير المتزامنة لقراءة نص الحافظة. كما هو الحال مع حدث copy
، لا تفعل
نسيت الاتصال بـ preventDefault()
.
document.addEventListener('paste', async (e) => {
e.preventDefault();
const text = await navigator.clipboard.readText();
console.log('Pasted text: ', text);
});
التعامل مع أنواع MIME متعددة
تضع معظم عمليات التنفيذ تنسيقات بيانات متعددة في الحافظة لإجراء عملية قطع أو نسخ واحدة. هناك سببان لذلك: بصفتك مطوّر تطبيقات، ليس لديك أي طريقة لمعرفة إمكانات التطبيق الذي يريد المستخدم نسخ نص أو صور إليه، والعديد من التطبيقات تتيح لصق البيانات المنظَّمة كنص عادي. عادةً ما تكون هذه يتم عرضه للمستخدمين مع عنصر القائمة تعديل باسم مثل لصق مطابقة النمط أو لصق بدون تنسيق.
يوضح المثال التالي كيفية إجراء هذا. يستخدم هذا المثال fetch()
للحصول على
بيانات الصورة، ولكن يمكن أن تأتي أيضًا من
<canvas>
أو File System Access API.
async function copy() {
const image = await fetch('kitten.png').then(response => response.blob());
const text = new Blob(['Cute sleeping kitten'], {type: 'text/plain'});
const item = new ClipboardItem({
'text/plain': text,
'image/png': image
});
await navigator.clipboard.write([item]);
}
الأمان والأذونات
دائمًا ما كان الوصول إلى الحافظة يمثّل مشكلةً أمنية للمتصفّحات. بدون
الأذونات المناسبة، يمكن للصفحة نسخ كل أنواع المحتوى الضار
بدون علم المستخدم إلى حافظة النسخ واللصق، ما قد يؤدي إلى نتائج كارثية عند لصقه.
تخيل صفحة ويب تنسخ rm -rf /
أو
صورة قنبلة فك الضغط
إلى الحافظة بدون أن تلاحظ ذلك.
إنّ منح صفحات الويب إذن وصول غير محدود للقراءة إلى الحافظة هو أمر أكثر إزعاجًا. نسخ المستخدمين المعلومات الحساسة بشكل روتيني مثل كلمات المرور التفاصيل الشخصية إلى الحافظة، والتي يمكن بعد ذلك قراءتها بواسطة أي صفحة دون معرفة المستخدم.
كما هو الحال مع العديد من واجهات برمجة التطبيقات الجديدة، لا تتوفّر واجهة برمجة التطبيقات Clipboard API إلا للصفحات التي يتم عرضها على بروتوكول بروتوكول HTTPS. للمساعدة في منع إساءة الاستخدام، لا يُسمح بالوصول إلى الحافظة إلا عندما تكون الصفحة علامة التبويب النشطة. يمكن للصفحات الموجودة في علامات التبويب النشطة الكتابة إلى الحافظة بدون يطلب إذنًا، ولكن القراءة من الحافظة تتطلب دائمًا إذن.
تمت إضافة أذونات النسخ واللصق إلى
Permissions API.
ويتم منح إذن "clipboard-write
" تلقائيًا للصفحات عندما
علامة التبويب النشطة. يجب طلب إذن clipboard-read
، ويمكنك
إجراء ذلك من خلال محاولة قراءة البيانات من الحافظة. يُظهر الكود التالي الأخير:
const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);
// Listen for changes to the permission state
permissionStatus.onchange = () => {
console.log(permissionStatus.state);
};
يمكنك أيضًا التحكم في ما إذا كانت إيماءة المستخدم مطلوبة لاستدعاء القطع أو
واللصق باستخدام خيار allowWithoutGesture
. تختلف القيمة التلقائية لهذه السمة
حسب المتصفح، لذا يجب تضمينها دائمًا.
في ما يلي الحالات التي تكون فيها الطبيعة غير المتزامنة لواجهة برمجة التطبيقات Clipboard API مفيدة حقًا: عند محاولة قراءة بيانات الحافظة أو كتابتها، يُطلب تلقائيًا من المستخدم منح الإذن إذا لم يتم منحه من قبل. ونظرًا لأن واجهة برمجة التطبيقات قائمة على الوعد، هذه المسألة شفافة تمامًا، ويؤدي رفض المستخدم لإذن الوصول إلى الحافظة والوعد بالرفض حتى تتمكن الصفحة من الاستجابة بشكل مناسب.
بما أنّ المتصفّحات لا تسمح بالوصول إلى الحافظة إلا عندما تكون الصفحة هي علامة التبويب النشطة،
ستلاحظ أنّ بعض الأمثلة الواردة هنا لا يتم تنفيذها إذا تم لصقها مباشرةً في
وحدة تحكّم المتصفّح، لأنّ أدوات المطوّرين هي علامة التبويب النشطة. هناك خدعة: يمكنك تأجيل
الوصول إلى الحافظة باستخدام setTimeout()
، ثم النقر سريعًا داخل الصفحة ل
تركيزها قبل استدعاء الدوال:
setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);
دمج سياسة الأذونات
لاستخدام واجهة برمجة التطبيقات في إطارات iframe، يجب تفعيلها من خلال
سياسة الأذونات
التي تحدد آلية تسمح بتمكين
إيقاف ميزات المتصفح وواجهات برمجة التطبيقات المتنوعة. على وجه التحديد، عليك اجتياز اختبار clipboard-read
أو clipboard-write
أو كلاهما، حسب احتياجات تطبيقك.
<iframe
src="index.html"
allow="clipboard-read; clipboard-write"
>
</iframe>
رصد الميزات
لاستخدام واجهة برمجة التطبيقات Async Clipboard API مع إتاحة جميع المتصفّحات، اختبِر navigator.clipboard
واستخدِم الطرق السابقة. على سبيل المثال، إليك كيفية
يمكنك تنفيذ اللصق لتضمين متصفحات أخرى.
document.addEventListener('paste', async (e) => {
e.preventDefault();
let text;
if (navigator.clipboard) {
text = await navigator.clipboard.readText();
}
else {
text = e.clipboardData.getData('text/plain');
}
console.log('Got pasted text: ', text);
});
هذه ليست القصة بأكملها. قبل Async Clipboard API، كان هناك مزيج من
عمليات التنفيذ المختلفة للنسخ واللصق في متصفّحات الويب. في معظم المتصفحات،
يمكن تشغيل نسخ ولصق المتصفح باستخدام
"document.execCommand('copy')
" وdocument.execCommand('paste')
" إذا كان النص الذي تريد نسخه هو سلسلة غير متوفّرة في نموذج DOM، يجب إدخالها في نموذج DOM واختيارها:
button.addEventListener('click', (e) => {
const input = document.createElement('input');
input.style.display = 'none';
document.body.appendChild(input);
input.value = text;
input.focus();
input.select();
const result = document.execCommand('copy');
if (result === 'unsuccessful') {
console.error('Failed to copy text.');
}
input.remove();
});
إصدارات تجريبية
يمكنك الاطّلاع على واجهة برمجة التطبيقات Async Clipboard API في العروض التوضيحية أدناه. تأثير اهتزاز يمكنه إنشاء ريمكس من العرض التوضيحي النصي أو العرض التوضيحي للصور وتجربتها معها.
يوضّح المثال الأول نقل النص من الحافظة وإليها.
لتجربة واجهة برمجة التطبيقات مع الصور، يمكنك استخدام هذا العرض التوضيحي. يُرجى العِلم أنّ ملفات PNG فقط هي المتوافقة وفي عدد قليل من المتصفّحات فقط.
روابط ذات صلة
الشكر والتقدير
نفّذ Darwin واجهة برمجة التطبيقات Asynchronous Clipboard API. "هوانغ" وغاري" Kachmarçík قدّم "داروين" أيضًا العرض الترويجي. نشكر كياريك وغاريك كاچمارسيك مرة أخرى على مراجعة أجزاء من هذه المقالة.
صورة رئيسية من إعداد ماركوس وينكلر على إزالة البداية