clipboard api參考:
https://www.inovex.de/blog/clipboard-api/
https://w3c.github.io/clipboard-apis/
mdn的文檔:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
js promise詳解:https://www.jb51.net/article/139825.htm
有深度的示例:http://help.dottoro.com/ljwexqxl.php
clipboard js api實作
js示例代碼:
navigator.clipboard.writeText(info).then(function() {
/* clipboard successfully set */
showMsg("Success to write clipboard:"+info)
}, function() {
showMsg("Fail to write clipboard!")
/* clipboard write failed */
});
由idl自動生成的綁定代碼進入:
自動生成的綁定代碼:v8_clipboard.cc
void V8Clipboard::WriteTextMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_Clipboard_writeText");
ExecutionContext* execution_context_for_measurement = CurrentExecutionContext(info.GetIsolate());
UseCounter::Count(execution_context_for_measurement, WebFeature::kAsyncClipboardAPIWriteText);
clipboard_v8_internal::WriteTextMethod(info);
}
static void WriteTextMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
ExceptionState exception_state(info.GetIsolate(), ExceptionState::kExecutionContext, "Clipboard", "writeText");
ExceptionToRejectPromiseScope reject_promise_scope(info, exception_state);
// V8DOMConfiguration::kDoNotCheckHolder
// Make sure that info.Holder() really points to an instance of the type.
if (!V8Clipboard::HasInstance(info.Holder(), info.GetIsolate())) {
exception_state.ThrowTypeError("Illegal invocation");
return;
}
Clipboard* impl = V8Clipboard::ToImpl(info.Holder());
ScriptState* script_state = ScriptState::ForRelevantRealm(info);
if (UNLIKELY(info.Length() < 1)) {
exception_state.ThrowTypeError(ExceptionMessages::NotEnoughArguments(1, info.Length()));
return;
}
V8StringResource<> data;
data = info[0];
if (!data.Prepare(exception_state))
return;
ScriptPromise result = impl->writeText(script_state, data);
V8SetReturnValue(info, result.V8Value());
}
- idl third_party\blink\renderer\modules\clipboard\clipboard.idl:
[
SecureContext,
Exposed=Window
] interface Clipboard : EventTarget {
[MeasureAs=AsyncClipboardAPIRead,
CallWith=ScriptState,
RuntimeEnabled=AsyncClipboard
] Promise<sequence<ClipboardItem>> read();
[MeasureAs=AsyncClipboardAPIReadText,
CallWith=ScriptState
] Promise<DOMString> readText();
[MeasureAs=AsyncClipboardAPIWrite,
CallWith=ScriptState,
RuntimeEnabled=AsyncClipboard
] Promise<void> write(sequence<ClipboardItem> data);
[MeasureAs=AsyncClipboardAPIWriteText,
CallWith=ScriptState
] Promise<void> writeText(DOMString data);
};
2,cpp實作
clipboard.cc clipboard.h
通過v8 bind,調用到。比如writeText()
3,clipboard_promise.cc 實作了js promise
ScriptPromise ClipboardPromise::CreateForWriteText(ScriptState* script_state,
const String& data) {
ClipboardPromise* clipboard_promise =
MakeGarbageCollected<ClipboardPromise>(script_state);
clipboard_promise->GetTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&ClipboardPromise::HandleWriteText,
WrapPersistent(clipboard_promise), data));
return clipboard_promise->script_promise_resolver_->Promise();
}
void ClipboardPromise::HandleWriteText(const String& data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
plain_text_ = data;
CheckWritePermission(WTF::Bind(
&ClipboardPromise::HandleWriteTextWithPermission, WrapPersistent(this)));
}
這裡取檢查權限:
void ClipboardPromise::CheckWritePermission(
PermissionService::HasPermissionCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(script_promise_resolver_);
if (!IsFocusedDocument(ExecutionContext::From(script_state_))) {
script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotAllowedError, "Document is not focused."));
return;
}
if (!GetPermissionService()) {
script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotAllowedError,
"Permission Service could not connect."));
return;
}
void ClipboardPromise::HandleWriteTextWithPermission(PermissionStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (status != PermissionStatus::GRANTED) {
script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotAllowedError, "Write permission denied."));
return;
}
這裡将同步調用systemClipboard方法:
SystemClipboard::GetInstance().WritePlainText(plain_text_);
SystemClipboard::GetInstance().CommitWrite();
這裡回調js裡promise的resolve方法。(失敗的話會調 reject方法)。
script_promise_resolver_->Resolve();
}
4,剛才的同步調用對到這裡:
third_party\blink\renderer\core\clipboard\system_clipboard.cc
void SystemClipboard::WritePlainText(const String& plain_text,
SmartReplaceOption) {
// TODO(https://crbug.com/106449): add support for smart replace, which is
// currently under-specified.
String text = plain_text;
#if defined(OS_WIN)
ReplaceNewlinesWithWindowsStyleNewlines(text);
#endif
clipboard_->WriteText(NonNullString(text));
}
其中
clipboard_是個mojo遠端調用對象:mojo::Remote<mojom::blink::ClipboardHost> clipboard_;,實作對象接口在
ClipboardHost中。
5,這裡已經跨程序,運作在browser程序中。Host即表示在browser程序中。
content\browser\renderer_host\clipboard_host_impl.cc
void ClipboardHostImpl::WriteText(const base::string16& text) {
clipboard_writer_->WriteText(text);
}
std::unique_ptr<ui::ScopedClipboardWriter> clipboard_writer_;
6,進入ui\base\clipboard\scoped_clipboard_writer.cc
寫其實是壓棧:
void ScopedClipboardWriter::WriteText(const base::string16& text) {
std::string utf8_text = base::UTF16ToUTF8(text);
Clipboard::ObjectMapParams parameters;
parameters.push_back(
Clipboard::ObjectMapParam(utf8_text.begin(), utf8_text.end()));
objects_[Clipboard::ObjectType::kText] = parameters;
}
然後調commit:
void ClipboardHostImpl::CommitWrite() {
clipboard_writer_.reset(
new ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste));
}
ScopedClipboardWriter::~ScopedClipboardWriter() {
if (!objects_.empty())
Clipboard::GetForCurrentThread()->WriteObjects(buffer_, objects_);
}
往下進入win移植層:clipboard_win.cc
void ClipboardWin::WriteObjects(ClipboardBuffer buffer,
const ObjectMap& objects) {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
::EmptyClipboard();
for (const auto& object : objects)
DispatchObject(object.first, object.second);
}
void ClipboardWin::WriteText(const char* text_data, size_t text_len) {
base::string16 text;
base::UTF8ToUTF16(text_data, text_len, &text);
HGLOBAL glob = CreateGlobalData(text);
WriteToClipboard(CF_UNICODETEXT, glob);
}
對于js,promise有 reject,resolve回調,對應cpp中為:
promise被拒:
script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotAllowedError, "Document is not focused."));
在clipboard寫資料後,添加回調通知:
{
ExecutionContext *context= ExecutionContext::From(script_state_);
Document* doc = To<Document>(context);
LocalDOMWindow* executing_window = doc->ExecutingWindow();
/* call to js */
Event* ce = Event::CreateBubble(event_type_names::kCopy);
//(event_interface_names::kCustomEvent);
// ce->SetType("copy");
executing_window->DispatchEvent(*ce, NULL);
}
js代碼:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>我的首頁 -</title>
<script type="application/javascript" src="jquery.js"></script>
<style>
.designBtn{
margin-bottom: 20px;
}
.button{
background-color: #00a4ff;
color: #fff;
display: inline-block;
border-color: #008aff;
padding: 6px 3px;
}
.button:hover{
background-color: #0074ff;
}
</style>
</head>
<body>
<div class="designBtn">
<a href="${response.encodeURL(ctx+'/user/logout')}" >設計</a>
<a href="http://www.163.com" class="sheji" onclick="design(event)">設計</a>
</div>
<input type="text" name="info" id="info">
<div class="button" >Copy</div>
<div class="view">
</div>
<script>
function getclipcontent(){
navigator.clipboard.readText().then(
clipText => {
console.log(clipText);
showMsg("Success to write clipboard:"+clipText)
}
);
}
navigator.clipboard.addEventListener('clipboardchange', function (event) {
console.log("clipboardchange un impl");
navigator.clipboard.readText().then(
clipText => {
console.log(clipText);
}
);;
});
navigator.clipboard.addEventListener('copy', function (event) {
console.log("clipboard listener copy");
//self.setTimeout("getclipcontent()",1);//delay
getclipcontent();
;
});
addEventListener('copy', (e) => {
console.log("window listener copy");
//self.setTimeout("getclipcontent()",1);//delay
getclipcontent();
});
document.addEventListener('copy', (e) => {
console.log("document copy listener");
e.preventDefault();
e.clipboardData.setData('text/plain', 'Hello World');
});
var clip={
action:0,//0write,1 paste
content:null,//message
}
function handlePermission() {
navigator.permissions.query({name:'clipboard-write'}).then(replyPermission);
navigator.permissions.query({name:'clipboard-read'}).then(replyPermission);
}
function replyPermission(result){
showMsg("permission:"+result.state);
if (result.state == 'granted') {
// report(result.state);
// geoBtn.style.display = 'none';
} else if (result.state == 'prompt') {
// report(result.state);
// navigator.geolocation.getCurrentPosition(revealPosition,positionDenied,geoSettings);
} else if (result.state == 'denied') {
// report(result.state);
// geoBtn.style.display = 'inline';
showMsg("You've prevented from copy or paste!")
}
result.onchange = function() {
showMsg("permission:"+result.state);
}
}
$(".button").bind('click',function(e){
var info=$("#info").val();
navigator.clipboard.writeText(info).then(function() {
navigator.clipboard.readText().then(
clipText => {
/* clipboard successfully set */
showMsg("Success to write clipboard:"+clipText)
}
);
}, function() {
showMsg("Fail to write clipboard!")
/* clipboard write failed */
});
$(".view").append("click!")
})
function showMsg(msg){
$(".view").append(msg+"<br>");
}
handlePermission();
</script>
</body>
</html>
View Code
代碼2:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
</head>
<body >
<input type="text" id="contents" />
<input type="button" onClick="copy0()" value="複制" />
<input type="button" onClick="setClipboard()" value="複制剪切闆" />
<script type="text/javascript">
function copy0(){
var e=document.getElementById("contents");//對象是contents
e.select(); //選擇對象
nice=document.execCommand("Copy"); //執行浏覽器複制指令
if(nice){
alert('複制内容成功');
}
}
function setClipboard() {
let data = new DataTransfer();
data.items.add("text/plain", "abcd");
navigator.clipboard.write(data).then(function() {
/* success */
console.log("copy success.")
}, function() {
/* failure */
console.log("copy failure.")
});
}
function oncopy1(){
navigator.clipboard.readText().then(
clipText => {
//document.querySelector(".cliptext0").innerText = "aaaaaaaaaaaa";
console.log(clipText);
}
);;
}
addEventListener('copy', function (event) {
navigator.clipboard.readText().then(
clipText => {
console.log(clipText);
}
);;
});
</script>
</body>
</html>
js:
const copy = document.getElementById('copyme');
copy.addEventListener('click', copyToClipboard);
function copyToClipboard() {
// set new clipboard data
function setData(e) {
e.preventDefault();
e.clipboardData.setData('text/plain', 'Hello, world!');
document.removeEventListener('copy', setData);
};
document.addEventListener('copy', setData);
let result;
try {
// execute copy command
document.execCommand('copy');
result = 'Copied';
} catch (err) {
console.log(err);
result = 'Unable to copy';
}
// show whether copy was successfull
document.getElementById('result').innerHTML = result;
setTimeout(() => document.getElementById('result').innerHTML = '', 3000);
};
<a href="https://w3c.github.io/clipboard-apis/#clipboard-event-api">
<h3>Clipboard Event API (synchronous)</h3>
</a>
<button id="copyme">
Copy Me!
</button>
<br/>
<div id="result"></div>
<br/>
<hr/><br/> Try to paste 😉 <br/>
<textarea rows="4" cols="40"></textarea>