參考網頁加載完成的事件,實現chromium內部對外的js發送事件。
FrameHostMsg_DidFinishLoad
帶反饋的可參考:FrameMsg_BeforeUnload ,反饋事件:FrameHostMsg_BeforeUnload_ACK,FrameHostMsg_RunBeforeUnloadConfirm
基於electron 7:
從js addEventListener事件截獲,發往iframe。
首先新添加消息。在D:\dev\electron7\src\content\common\frame_messages.h 定義消息。
IPC_MESSAGE_ROUTED1(FrameMsg_Notify_addEventListener, bool /* is_reload */)
1,事件截獲:d:\dev\electron7\src\third_party\blink\renderer\core\dom\events\event_target.cc
bool EventTarget::AddEventListenerInternal( const AtomicString& event_type, EventListener* listener, const AddEventListenerOptionsResolved* options) { if (!listener) return false; if (event_type == event_type_names::kTouchcancel || event_type == event_type_names::kTouchend || event_type == event_type_names::kTouchmove || event_type == event_type_names::kTouchstart) { if (const LocalDOMWindow* executing_window = ExecutingWindow()) { if (const Document* document = executing_window->document()) { document->CountUse(options->passive() ? WebFeature::kPassiveTouchEventListener : WebFeature::kNonPassiveTouchEventListener); } } } V8DOMActivityLogger* activity_logger = V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld(); if (activity_logger) { Vector<String> argv; argv.push_back(ToNode() ? ToNode()->nodeName() : InterfaceName()); argv.push_back(event_type); activity_logger->LogEvent("blinkAddEventListener", argv.size(), argv.data()); } #ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content // start call { LocalDOMWindow* executing_window = ExecutingWindow(); LocalFrame* frame = executing_window->GetFrame(); Node* node = ToNode(); // String node_name = node ? node->nodeName() : InterfaceName(); if (node && node->IsElementNode()) { Element* ele = static_cast<Element*>(node); AtomicString old_id = ele->IdForStyleResolution(); frame->Client()->DispatchDidNotifyEventAdded( old_id.GetString().Utf8(), event_type.GetString().Utf8()); } /* don't send built-in addEventListener else{ frame->Client()->DispatchDidNotifyEventAdded( node_name.Utf8(), event_type.GetString().Utf8()); } */ } #endif RegisteredEventListener registered_listener; bool added = EnsureEventTargetData().event_listener_map.Add( event_type, listener, options, ®istered_listener); if (added) { AddedEventListener(event_type, registered_listener); if (IsA<JSBasedEventListener>(listener) && IsInstrumentedForAsyncStack(event_type)) { probe::AsyncTaskScheduled(GetExecutionContext(), event_type, listener->async_task_id()); } } return added;
2,發往外圍client:
d:\dev\electron7\src\third_party\blink\renderer\core\frame\local_frame_client.h
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content // ipc virtual void DispatchDidNotifyEventAdded(const std::string& node_name, const std::string& event_type) {} #endif
d:\dev\electron7\src\third_party\blink\renderer\core\exported\local_frame_client_impl.h
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content // ipc void DispatchDidNotifyEventAdded(const std::string& node_name, const std::string& event_type) override; #endif
d:\dev\electron7\src\third_party\blink\renderer\core\exported\local_frame_client_impl.cc
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content void LocalFrameClientImpl::DispatchDidNotifyEventAdded( const std::string& node_name, const std::string& event_type) { web_frame_->DidNotifyEvent(node_name,event_type); } #endif
3,傳到web層接口:
d:\dev\electron7\src\third_party\blink\public\web\web_local_frame_client.h
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content // ipc voidparam virtual void DidNotifyEventAdded(const std::string& node_name, const std::string& event_type) {} #endif
實現:d:\dev\electron7\src\third_party\blink\renderer\core\frame\web_local_frame_impl.h
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content void DidNotifyEvent(const std::string& node_name, const std::string& event_type); #endif
d:\dev\electron7\src\third_party\blink\renderer\core\frame\web_local_frame_impl.cc
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content void WebLocalFrameImpl::DidNotifyEvent(const std::string& node_name, const std::string& event_type) { if (!Client()) return; // if (WebPluginContainerImpl* plugin = GetFrame()->GetWebPluginContainer()) //zhi:todo // plugin->DidFinishLoading(); Client()->DidNotifyEventAdded(node_name,event_type);//voidparam not client's interface } #endif
4,最后到達frame的實現層,將ipc 消息發送出去:
d:\dev\electron7\src\content\renderer\render_frame_impl.h
d:\dev\electron7\src\content\renderer\render_frame_impl.cc
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content // voidParam void DidNotifyEventAdded(const std::string& node_name, const std::string& event_type) override; #endif
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH // zhibin:patch_to_content send notification void RenderFrameImpl::DidNotifyEventAdded(const std::string& node_name, const std::string& event_type) { TRACE_EVENT1("navigation,benchmark,rail", "RenderFrameImpl::DidAddEventListenerCalled", "id", routing_id_); if (!frame_->Parent()) { TRACE_EVENT_INSTANT0("WebCore,benchmark,rail", "DidAddEventListenerCalled", TRACE_EVENT_SCOPE_PROCESS); } /* for (auto& observer : observers_) observer.DidFinishLoad();//:todo */ // WebDocumentLoader* document_loader = frame_->GetDocumentLoader(); Send(new FrameHostMsg_DidAddEventListenerCalled(routing_id_, node_name, event_type)); }
5,content外層接收事件
D:\dev\electron7\src\content\browser\web_contents\web_contents_impl.h 添加事件處理函數:
#if 1//zhibin:patch ipc
void OnAddEventListenerCalled(RenderFrameHost* render_frame_host,
const GURL& url) override; #endif
D:\dev\electron7\src\content\browser\web_contents\web_contents_impl.cc
#if 1//zhibin:patch ipc
IPC_MESSAGE_HANDLER(FrameMsg_Notify_addEventListener, OnAddEventListenerCalled)
#endif
#if 1//zhibin:patch ipc
void WebContentsImpl::OnAddEventListenerCalled(RenderFrameHostImpl* source,
const GURL& url) { if (!source->GetParent()) { size_t frame_count = source->frame_tree_node()->GetFrameTreeSize(); UMA_HISTOGRAM_COUNTS_1000("Navigation.MainFrame.FrameCount", frame_count); } for (auto& observer : observers_) observer.DidAddEventListenerCalled(source, validated_url); } #endif
6,調用ovserver通知出去
D:\dev\electron7\src\content\public\browser\web_contents_observer.h 回調接口,需要被繼承實現。
virtual void DidFinishLoad(RenderFrameHost* render_frame_host,
const GURL& validated_url) {}
#if 1
virtual void DidAddEventListenerCalled(RenderFrameHost* render_frame_host,
const GURL& validated_url) {}
#endif
7,外圍electron實現:
D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_contents.cc
#if 1//zhibin:patch ipc
void WebContents::DidAddEventListenerCalled(
content::RenderFrameHost* render_frame_host, const GURL& validated_url) { bool is_main_frame = !render_frame_host->GetParent(); int frame_process_id = render_frame_host->GetProcess()->GetID(); int frame_routing_id = render_frame_host->GetRoutingID(); Emit("did-frame-finish-load", is_main_frame, frame_process_id, frame_routing_id); Emit("event-demo"); } #endif
D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_contents.h
#if 1//zhibin:patch ipc
void DidAddEventListenerCalled(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override; #endif
nodejs測試示例:
package.json
{
"name": "eventdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"event"
],
"author": "zb",
"license": "ISC"
}
index.html
<!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> <h1><a href=index.html> myself1</a></h1> <h1><a href=http://www.baidu.com>baidu</a></h1> We are using node <script>document.write(process.versions.node)</script>, Chrome <script>document.write(process.versions.chrome)</script>, and Electron <script>document.write(process.versions.electron)</script>. </body> <button id="btn">submit button</button> <script> alert("I am a alert."); document.getElementById("btn").addEventListener("click",function(){ console.log("button clicked.") },false); </script> </html>
index.js
const { dialog, app, BrowserWindow } = require('electron') function createWindow () { // 創建瀏覽器窗口 const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true } }) // 並且為你的應用加載index.html win.loadFile('index.html') // 打開開發者工具 //win.webContents.openDevTools(); win.webContents.on('will-navigate', (e, url,) => { console.log("===will-navigate==="); }); win.webContents.on('did-finish-load', function() { console.log("===============load page successfully"); // win.webContents.executeJavaScript('alert(" load finish alert ");'); }); /* win.webContents.on('event-demo', function() { console.log("==== event-demo========================="); }); */ //addEventListener事件通知 win.webContents.on('event-demo', function(event,element_id, event_name){ console.log("========================= tistar-addEventListener-event begin ========================="); console.log(event); console.log(element_id); console.log(event_name); var str='var bbb=document.getElementById("'+'btn'+'");alert(bbb.innerHTML)'; console.log(str); win.webContents.executeJavaScript(str); console.log("========================= tistar-addEventListener-event end ========================="); }); //屏蔽彈出框事件 win.webContents.on('tistar-dialog-event', function(event,dialog_type, message) { console.log("========= tistar-dialog-event begin ========="); //console.log(event); console.log(dialog_type); console.log(message); console.log("========= tistar-dialog-event end =========="); }); } // Electron會在初始化完成並且准備好創建瀏覽器窗口時調用這個方法 // 部分 API 在 ready 事件觸發后才能使用。 app.whenReady().then(createWindow) //當所有窗口都被關閉后退出 app.on('window-all-closed', () => { // 在 macOS 上,除非用戶用 Cmd + Q 確定地退出, // 否則絕大部分應用及其菜單欄會保持激活。 if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { // 在macOS上,當單擊dock圖標並且沒有其他窗口打開時, // 通常在應用程序中重新創建一個窗口。 if (BrowserWindow.getAllWindows().length === 0) { createWindow() } }) // 您可以把應用程序其他的流程寫在在此文件中 // 代碼 也可以拆分成幾個文件,然后用 require 導入。
方式二:
直接通過發送event,回調到注冊過addEventListener的方法中。這種方法實現非常簡單。
index.html更改為:
<!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> <h1><a href=index.html> myself</a></h1> <h1><a href=http://www.baidu.com>baidu</a></h1> <button id="btn">submit button</button>
Enter your name: <input type="text" id="fname" onchange="upperCase(this.id)">
<script> var ev = new CustomEvent('cust_event_add_event_listener', { bubbles: 'false', cancelable: 'false', detail: 'Tell me your story!' }); addEventListener('cust_event_add_event_listener', function (event) { var tmpobj=event.target;//.document.getElementById("btn") var str; for (var item in tmpobj){ str +=item+":"+tmpobj[item]+"\n"; } console.log(str); console.log(event.target.id); }, false); document.getElementById("btn").addEventListener("click",function(l1,l2,l3){ //alert(l1); console.log("button clicked."); dispatchEvent(ev); },false);
function upperCase(x)
{
var y=document.getElementById(x).value
document.getElementById(x).value=y.toUpperCase()
}
</script> </body> </html>
當有值改動是通知:
void Node::DispatchScopedEvent(Event& event) { #ifndef CUST_NO_EVENT_ADD_EVENT_LISTENER_NOTIFY // zhibin:call js if (event.type() == "change") { LocalDOMWindow* executing_window = GetDocument().ExecutingWindow(); Event* ce = Event::CreateBubble(event_interface_names::kCustomEvent); ce->SetType("cust_event_notify_change"); ce->SetTarget(this); ce->SetTrusted(true); executing_window->DispatchEvent(*ce, this); } #endif event.SetTrusted(true); EventDispatcher::DispatchScopedEvent(*this, event); }
在需要發出事件的地方調用:
LocalDOMWindow* executing_window = ExecutingWindow(); Node* node = ToNode(); // String node_name = node ? node->nodeName() : InterfaceName(); if (node && node->IsElementNode()) { auto* ele = DynamicTo<Element>(node); /* call to js */ CustomEvent* ce = CustomEvent::Create(); ce->SetType("cust_event_add_event_listener"); // ce->SetTarget(ele); // ce->SetCurrentTarget(this); ce->SetEventPhase(Event::kAtTarget); // ce->SetTrusted(true); executing_window->DispatchEvent(*ce,ele); }
new一個event參考代碼:
MouseEventInit* initializer = MouseEventInit::Create(); initializer->setBubbles(!is_mouse_enter_or_leave); initializer->setCancelable(!is_mouse_enter_or_leave); MouseEvent::SetCoordinatesFromWebPointerProperties( mouse_event.FlattenTransform(), target_node->GetDocument().domWindow(), initializer); UpdateMouseMovementXY(mouse_event, last_position, target_node->GetDocument().domWindow(), initializer); initializer->setButton(static_cast<int16_t>(mouse_event.button)); initializer->setButtons(MouseEvent::WebInputEventModifiersToButtons( mouse_event.GetModifiers())); initializer->setView(target_node->GetDocument().domWindow()); initializer->setComposed(true); initializer->setDetail(click_count); initializer->setRegion(canvas_region_id); initializer->setRelatedTarget(related_target); UIEventWithKeyState::SetFromWebInputEventModifiers( initializer, static_cast<WebInputEvent::Modifiers>(mouse_event.GetModifiers())); initializer->setSourceCapabilities( target_node->GetDocument().domWindow() ? target_node->GetDocument() .domWindow() ->GetInputDeviceCapabilities() ->FiresTouchEvents(mouse_event.FromTouch()) : nullptr); MouseEvent* event = MouseEvent::Create( mouse_event_type, initializer, mouse_event.TimeStamp(), mouse_event.FromTouch() ? MouseEvent::kFromTouch : MouseEvent::kRealOrIndistinguishable, mouse_event.menu_source_type);
參考:https://www.jianshu.com/p/2a2424bdc057
https://www.cnblogs.com/danxi/p/7766147.html
chromium源碼閱讀--進程間通信(IPC)
第一篇就有提到Chromium是目前默認是采用多進程架構,當然,chromium有singe-process的版本。
多進程與多線程的區別,確實有很多可以講的,我的另一篇博客也講了一些 (Linux 進程,線程),這里是從瀏覽器的角度來說,如果是多線程,如果一個線程崩潰,影響了整個瀏覽器的使用,因為在現在的網頁標准更新了很多個版本,會有不同標准的頁面在網絡上,極大可能出現解析,渲染,插件等問題,那么對於用戶來說,體驗就會差很多了,瀏覽一個頁面出問題,就要重啟瀏覽器。而多進程則可以避免此問題,render進程崩潰只會影響當前的tab。
嗯,上面說了那么多,就是為了說,多進程之間就需要進程通信來協作,而chromium的進程間通信是非常繁雜的,如何處理這個是我們需要了解的關鍵。
那么本質的問題就是:
1、發那些消息(Message Type)
2、消息通道是怎么建立的 (Message Channel)
3、發送者和接收者(Sender,Listener)
OK,咱一個個來。
一、 Message Type
主要分為2類:“routed” 和 “control”。
1、routed消息
主要是用來給某個RenderViewHost對象發送消息的。不過,任何類都可以通過GetNextRoutingID 和 AddRoute 注冊,就能接收routed消息。
2、control消息
control消息有創建pipe的類處理,當然這些類也可以接收routed消息。比如,請求資源或修改剪貼板不是特定於視圖的,所以是控制消息。
3、消息的聲明
1 IPC_MESSAGE_ROUTED2(FrameHostMsg_MyMessage, GURL, int)
這個宏用來聲明routed消息,這里聲明了一個從render進程發送到browser進程的消息,並有一個GURL參數,一個int參數
1 IPC_MESSAGE_CONTROL0(FrameMsg_MyMessage)
這個宏用來聲明control消息,這里聲明了一個從browser進程發送到render進程的消息,沒有參數。
這里還有幾個默認的約定:
(1)這些宏后面的數字表明有幾個參數,最多5個參數,即: IPC_MESSAGE_ROUTED0~IPC_MESSAGE_ROUTED5 或者 IPC_MESSAGE_CONTROL0~IPC_MESSAGE_CONTROL5
(2)消息名稱表明消息的接受者,FrameHostMsg,帶Host后綴的類,表示在browser進程接收處理的消息,FrameMsg,則表示在render進程處理的消息,如果是Plugin進程,也會帶有Plugin字樣。
二、Message Channel
chromium的使用mojo IPC,並且在官網提供了性能對比 (Times in microseconds)
Windows Z840 |
Linux Z620 |
MacBook Pro 15" 2016 |
|
IPC |
36.9 |
69.5 |
52.5 |
Mojo cross-process |
28.2 |
48 |
34.9 |
這里是官網關於mojo的一些介紹,https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md#System-Overview
從unittest看channel的創建:
1 void IPCChannelMojoTestBase::CreateChannel(IPC::Listener* listener) { 2 channel_ = 3 IPC::ChannelMojo::Create(TakeHandle(), IPC::Channel::MODE_SERVER, 4 listener, base::ThreadTaskRunnerHandle::Get()); 5 }
在IPC::ChannelMojo::Create里看到需要 IPC::ChannelMojo的構造,
1 ChannelMojo::ChannelMojo( 2 mojo::ScopedMessagePipeHandle handle, 3 Mode mode, 4 Listener* listener, 5 const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner) 6 : task_runner_(ipc_task_runner), 7 pipe_(handle.get()), 8 listener_(listener), 9 weak_factory_(this) { 10 weak_ptr_ = weak_factory_.GetWeakPtr(); 11 bootstrap_ = MojoBootstrap::Create(std::move(handle), mode, ipc_task_runner); 12 }
在MojoBootstrapImpl里完成sender和listener的綁定:
1 class MojoBootstrapImpl : public MojoBootstrap { 2 public: 3 MojoBootstrapImpl( 4 mojo::ScopedMessagePipeHandle handle, 5 const scoped_refptr<ChannelAssociatedGroupController> controller) 6 : controller_(controller), 7 associated_group_(controller), 8 handle_(std::move(handle)) {} 9 10 ~MojoBootstrapImpl() override { 11 controller_->ShutDown(); 12 } 13 14 private: 15 void Connect(mojom::ChannelAssociatedPtr* sender, 16 mojom::ChannelAssociatedRequest* receiver) override { 17 controller_->Bind(std::move(handle_)); 18 controller_->CreateChannelEndpoints(sender, receiver); 19 } 20 21 。。。 22 }
上面的mojo Channel的創建過程,linux提供的IPC比如:pipe,unix socket,share memory都不是線程安全的,mojo封裝了底層IPC細節並提供了線程安全保障,並且看上面的性能對比,mojo性能更好,這也是chromium逐漸轉用mojo的主要因素吧。
OK,上面介紹了mojo,接下來我們會發現,在進程里都是使用IPC::ChannelProxy這個類來代理完成Channel的各種工作。
這里我們只需看一個例子就能理解了,比如在browser進程的RenderProcessHost類里聲明了GetChannel接口:
1 IPC::ChannelProxy* GetChannel() = 0;
根據chromium的套路,你大致就能想到,有一個RenderProcessHostImpl類會來實現這個接口,嗯,果不其然:
1 class CONTENT_EXPORT RenderProcessHostImpl 2 : public RenderProcessHost, 3 public ChildProcessLauncher::Client, 4 public ui::GpuSwitchingObserver, 5 public mojom::RouteProvider, 6 public mojom::AssociatedInterfaceProvider, 7 public mojom::RendererHost { 8 ... 9 IPC::ChannelProxy* GetChannel() override; 10 ... 11 }
我們可以看到這里會提供一個IPC::ChannelProxy的指針,那么順着這個,ChannelProxy的創建和初始化就不遠了。
bool RenderProcessHostImpl::Init() { ... if (!channel_) InitializeChannelProxy(); ... CreateMessageFilters(); RegisterMojoInterfaces(); ... }
可以看到,上面初始化了Channel並給當前實例創建了MessageFilter和在mojo里注冊了消息發送的mojo interface。
mojo會負責將channel兩端連通,之后的消息發送就可使用IPC::ChannelProxy來完成了。
三、發送者和接收者
1、發送者
chromium里定義了IPC::Sender的接口:
1 class Message; 2 3 class IPC_EXPORT Sender { 4 public: 5 // Sends the given IPC message. The implementor takes ownership of the 6 // given Message regardless of whether or not this method succeeds. This 7 // is done to make this method easier to use. Returns true on success and 8 // false otherwise. 9 virtual bool Send(Message* msg) = 0; 10 11 protected: 12 virtual ~Sender() {} 13 };
上面的使用例子,我們可以看到 IPC::ChannelProxy 是消息的發送者,看類的聲明:
1 class IPC_EXPORT ChannelProxy : public Sender { 2 3 }
2、接收者
同樣chromium也定義Listener。
class Message; // Implemented by consumers of a Channel to receive messages. class IPC_EXPORT Listener { public: // Called when a message is received. Returns true iff the message was // handled. virtual bool OnMessageReceived(const Message& message) = 0; ... };
我們在前面提到的router,是消息接收者,也是消息發送者:
1 class IPC_EXPORT MessageRouter : public Listener, public Sender { 2 ... 3 }
還有子線程實例也是Listener:
1 class CONTENT_EXPORT ChildThreadImpl 2 : public IPC::Listener, 3 virtual public ChildThread, 4 private base::FieldTrialList::Observer, 5 public mojom::RouteProvider, 6 public mojom::AssociatedInterfaceProvider, 7 public mojom::ChildControl { 8 ... 9 }
好了,更多例子我也不舉了,chromium IPC還有更多的內容,在代碼待我們學習,這里暫時總結到這里,后續再補充。
7.Web IDL綁定
當JavaScript訪問node.firstChild時,將調用node.h中的Node :: firstChild()。它是如何工作的?我們來看看node.firstChild是如何工作的。
首先,您需要根據規范定義IDL文件:
// node.idl interface Node : EventTarget { [...] readonly attribute Node? firstChild; };
Web IDL的語法在Web IDL規范中定義。 [...]稱為IDL擴展屬性。一些IDL擴展屬性在Web IDL規范中定義,而其他屬性是特定於Blink的IDL擴展屬性。除了特定於Blink的IDL擴展屬性外,IDL文件應以特定的方式編寫(即只需從規范中復制和粘貼)。
其次,您需要為Node定義C ++類並為firstChild實現C ++ getter:
class EventTarget : public ScriptWrappable { // All classes exposed to JavaScript must inherit from ScriptWrappable. ...; }; class Node : public EventTarget { DEFINE_WRAPPERTYPEINFO(); // All classes that have IDL files must have this macro. Node* firstChild() const { return first_child_; } };
在一般情況下,就是這樣。構建node.idl時,IDL編譯器會自動為Node接口和Node.firstChild生成Blink-V8綁定。自動生成的綁定在// src / out / {Debug,Release} / gen / third_party / blink / renderer / bindings / core / v8 / v8_node.h中生成。當JavaScript調用node.firstChild時,V8在v8_node.h中調用V8Node :: firstChildAttributeGetterCallback(),然后它調用您在上面定義的Node :: firstChild()。
8.V8和Blink
8.1 Isolate, Context, World
當您編寫涉及V8 API的代碼時,了解Isolate,Context和World的概念非常重要。它們分別由代碼庫中的v8 :: Isolate,v8 :: Context和DOMWrapperWorld表示。
Isolate對應於物理線程。 Isolate : physical thread in Blink = 1 : 1。主線程有自己的隔離。工作線程有自己的隔離。
Context對應於全局對象(在Frame的情況下,它是Frame的窗口對象)。由於每個幀都有自己的窗口對象,因此渲染器進程中有多個上下文。當您調用V8 API時,您必須確保您處於正確的上下文中。否則,v8 :: Isolate :: GetCurrentContext()將返回錯誤的上下文,在最壞的情況下,它將最終泄漏對象並導致安全問題。
World是支持Chrome擴展程序內容腳本的概念。世界與Web標准中的任何內容都不對應。內容腳本希望與網頁共享DOM,但出於安全原因,必須將內容腳本的JavaScript對象與網頁的JavaScript堆隔離。 (另外一個內容腳本的JavaScript堆必須與另一個內容腳本的JavaScript堆隔離。)為了實現隔離,主線程為網頁創建一個主要世界,為每個內容腳本創建一個隔離的世界。主要世界和孤立的世界可以訪問相同的C ++ DOM對象,但它們的JavaScript對象是隔離的。通過為一個C ++ DOM對象創建多個V8包裝器來實現這種隔離。即每個世界一個V8包裝器。

Context,World和Frame之間有什么關系?
想象一下,主線上有N個世界(一個主要世界+(N - 1)個孤立的世界)。然后一個Frame應該有N個窗口對象,每個窗口對象用於一個世界。上下文是對應於窗口對象的概念。這意味着當我們有M幀和N個世界時,我們有M * N上下文(但是上下文是懶洋洋地創建的)。
對於worker,只有一個世界和一個全球對象。因此,只有一個上下文。
同樣,當您使用V8 API時,您應該非常小心使用正確的上下文。否則,您最終會在孤立的世界之間泄漏JavaScript對象並導致安全災難(例如,A.com的擴展可以操縱來自B.com的擴展)。
8.2 V8 API
在//v8/include/v8.h中定義了很多V8 API。由於V8 API是低級的並且難以正確使用,因此platform / bindings /提供了一堆包裝V8 API的輔助類。您應該考慮盡可能多地使用幫助程序類。如果您的代碼必須大量使用V8 API,那么這些文件應該放在bindings / {core,modules}中。
V8使用句柄指向V8對象。最常見的句柄是v8 :: Local <>,用於指向機器堆棧中的V8對象。在機器堆棧上分配v8 :: HandleScope后,必須使用v8 :: Local <>。不應在機器堆棧外使用v8 :: Local <>:
void function() { v8::HandleScope scope; v8::Local<v8::Object> object = ...; // This is correct. } class SomeObject : public GarbageCollected<SomeObject> { v8::Local<v8::Object> object_; // This is wrong. };
8.3 V8 wrappers
每個C ++ DOM對象(例如,Node)具有其對應的V8包裝器。准確地說,每個C ++ DOM對象每個世界都有相應的V8包裝器。
V8包裝器對其對應的C ++ DOM對象具有強引用。但是,C ++ DOM對象只有對V8包裝器的弱引用。因此,如果您希望將V8包裝器保持活動一段時間,則必須明確地執行此操作。否則,V8包裝器將過早收集,V8包裝器上的JS屬性將丟失。
div = document.getElementbyId("div"); child = div.firstChild; child.foo = "bar"; child = null; gc(); // If we don't do anything, the V8 wrapper of |firstChild| is collected by the GC. assert(div.firstChild.foo === "bar"); //...and this will fail.
如果我們不做任何事情,那么孩子會被GC收集,因此child.foo會丟失。為了使div.firstChild的V8包裝器保持活動狀態,我們必須添加一種機制,“只要div所屬的DOM樹可以從V8到達,就可以使div.firstChild的V8包裝器保持活動狀態”。
作者:JeffMony
鏈接:https://www.jianshu.com/p/2a2424bdc057
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。