非同步剪貼簿API:解放剪貼簿操作

西樓聽雨發表於2018-03-18

本文是谷歌開發者網站近期釋出的關於非同步剪貼簿API使用方式的指導文章。

原文(需越牆)https://developers.google.com/web/updates/2018/03/clipboardapi

原文作者Jason Miller 譯者西樓聽雨
(轉載請註明出處)

在過去的幾年裡,對於與剪貼簿互動的支援,各家瀏覽器都在往 document.execCommand 聚集。雖然這種統一的、廣泛的支援是一件好事,但是這裡還有一個問題:這種剪貼簿的操作方式是同步性的,而且只能讀寫 DOM 上的內容。

同步式的複製、貼上對於短小的文字來說看上去還沒問題,但其實還有許多應用場景是會由於這種阻塞頁面的操作帶來糟糕使用者體驗的。例如,在富文字內容可以被貼上進來時,瀏覽器需要先進行耗時的內容轉換、格式清理工作,或者圖片解壓工作;而對於外部資源,瀏覽器可能還需要先進行載入——這樣就會阻塞頁面,直到資源從硬碟或者網路載入完成;如果再在這些過程中新增許可權控制,則會導致更嚴重的阻塞,因為需要先請求使用者授予應用訪問剪貼簿的許可權。

當下,與document.execCommand中剪貼簿互動相關的許可權,還沒有精細的定義,各家瀏覽器之間也不盡相同。所以,如果我們希望有一個能專門解決阻塞、許可權問題的剪貼簿 API 出現,它應該是什麼樣的呢?

新制定的“非同步剪貼簿API”就是我們想要的,它對相關許可權定義了一個優良的模型,也不會阻塞頁面,同時簡化了剪貼簿相關的事件,並且與“拖放API(Drag & Drop API)”進行了打通。這個新的 API 將在 Chrome 66 中釋出。

視訊演示:v.youku.com/v_show/id_X…

複製:向剪貼簿寫入文字

文字內容可以通過 writeText() 寫入剪貼簿。因為這個 API 是非同步式的,所以這個方法會返回一個 Promise,成功或者失敗則基於我們傳入的文字是否複製成功:

navigator.clipboard.writeText('Text to be copied')
  .then(() => {
    console.log('文字已複製到了剪貼簿');
  })
  .catch(err => {
    // 當使用者拒絕剪貼簿寫入操作的許可權請求時,就會失敗
    console.error('無法複製文字: ', err);
  });複製程式碼

我們也可以用非同步函式的方式來實現,然後通過 await 關鍵字來返回 writeText() 的結果:

async function copyPageUrl() {
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('頁面URL複製成功');
  } catch (err) {
    console.error('複製失敗: ', err);
  }
}
複製程式碼

貼上:從剪貼簿讀取文字

和複製操作非常類似,文字內容可以通過呼叫 readText() 從剪貼簿讀取:

navigator.clipboard.readText()
  .then(text => {
    console.log('剪貼簿內容: ', text);
  })
  .catch(err => {
    console.error('讀取剪貼簿失敗: ', err);
  });
複製程式碼

非同步函式實現:

async function getClipboardContents() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('剪貼簿內容: ', text);
  } catch (err) {
    console.error('讀取剪貼簿失敗: ', err);
  }
}
複製程式碼

處理貼上事件

對於剪貼簿內容的變動檢測,已經有計劃為其引入一個事件了,但是現在最好是通過"paste"事件來實現檢測,它可以和這個新的 API 很好地配合使用:

document.addEventListener('paste', event => {
  event.preventDefault();
  navigator.clipboard.getText().then(text => {
    console.log('Pasted text: ', text);
  });
});複製程式碼

安全性和許可權

剪貼簿訪問的安全性一直是瀏覽器的一個關注點,如果沒有在關鍵的地方進行恰當的許可權控制,頁面可能會不知不覺地複製各種形式的惡意內容到使用者的剪貼簿,進而帶來災難性的後果。例如,想象這樣一個Web頁面,它不知不覺地複製了“rm -rf /”這個命令,或者複製了一張“解壓炸彈圖片(decompression bomb image)”(譯註:一種攻擊形式,利用了內容重複的超大尺寸圖片壓縮後可以變得非常小的特性;因瀏覽器渲染時需要先解壓,這個過程就會耗用巨大的記憶體和效能)。

給予 Web 頁面不受限制的讀取許可權,甚至可能會更糟糕。例如,當使用者複製了敏感資訊,比如,密碼、個人資訊時,任何頁面都可以在使用者不知情的情況下進行讀取。

所以,和其他許多近來新出現的 API 一樣,navigator.clipboard 也僅僅在 HTTPS 下才可用。而且為了防止濫用,剪貼簿的訪問僅在頁面所在標籤處於啟用狀態時才被允許。其中剪貼簿的寫入在頁面標籤為啟用狀態時不需要請求許可權,而讀取則在任何情況下都需要許可權。

為了讓事情變得容易,和複製、貼上相關的兩種許可權項被新增到了許可權API(Permissions API)中。一個是 clipboard-write,頁面標籤在啟用狀態時,會自動獲得該許可權;一個是clipboard-read,該許可權可以通過嘗試從剪貼簿讀取資料來請求。

{ name: 'clipboard-read' }
{ name: 'clipboard-write' }複製程式碼

Screenshot
of the permissions prompt shown when attempting to read from the clipboard.

和其他任何使用許可權 API 的情況一樣, 可以通過它檢查你的應用是否有和剪貼簿互動的許可權:

navigator.permissions.query({
  name: 'clipboard-read'
}).then(permissionStatus => {
  // Will be 'granted', 'denied' or 'prompt':
  console.log(permissionStatus.state);

  // Listen for changes to the permission state
  permissionStatus.onchange = () => {
    console.log(permissionStatus.state);
  };
});複製程式碼

非同步剪貼簿API中的“非同步”特性有其非常方便好用的一面,例如:在使用者還沒授予許可權之前,你嘗試對剪貼簿進行讀或寫操作,會自動幫你提示使用者進行授權;又如:因為這個 API 是基於 promise 的,所以這個授權的過程是完全透明的;而且當使用者拒絕了剪貼簿訪問請求時,頁面也有機會做出相應的響應。

因為只有頁面是在啟用狀態時才可以進行剪貼簿訪問,而 Chrome 的開發者工具本身也是標籤,所以在開啟了開發者工具時,某些情況下可能就會出現不正常現象。對於這個問題,這裡有個技巧:我們可以通過 setTimeout 來推遲對剪貼簿的訪問,然後再在回撥函式執行之前,快速點選頁面,把焦點切換進去:

setTimeout(async () => {
  const text = await navigator.clipboard.readText();
  console.log(text);
}, 2000);複製程式碼

歷史回顧

在引入非同步剪貼簿 API 之前,我們會採用了一種混合了不同複製、貼上實現方法的方案,來支援各家瀏覽器。

但在大多數瀏覽器中,他們的複製、貼上操作都是通過document.execCommand('copy')document.execCommand('paste')來執行的。如果被拷貝的文字不在 DOM 中,我們還需要先手動把它注入到 DOM 中然後選擇它:

button.addEventListener('click', e => {
  const input = document.createElement('input');
  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.');
  }
})複製程式碼

對於,在不支援非同步剪貼簿API的瀏覽器中處理貼上操作,則是這樣的:

document.addEventListener('paste', e => {
  const text = e.clipboardData.getData('text/plain');
  console.log('Got pasted text: ', text);
})複製程式碼

而在 IE 中,我們可以通過 window.clipboardData 來訪問剪貼簿。而且如果是在一個使用者手勢事件——如點選事件——中訪問的,那麼許可權請求的對話方塊就不會彈出——asking permission responsibly(譯註:因為這其實相當於一種已經獲得了使用者默許的環境)。

特性檢測及備用方案

在利用非同步剪貼簿API好處的同時也支援其他不支援這個 API 的瀏覽器,是一個不錯的方案。要實現這種效果,你可以通過檢查navigator.clipboard屬性的存在性來判斷非同步剪貼API是否受支援:

document.addEventListener('paste', async e => {
  let text;
  if (navigator.clipboard) {
    text = await navigator.clipboard.readText()
  }
  else {
    text = e.clipboardData.getData('text/plain');
  }
  console.log('Got pasted text: ', text);
});
複製程式碼

接下來還有什麼

通篇下來,你可能注意到了,我們僅涉及到了 navigator.clipboard 文字操作的部分,其實,這個 API 規範還定義了一些更通用的 read() 和 write() 方法,但是實現這些方法存在複雜性和安全性問題(還記得“圖片炸彈”嗎?)。所以,Chrome 現在只釋放了這個 API 中和文字相關的一些部分。

更多資訊


相關文章