clipboard 剪切板js chrom参考

 

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());
}

 

    1. 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>
View Code

 


 

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>

 

ctl+v触发粘贴事件:

js设置:

document.addEventListener('paste', this.pasteHandler, false)
    
对于粘贴内容获取:
 pasteHandler(event) {
      // 阻止默认的粘贴行为
      event.preventDefault();

      var paste = ""
      var type = 0;
      var isFile = false;

      // 获取剪贴板数据
      var clipboardData = event.clipboardData || window.clipboardData;
      var types = clipboardData.types;

      if (types) {
        console.log("all types:", types)
        if (types.indexOf('text/html') > -1) {
          // HTML content
          var htmlContent = clipboardData.getData('text/html');
          console.log('HTML content:', htmlContent);
          if (htmlContent) {
            paste = htmlContent
            type = 1;
          }
        } else {
          // Image content
          const files = clipboardData.files;
          console.log('Image content:', files);
          if (files.length > 0) {
            isFile = true;
            for (let i = 0; i < files.length; i++) {
              var reader = new FileReader();
              reader.onload = (event) => {
                const imageUrl = event.target.result;
                console.log(imageUrl); // This is the image as a data URL
                this.ws.send(keyboard.pasteText(this.lastId, 3, imageUrl))
              };
              reader.readAsDataURL(files[i]);
            }
          } else {

            // 如果没有HTML,尝试获取文本
            var plainTextContent = clipboardData.getData('text/plain');
            console.log('Plain text content pasted:', plainTextContent);
            paste = plainTextContent
            type = 0;

          }
        }
        if (!isFile && paste.length > 0)
          this.ws.send(keyboard.pasteText(this.lastId, type, paste))
        else {
          if (!isFile && paste.length < 1)
            console.log("=== No content to paste! ===")
        }
      }//types

    },

触发的事件event回传给客户端js定义在

D:\chromium110\chromium\src\third_party\blink\renderer\core\events\clipboard_event.idl

[
    Exposed=Window
] interface ClipboardEvent : Event {
    constructor(DOMString type, optional ClipboardEventInit eventInitDict = {});
    readonly attribute DataTransfer? clipboardData;
};

可以看到clipboardData是个 DataTransfer 类型对象,有个 files 属性。对应的上面js代码的访问。


[
    Exposed=Window
] interface DataTransfer {
    constructor();
    attribute DOMString dropEffect;
    attribute DOMString effectAllowed;

    [SameObject] readonly attribute DataTransferItemList items;

    void setDragImage(Element image, long x, long y);

    /* old interface */
    [CachedAttribute=hasDataStoreItemListChanged] readonly attribute FrozenArray<DOMString> types;
    DOMString getData(DOMString format);
    void setData(DOMString format, DOMString data);
    void clearData(optional DOMString format);
    [SameObject] readonly attribute FileList files;
};

ClipboardEvent生成对应的cpp代码:

D:\chromium110\chromium\src\third_party\blink\renderer\core\events\clipboard_event.h


class ClipboardEventInit;

class ClipboardEvent final : public Event {
  DEFINE_WRAPPERTYPEINFO();

 public:
  ClipboardEvent(const AtomicString& type, DataTransfer* clipboard_data);
  ClipboardEvent(const AtomicString& type, const ClipboardEventInit*);
  ~ClipboardEvent() override;

  static ClipboardEvent* Create(const AtomicString& type,
                                DataTransfer* data_transfer) {
    return MakeGarbageCollected<ClipboardEvent>(type, data_transfer);
  }

  static ClipboardEvent* Create(const AtomicString& type,
                                const ClipboardEventInit* initializer) {
    return MakeGarbageCollected<ClipboardEvent>(type, initializer);
  }

  DataTransfer* clipboardData() const { return clipboard_data_.Get(); }

  void Trace(Visitor*) const override;

 private:
  const AtomicString& InterfaceName() const override;
  bool IsClipboardEvent() const override;

  Member<DataTransfer> clipboard_data_;
};

 

posted @ 2020-07-16 14:31  Bigben  阅读(1071)  评论(0编辑  收藏  举报