وصول آمن وغير آمن إلى الحافظة للنصوص والصور
كانت الطريقة التقليدية للوصول إلى حافظة النظام هي
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);
}
}
write()
في الواقع، تمثّل writeText()
مجرد طريقة ملائمة لنوع write()
العام.
والذي يتيح لك أيضًا نسخ الصور إلى الحافظة. مثل writeText()
،
غير متزامن ويعرض وعدًا.
لكتابة صورة إلى الحافظة، ستحتاج إلى الصورة
blob
تتمثل إحدى طرق القيام
وذلك عن طريق طلب الصورة من أحد الخوادم باستخدام fetch()
، ثم الاتصال
blob()
في
الاستجابة.
قد يكون طلب إحدى الصور من الخادم غير مرغوب فيه أو ربما يكون
مجموعة متنوعة من الأسباب. لحسن الحظ، يمكنك أيضًا رسم الصورة إلى لوحة
لاستدعاء اللوحة
toBlob()
.
بعد ذلك، عليك تمرير مصفوفة من عناصر ClipboardItem
كمَعلمة إلى write()
.
. في الوقت الحالي، يمكنك تمرير صورة واحدة فقط في كل مرة، لكننا نأمل أن نضيف إليها
دعم صور متعددة في المستقبل. يستخدم ClipboardItem
كائنًا باستخدام
نوع MIME للصورة كمفتاح والنقطة الثنائية (blob) كقيمة. للكائنات الثنائية الكبيرة (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
فارغًا.
ضع في اعتبارك صفحة تحتوي على نص وصورة، وعندما يحدد المستخدم الكل
في إنشاء نسخة من الحافظة، فيجب على الحل المخصص تجاهل النص
انسخ الصورة. يمكنك تحقيق ذلك كما هو موضّح في نموذج التعليمات البرمجية أدناه.
ما لم يتم تناوله في هذا المثال هو كيفية العودة إلى السابق
واجهات برمجة التطبيقات في حال عدم توافق واجهة برمجة التطبيقات للحافظة
<!-- 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
" الحالي. وهو يعمل بشكل جيد مع الواجهة الجديدة
طرق غير متزامنة لقراءة نص الحافظة. كما هو الحال مع حدث 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 /
أو
صورة قنبلة إزالة الضغط
إلى الحافظة.
أكثر ما يتيحه ذلك هو منح صفحات الويب إذن الوصول للقراءة بدون قيود إلى الحافظة. مزعجًا. نسخ المستخدمين المعلومات الحساسة بشكل روتيني مثل كلمات المرور التفاصيل الشخصية إلى الحافظة، والتي يمكن بعد ذلك قراءتها بواسطة أي صفحة دون معرفة المستخدم.
كما هو الحال مع العديد من واجهات برمجة التطبيقات الجديدة، لا تتوافق واجهة برمجة تطبيقات الحافظة إلا مع الصفحات التي يتم عرضها على 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 سهلة الاستخدام: عند محاولة قراءة بيانات الحافظة أو كتابتها، يتم تلقائيًا مطالبة المستخدم إذن إذا لم يتم منحه من قبل. ونظرًا لأن واجهة برمجة التطبيقات قائمة على الوعد، هذه المسألة شفافة تمامًا، ويؤدي رفض المستخدم لإذن الوصول إلى الحافظة والوعد بالرفض حتى تتمكن الصفحة من الاستجابة بشكل مناسب.
ولأنّ المتصفحات لا تسمح بالوصول إلى الحافظة إلا عندما تكون الصفحة هي علامة التبويب النشطة،
ستجد أن بعض الأمثلة هنا لا تعمل إذا تم لصقها مباشرةً في
وحدة تحكم المتصفح، نظرًا لأن أدوات المطور نفسها هي علامة التبويب النشطة. هناك خدعة: يمكنك التأجيل
إلى الحافظة باستخدام 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 كما قدم داروين العرض التوضيحي. نتوجّه بالشكر إلى كياريك ونشكرك مجددًا على "غاري كاكمارشيك" مراجعة أجزاء من هذه المقالة.
صورة رئيسية من إعداد ماركوس وينكلر على إزالة البداية