Chromium 中chrome.contextMenus扩展接口实现分析c++

一、chrome.contextMenus

使用 chrome.contextMenus API 向 Google Chrome 的上下文菜单中添加项。您可以选择从右键菜单中添加的对象类型,例如图片、超链接和页面。

权限

contextMenus

您必须在扩展程序的清单中声明 "contextMenus" 权限,才能使用该 API。此外, 您应指定一个 16 x 16 像素的图标,显示在菜单项旁边。例如:

{"name": "My extension",..."permissions": ["contextMenus"],"icons": {"16": "icon-bitty.png","48": "icon-small.png","128": "icon-large.png"},...
}

概念和用法

上下文菜单项可以出现在任何文档(或文档中的框架)中,甚至是那些带有 file:// 的菜单项 或 chrome:// 网址。要控制您的内容可以显示在哪些文档中,请指定 documentUrlPatterns 字段。create()update()

您可以根据需要创建任意数量的上下文菜单项,但如果扩展程序中的多个菜单项 则 Google Chrome 会自动将它们收起为一个父级菜单。

示例

若要试用此 API,请从 chrome-extension-samples 安装 contextMenus API 示例 存储库

1、manifest.json

{"name": "Context Menus Sample","description": "Uses the chrome.contextMenus API to customize the context menu.","version": "0.7","permissions": ["contextMenus"],"background": {"service_worker": "sample.js"},"manifest_version": 3
}

2、sample.js

// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.// A generic onclick callback function.
chrome.contextMenus.onClicked.addListener(genericOnClick);// A generic onclick callback function.
function genericOnClick(info) {switch (info.menuItemId) {case 'radio':// Radio item functionconsole.log('Radio item clicked. Status:', info.checked);break;case 'checkbox':// Checkbox item functionconsole.log('Checkbox item clicked. Status:', info.checked);break;default:// Standard context menu item functionconsole.log('Standard context menu item clicked.');}
}
chrome.runtime.onInstalled.addListener(function () {// Create one test item for each context type.let contexts = ['page','selection','link','editable','image','video','audio'];for (let i = 0; i < contexts.length; i++) {let context = contexts[i];let title = "Test '" + context + "' menu item";chrome.contextMenus.create({title: title,contexts: [context],id: context});}// Create a parent item and two children.let parent = chrome.contextMenus.create({title: 'Test parent item',id: 'parent'});chrome.contextMenus.create({title: 'Child 1',parentId: parent,id: 'child1'});chrome.contextMenus.create({title: 'Child 2',parentId: parent,id: 'child2'});// Create a radio item.chrome.contextMenus.create({title: 'radio',type: 'radio',id: 'radio'});// Create a checkbox item.chrome.contextMenus.create({title: 'checkbox',type: 'checkbox',id: 'checkbox'});// Intentionally create an invalid item, to show off error checking in the// create callback.chrome.contextMenus.create({ title: 'Oops', parentId: 999, id: 'errorItem' },function () {if (chrome.runtime.lastError) {console.log('Got expected error: ' + chrome.runtime.lastError.message);}});
});

 二、context_menus接口定义:

   1、chrome\common\extensions\api\context_menus.json

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.[{"namespace": "contextMenus","description": "Use the <code>chrome.contextMenus</code> API to add items to Google Chrome's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.","properties": {"ACTION_MENU_TOP_LEVEL_LIMIT": {"value": 6,"description": "The maximum number of top level extension items that can be added to an extension action context menu. Any items beyond this limit will be ignored."}},"types": [{"id": "ContextType","type": "string","enum": ["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio", "launcher", "browser_action", "page_action", "action"],"description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'launcher'. The 'launcher' context is only supported by apps and is used to add menu items to the context menu that appears when clicking the app icon in the launcher/taskbar/dock/etc. Different platforms might put limitations on what is actually supported in a launcher context menu."},{"id": "ItemType","type": "string","enum": ["normal", "checkbox", "radio", "separator"],"description": "The type of menu item."},{"id": "OnClickData","type": "object","description": "Information sent when a context menu item is clicked.","properties": {"menuItemId": {"choices": [{ "type": "integer" },{ "type": "string" }],"description": "The ID of the menu item that was clicked."},"parentMenuItemId": {"choices": [{ "type": "integer" },{ "type": "string" }],"optional": true,"description": "The parent ID, if any, for the item clicked."},"mediaType": {"type": "string","optional": true,"description": "One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements."},"linkUrl": {"type": "string","optional": true,"description": "If the element is a link, the URL it points to."},"srcUrl": {"type": "string","optional": true,"description": "Will be present for elements with a 'src' URL."},"pageUrl": {"type": "string","optional": true,"description": "The URL of the page where the menu item was clicked. This property is not set if the click occured in a context where there is no current page, such as in a launcher context menu."},"frameUrl": {"type": "string","optional": true,"description": " The URL of the frame of the element where the context menu was clicked, if it was in a frame."},"frameId": {"type": "integer","optional": true,"description": " The <a href='webNavigation#frame_ids'>ID of the frame</a> of the element where the context menu was clicked, if it was in a frame."},"selectionText": {"type": "string","optional": true,"description": "The text for the context selection, if any."},"editable": {"type": "boolean","description": "A flag indicating whether the element is editable (text input, textarea, etc.)."},"wasChecked": {"type": "boolean","optional": true,"description": "A flag indicating the state of a checkbox or radio item before it was clicked."},"checked": {"type": "boolean","optional": true,"description": "A flag indicating the state of a checkbox or radio item after it is clicked."}}}],"functions": [{"name": "create","type": "function","description": "Creates a new context menu item. If an error occurs during creation, it may not be detected until the creation callback fires; details will be in $(ref:runtime.lastError).","returns": {"choices": [{ "type": "integer" },{ "type": "string" }],"description": "The ID of the newly created item."},"parameters": [{"type": "object","name": "createProperties","properties": {"type": {"$ref": "ItemType","optional": true,"description": "The type of menu item. Defaults to <code>normal</code>."},"id": {"type": "string","optional": true,"description": "The unique ID to assign to this item. Mandatory for event pages. Cannot be the same as another ID for this extension."},"title": {"type": "string","optional": true,"description": "The text to display in the item; this is <em>required</em> unless <code>type</code> is <code>separator</code>. When the context is <code>selection</code>, use <code>%s</code> within the string to show the selected text. For example, if this parameter's value is \"Translate '%s' to Pig Latin\" and the user selects the word \"cool\", the context menu item for the selection is \"Translate 'cool' to Pig Latin\"."},"checked": {"type": "boolean","optional": true,"description": "The initial state of a checkbox or radio button: <code>true</code> for selected, <code>false</code> for unselected. Only one radio button can be selected at a time in a given group."},"contexts": {"type": "array","items": {"$ref": "ContextType"},"minItems": 1,"optional": true,"description": "List of contexts this menu item will appear in. Defaults to <code>['page']</code>."},"visible": {"type": "boolean","optional": true,"description": "Whether the item is visible in the menu."},"onclick": {"type": "function","optional": true,"description": "A function that is called back when the menu item is clicked. This is not available inside of a service worker; instead, they should register a listener for $(ref:contextMenus.onClicked).","parameters": [{"name": "info","$ref": "OnClickData","description": "Information about the item clicked and the context where the click happened."},{"name": "tab","$ref": "tabs.Tab","description": "The details of the tab where the click took place. This parameter is not present for platform apps."}]},"parentId": {"choices": [{ "type": "integer" },{ "type": "string" }],"optional": true,"description": "The ID of a parent menu item; this makes the item a child of a previously added item."},"documentUrlPatterns": {"type": "array","items": {"type": "string"},"optional": true,"description": "Restricts the item to apply only to documents or frames whose URL matches one of the given patterns. For details on pattern formats, see <a href='match_patterns'>Match Patterns</a>."},"targetUrlPatterns": {"type": "array","items": {"type": "string"},"optional": true,"description": "Similar to <code>documentUrlPatterns</code>, filters based on the <code>src</code> attribute of <code>img</code>, <code>audio</code>, and <code>video</code> tags and the <code>href</code> attribute of <code>a</code> tags."},"enabled": {"type": "boolean","optional": true,"description": "Whether this context menu item is enabled or disabled. Defaults to <code>true</code>."}}},{"type": "function","name": "callback","optional": true,"description": "Called when the item has been created in the browser. If an error occurs during creation, details will be available in $(ref:runtime.lastError).","parameters": []}]},{"name": "update","type": "function","description": "Updates a previously created context menu item.","parameters": [{"choices": [{ "type": "integer" },{ "type": "string" }],"name": "id","description": "The ID of the item to update."},{"type": "object","name": "updateProperties","description": "The properties to update. Accepts the same values as the $(ref:contextMenus.create) function.",// We need to preserve null because we use it as an indication to clear the callback."preserveNull": true,"properties": {"type": {"$ref": "ItemType","optional": true},"title": {"type": "string","optional": true},"checked": {"type": "boolean","optional": true},"contexts": {"type": "array","items": {"$ref": "ContextType"},"minItems": 1,"optional": true},"visible": {"type": "boolean","optional": true,"description": "Whether the item is visible in the menu."},"onclick": {"type": "function","optional": true,"parameters": [{"name": "info","$ref": "OnClickData"},{"name": "tab","$ref": "tabs.Tab","description": "The details of the tab where the click took place. This parameter is not present for platform apps."}]},"parentId": {"choices": [{ "type": "integer" },{ "type": "string" }],"optional": true,"description": "The ID of the item to be made this item's parent. Note: You cannot set an item to become a child of its own descendant."},"documentUrlPatterns": {"type": "array","items": {"type": "string"},"optional": true},"targetUrlPatterns": {"type": "array","items": {"type": "string"},"optional": true},"enabled": {"type": "boolean","optional": true}}},{"type": "function","name": "callback","optional": true,"parameters": [],"description": "Called when the context menu has been updated."}]},{"name": "remove","type": "function","description": "Removes a context menu item.","parameters": [{"choices": [{ "type": "integer" },{ "type": "string" }],"name": "menuItemId","description": "The ID of the context menu item to remove."},{"type": "function","name": "callback","optional": true,"parameters": [],"description": "Called when the context menu has been removed."}]},{"name": "removeAll","type": "function","description": "Removes all context menu items added by this extension.","parameters": [{"type": "function","name": "callback","optional": true,"parameters": [],"description": "Called when removal is complete."}]}],"events": [{"name": "onClicked","type": "function","description": "Fired when a context menu item is clicked.","parameters": [{"name": "info","$ref": "OnClickData","description": "Information about the item clicked and the context where the click happened."},{"name": "tab","$ref": "tabs.Tab","description": "The details of the tab where the click took place. If the click did not take place in a tab, this parameter will be missing.","optional": true}]}]}
]

out\Debug\gen\chrome\common\extensions\api\context_menus.h

out\Debug\gen\chrome\common\extensions\api\context_menus.cc

 三、context_menus接口实现:

chrome\browser\extensions\api\context_menus\context_menus_api.h

chrome\browser\extensions\api\context_menus\context_menus_api.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.#ifndef CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_H_#include "extensions/browser/extension_function.h"namespace extensions {class ContextMenusCreateFunction : public ExtensionFunction {public:DECLARE_EXTENSION_FUNCTION("contextMenus.create", CONTEXTMENUS_CREATE)protected:~ContextMenusCreateFunction() override {}// ExtensionFunction:ResponseAction Run() override;
};class ContextMenusUpdateFunction : public ExtensionFunction {public:DECLARE_EXTENSION_FUNCTION("contextMenus.update", CONTEXTMENUS_UPDATE)protected:~ContextMenusUpdateFunction() override {}// ExtensionFunction:ResponseAction Run() override;
};class ContextMenusRemoveFunction : public ExtensionFunction {public:DECLARE_EXTENSION_FUNCTION("contextMenus.remove", CONTEXTMENUS_REMOVE)protected:~ContextMenusRemoveFunction() override {}// ExtensionFunction:ResponseAction Run() override;
};class ContextMenusRemoveAllFunction : public ExtensionFunction {public:DECLARE_EXTENSION_FUNCTION("contextMenus.removeAll", CONTEXTMENUS_REMOVEALL)protected:~ContextMenusRemoveAllFunction() override {}// ExtensionFunction:ResponseAction Run() override;
};}  // namespace extensions#endif  // CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_H_

四、 扩展进程与主进程通信接口定义:

    1、extensions\common\mojom\service_worker_host.mojom

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.module extensions.mojom;import "mojo/public/mojom/base/unguessable_token.mojom";
import "mojo/public/mojom/base/uuid.mojom";
import "extensions/common/mojom/event_dispatcher.mojom";
import "extensions/common/mojom/extra_response_data.mojom";
import "extensions/common/mojom/frame.mojom";
import "mojo/public/mojom/base/values.mojom";
import "extensions/common/mojom/message_port.mojom";
import "url/mojom/url.mojom";// An interface for an extension service worker context. Implemented in the
// browser process.
interface ServiceWorkerHost {// Tells the browser that an extension service worker context was initialized,// but possibly didn't start executing its top-level JavaScript.DidInitializeServiceWorkerContext(string extension_id,int64 service_worker_version_id,int32 worker_thread_id,pending_associated_remote<EventDispatcher> event_dispatcher);// Tells the browser that an extension service worker context has started and// finished executing its top-level JavaScript.// Start corresponds to EmbeddedWorkerInstance::OnStarted notification.//// TODO(crbug.com/1422440): This is a workaround: ideally this IPC should be// redundant because it directly corresponds to// EmbeddedWorkerInstance::OnStarted message. However, because OnStarted// message is on different mojo IPC pipe, and most extension IPCs are on// legacy IPC pipe, this IPC is necessary to ensure FIFO ordering of this// message with rest of the extension IPCs.// Two possible solutions to this://   - Associate extension IPCs with Service Worker IPCs. This can be done//     (and will be a requirement) when extension IPCs are moved to mojo, but//     requires resolving or defining ordering dependencies amongst the//     extension messages, and any additional messages in Chrome.//   - Make Service Worker IPCs channel-associated so that there's FIFO//     guarantee between extension IPCs and Service Worker IPCs. This isn't//     straightforward as it changes SW IPC ordering with respect of rest of//     Chrome.// See https://crbug.com/879015#c4 for details.DidStartServiceWorkerContext(string extension_id,mojo_base.mojom.UnguessableToken activation_token,url.mojom.Url service_worker_scope,int64 service_worker_version_id,int32 worker_thread_id);// Tells the browser that an extension service worker context has been// destroyed.DidStopServiceWorkerContext(string extension_id,mojo_base.mojom.UnguessableToken activation_token,url.mojom.Url service_worker_scope,int64 service_worker_version_id,int32 worker_thread_id);// A service worker thread sends this message when an extension service worker// starts an API request. We use [UnlimitedSize] here because `params` may be// large with some extension function (ex. Storage API).[UnlimitedSize]RequestWorker(RequestParams params)=> (bool success,mojo_base.mojom.ListValue response_wrapper,string error,ExtraResponseData? extra_data);// Optional Ack message sent to the browser to notify that the response to a// function has been processed.// The `request_uuid` is the UUID of the extension function.WorkerResponseAck(mojo_base.mojom.Uuid request_uuid);// Open a channel to all listening contexts owned by the extension with// the given ID.OpenChannelToExtension(extensions.mojom.ExternalConnectionInfo info,extensions.mojom.ChannelType channel_type,string channel_name, extensions.mojom.PortId port_id,pending_associated_remote<extensions.mojom.MessagePort> port,pending_associated_receiver<extensions.mojom.MessagePortHost> port_host);// Get a port handle to the native application.  The handle can be used for// sending messages to the extension.OpenChannelToNativeApp(string native_app_name, extensions.mojom.PortId port_id,pending_associated_remote<extensions.mojom.MessagePort> port,pending_associated_receiver<extensions.mojom.MessagePortHost> port_host);// Get a port handle to the given tab.  The handle can be used for sending// messages to the extension.OpenChannelToTab(int32 tab_id, int32 frame_id, string? document_id,extensions.mojom.ChannelType channel_type,string channel_name, extensions.mojom.PortId port_id,pending_associated_remote<extensions.mojom.MessagePort> port,pending_associated_receiver<extensions.mojom.MessagePortHost> port_host);
};

out\Debug\gen\extensions\common\mojom\service_worker_host.mojom.cc

out\Debug\gen\extensions\common\mojom\service_worker_host.mojom.h 

//发送
void ServiceWorkerHostProxy::RequestWorker(::extensions::mojom::RequestParamsPtr in_params, RequestWorkerCallback callback) {
#if BUILDFLAG(MOJO_TRACE_ENABLED)TRACE_EVENT1("mojom", "Send extensions::mojom::ServiceWorkerHost::RequestWorker", "input_parameters",[&](perfetto::TracedValue context){auto dict = std::move(context).WriteDictionary();perfetto::WriteIntoTracedValueWithFallback(dict.AddItem("params"), in_params,"<value of type ::extensions::mojom::RequestParamsPtr>");});
#endifconst bool kExpectsResponse = true;const bool kIsSync = false;const bool kAllowInterrupt = true;const bool is_urgent = false;const uint32_t kFlags =((kExpectsResponse) ? mojo::Message::kFlagExpectsResponse : 0) |((kIsSync) ? mojo::Message::kFlagIsSync : 0) |((kAllowInterrupt) ? 0 : mojo::Message::kFlagNoInterrupt) |((is_urgent) ? mojo::Message::kFlagIsUrgent : 0);mojo::Message message(internal::kServiceWorkerHost_RequestWorker_Name, kFlags, 0, 0,MOJO_CREATE_MESSAGE_FLAG_UNLIMITED_SIZE, nullptr);mojo::internal::MessageFragment<::extensions::mojom::internal::ServiceWorkerHost_RequestWorker_Params_Data> params(message);params.Allocate();mojo::internal::MessageFragment<typename decltype(params->params)::BaseType> params_fragment(params.message());mojo::internal::Serialize<::extensions::mojom::RequestParamsDataView>(in_params, params_fragment);params->params.Set(params_fragment.is_null() ? nullptr : params_fragment.data());MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(params->params.is_null(),mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,"null params in ServiceWorkerHost.RequestWorker request");#if defined(ENABLE_IPC_FUZZER)message.set_interface_name(ServiceWorkerHost::Name_);message.set_method_name("RequestWorker");
#endifstd::unique_ptr<mojo::MessageReceiver> responder(new ServiceWorkerHost_RequestWorker_ForwardToCallback(std::move(callback)));::mojo::internal::SendMojoMessage(*receiver_, message, std::move(responder));
}//响应消息处理ServiceWorkerHost::RequestWorkerCallback callback =ServiceWorkerHost_RequestWorker_ProxyToResponder::CreateCallback(*message, std::move(responder));// A null |impl| means no implementation was bound.DCHECK(impl);impl->RequestWorker(
std::move(p_params), std::move(callback));

2、service_worker_host.mojom 接口主进程实现:

extensions\browser\service_worker\service_worker_host.cc

extensions\browser\service_worker\service_worker_host.h

实现 void RequestWorker(mojom::RequestParamsPtr params, RequestWorkerCallback callback)等接口函数。具体如下:

class GURL;namespace base {
class UnguessableToken;
}namespace content {
class BrowserContext;
class RenderProcessHost;
}  // namespace contentnamespace extensions {class ExtensionFunctionDispatcher;// This class is the host of service worker execution context for extension
// in the renderer process. Lives on the UI thread.
class ServiceWorkerHost :
#if !BUILDFLAG(ENABLE_EXTENSIONS_LEGACY_IPC)public PermissionsManager::Observer,
#endifpublic mojom::ServiceWorkerHost,public content::RenderProcessHostObserver {public:explicit ServiceWorkerHost(content::RenderProcessHost* render_process_host,mojo::PendingAssociatedReceiver<mojom::ServiceWorkerHost> receiver);ServiceWorkerHost(const ServiceWorkerHost&) = delete;ServiceWorkerHost& operator=(const ServiceWorkerHost&) = delete;~ServiceWorkerHost() override;#if !BUILDFLAG(ENABLE_EXTENSIONS_LEGACY_IPC)static ServiceWorkerHost* GetWorkerFor(const WorkerId& worker);
#endifstatic void BindReceiver(int render_process_id,mojo::PendingAssociatedReceiver<mojom::ServiceWorkerHost> receiver);// mojom::ServiceWorkerHost:void DidInitializeServiceWorkerContext(const ExtensionId& extension_id,int64_t service_worker_version_id,int worker_thread_id,mojo::PendingAssociatedRemote<mojom::EventDispatcher> event_dispatcher)override;void DidStartServiceWorkerContext(const ExtensionId& extension_id,const base::UnguessableToken& activation_token,const GURL& service_worker_scope,int64_t service_worker_version_id,int worker_thread_id) override;void DidStopServiceWorkerContext(const ExtensionId& extension_id,const base::UnguessableToken& activation_token,const GURL& service_worker_scope,int64_t service_worker_version_id,int worker_thread_id) override;void RequestWorker(mojom::RequestParamsPtr params,RequestWorkerCallback callback) override;void WorkerResponseAck(const base::Uuid& request_uuid) override;void OpenChannelToExtension(extensions::mojom::ExternalConnectionInfoPtr info,extensions::mojom::ChannelType channel_type,const std::string& channel_name,const PortId& port_id,mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>port_host) override;void OpenChannelToNativeApp(const std::string& native_app_name,const PortId& port_id,mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>port_host) override;void OpenChannelToTab(int32_t tab_id,int32_t frame_id,const std::optional<std::string>& document_id,extensions::mojom::ChannelType channel_type,const std::string& channel_name,const PortId& port_id,mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>port_host) override;#if !BUILDFLAG(ENABLE_EXTENSIONS_LEGACY_IPC)// PermissionManager::Observer overrides.void OnExtensionPermissionsUpdated(const Extension& extension,const PermissionSet& permissions,PermissionsManager::UpdateReason reason) override;// Returns the mojo channel to the service worker. It may be null// if the service worker doesn't have a live service worker matching// the version id.mojom::ServiceWorker* GetServiceWorker();mojo::AssociatedReceiver<mojom::ServiceWorkerHost>& receiver_for_testing() {return receiver_;}
#endif// content::RenderProcessHostObserver implementation.void RenderProcessExited(content::RenderProcessHost* host,const content::ChildProcessTerminationInfo& info) override;private:// Returns the browser context associated with the render process this// `ServiceWorkerHost` belongs to.content::BrowserContext* GetBrowserContext();void RemoteDisconnected();// Destroys this instance by removing it from the ServiceWorkerHostList.void Destroy();// This is safe because ServiceWorkerHost is tied to the life time of// RenderProcessHost.const raw_ptr<content::RenderProcessHost> render_process_host_;std::unique_ptr<ExtensionFunctionDispatcher> dispatcher_;mojo::AssociatedReceiver<mojom::ServiceWorkerHost> receiver_{this};
#if !BUILDFLAG(ENABLE_EXTENSIONS_LEGACY_IPC)mojo::AssociatedRemote<mojom::ServiceWorker> remote_;WorkerId worker_id_;base::ScopedObservation<PermissionsManager, PermissionsManager::Observer>permissions_observer_{this};
#endif
};}  // namespace extensions#endif  // EXTENSIONS_BROWSER_SERVICE_WORKER_SERVICE_WORKER_HOST_H_

五、加载测试扩展看下调用堆栈:

1、加载扩展看下chrome.contextMenus.create调用过程:

2、扩展进程ID=27348,主进程ID=20348 

  2.1)、扩展进程调用NativeExtensionBindingsSystem::SendRequest函数。

      src\extensions\renderer\native_extension_bindings_system.cc

在SendRequest函数里面构建函数名字和参数之后调用ipc_message_sender_->SendRequestIPC

->ServiceWorkerHostProxy::RequestWorker发送给主进程ID=20348

void NativeExtensionBindingsSystem::SendRequest(std::unique_ptr<APIRequestHandler::Request> request,v8::Local<v8::Context> context) {ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);CHECK_NE(mojom::ContextType::kUnspecified, script_context->context_type())<< "Attempting to send a request from an unspecified context type. "<< "Request: " << request->method_name<< ", Context: " << script_context->GetDebugString();TRACE_RENDERER_EXTENSION_EVENT("NativeExtensionBindingsSystem::SendRequest",script_context->GetExtensionID());GURL url;blink::WebLocalFrame* frame = script_context->web_frame();if (frame && !frame->GetDocument().IsNull())url = frame->GetDocument().Url();elseurl = script_context->url();auto params = mojom::RequestParams::New();params->name = request->method_name;params->arguments = std::move(request->arguments_list);params->extension_id = script_context->GetExtensionID();params->source_url = url;params->context_type = script_context->context_type();params->request_id = request->request_id;params->has_callback = request->has_async_response_handler;params->user_gesture = request->has_user_gesture;// The IPC sender will update these members, if appropriate.params->worker_thread_id = kMainThreadId;params->service_worker_version_id =blink::mojom::kInvalidServiceWorkerVersionId;CHECK_NE(mojom::ContextType::kUnspecified, script_context->context_type())<< script_context->GetDebugString();ipc_message_sender_->SendRequestIPC(script_context, std::move(params));
}

2.2)、主进程ID=20348 在ServiceWorkerHost::RequestWorker响应扩展进程发送的mojom消息:

2.3)、主进程在extension_function_dispatcher.cc

ExtensionFunctionDispatcher::DispatchWithCallbackInternal()函数里面分发扩展处理:

   1、根据扩展ID和参数构建扩展函数:

          scoped_refptr<ExtensionFunction> function = CreateExtensionFunction(
     params, extension, render_process_id, is_worker_request,
     render_frame_host_url, params.context_type,
     ExtensionAPI::GetSharedInstance(), std::move(callback),
     render_frame_host);
 if (!function.get())
   return;

function数据如下图:

  2、调用     base::ElapsedTimer timer;
    function->RunWithValidation().Execute();执行

void ExtensionFunctionDispatcher::DispatchWithCallbackInternal(const mojom::RequestParams& params,content::RenderFrameHost* render_frame_host,content::RenderProcessHost& render_process_host,ExtensionFunction::ResponseCallback callback) {ProcessMap* process_map = ProcessMap::Get(browser_context_);if (!process_map) {constexpr char kProcessNotFound[] ="The process for the extension is not found.";ResponseCallbackOnError(std::move(callback), ExtensionFunction::FAILED,kProcessNotFound);return;}const int render_process_id = render_process_host.GetID();const GURL* render_frame_host_url = nullptr;if (render_frame_host) {render_frame_host_url = &render_frame_host->GetLastCommittedURL();DCHECK_EQ(render_process_id, render_frame_host->GetProcess()->GetID());}ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);const Extension* extension =registry->enabled_extensions().GetByID(params.extension_id);// Check if the call is from a hosted app. Hosted apps can only make call from// render frames, so we can use `render_frame_host_url`.// TODO(devlin): Isn't `params.extension_id` still populated for hosted app// calls?if (!extension && render_frame_host_url) {extension = registry->enabled_extensions().GetHostedAppByURL(*render_frame_host_url);}if (!process_map->CanProcessHostContextType(extension, render_process_host,params.context_type)) {// TODO(https://crbug.com/1186557): Ideally, we'd be able to mark some// of these as bad messages. We can't do that in all cases because there// are times some of these might legitimately fail (for instance, during// extension unload), but there are others that should never, ever happen// (privileged extension contexts in web processes).static constexpr char kInvalidContextType[] ="Invalid context type provided.";ResponseCallbackOnError(std::move(callback), ExtensionFunction::FAILED,kInvalidContextType);return;}if (params.context_type == mojom::ContextType::kUntrustedWebUi) {// TODO(https://crbug.com/1435575): We should, at minimum, be using an// origin here. It'd be even better if we could have a more robust way of// checking that a process can host untrusted webui.if (extension || !render_frame_host_url ||!render_frame_host_url->SchemeIs(content::kChromeUIUntrustedScheme)) {constexpr char kInvalidWebUiUntrustedContext[] ="Context indicated it was untrusted webui, but is invalid.";ResponseCallbackOnError(std::move(callback), ExtensionFunction::FAILED,kInvalidWebUiUntrustedContext);return;}}const bool is_worker_request = IsRequestFromServiceWorker(params);scoped_refptr<ExtensionFunction> function = CreateExtensionFunction(params, extension, render_process_id, is_worker_request,render_frame_host_url, params.context_type,ExtensionAPI::GetSharedInstance(), std::move(callback),render_frame_host);if (!function.get())return;if (extension &&ExtensionsBrowserClient::Get()->CanExtensionCrossIncognito(extension, browser_context_)) {function->set_include_incognito_information(true);}if (!extension) {if (function->source_context_type() == mojom::ContextType::kWebUi) {base::UmaHistogramSparse("Extensions.Functions.WebUICalls",function->histogram_value());} else if (function->source_context_type() ==mojom::ContextType::kUntrustedWebUi) {base::UmaHistogramSparse("Extensions.Functions.WebUIUntrustedCalls",function->histogram_value());} else if (function->source_context_type() ==mojom::ContextType::kWebPage) {base::UmaHistogramSparse("Extensions.Functions.NonExtensionWebPageCalls",function->histogram_value());}// Skip the quota, event page, activity logging stuff if there// isn't an extension, e.g. if the function call was from WebUI.function->RunWithValidation().Execute();return;}// Fetch the ProcessManager before |this| is possibly invalidated.ProcessManager* process_manager = ProcessManager::Get(browser_context_);ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_);QuotaService* quota = extension_system->quota_service();std::string violation_error =quota->Assess(extension->id(), function.get(), params.arguments,base::TimeTicks::Now());if (violation_error.empty()) {// See crbug.com/39178.ExtensionsBrowserClient::Get()->PermitExternalProtocolHandler();NotifyApiFunctionCalled(extension->id(), params.name, params.arguments,browser_context_);// Note: Deliberately don't include external component extensions here -// this lets us differentiate between "built-in" extension calls and// external extension callsif (extension->location() == mojom::ManifestLocation::kComponent) {base::UmaHistogramSparse("Extensions.Functions.ComponentExtensionCalls",function->histogram_value());} else {base::UmaHistogramSparse("Extensions.Functions.ExtensionCalls",function->histogram_value());}if (IsRequestFromServiceWorker(params)) {base::UmaHistogramSparse("Extensions.Functions.ExtensionServiceWorkerCalls",function->histogram_value());}if (extension->manifest_version() == 3) {base::UmaHistogramSparse("Extensions.Functions.ExtensionMV3Calls",function->histogram_value());}base::ElapsedTimer timer;function->RunWithValidation().Execute();// TODO(devlin): Once we have a baseline metric for how long functions take,// we can create a handful of buckets and record the function name so that// we can find what the fastest/slowest are.// Note: Many functions execute finish asynchronously, so this time is not// always a representation of total time taken. See also// Extensions.Functions.TotalExecutionTime.UMA_HISTOGRAM_TIMES("Extensions.Functions.SynchronousExecutionTime",timer.Elapsed());} else {function->OnQuotaExceeded(violation_error);}// Note: do not access |this| after this point. We may have been deleted// if function->Run() ended up closing the tab that owns us.// Check if extension was uninstalled by management.uninstall.if (!registry->enabled_extensions().GetByID(params.extension_id))return;function->set_request_uuid(base::Uuid::GenerateRandomV4());// Increment the keepalive to ensure the extension doesn't shut down while// it's executing an API function.if (IsRequestFromServiceWorker(params)) {CHECK(function->worker_id());content::ServiceWorkerExternalRequestTimeoutType timeout_type =function->ShouldKeepWorkerAliveIndefinitely()? content::ServiceWorkerExternalRequestTimeoutType::kDoesNotTimeout: content::ServiceWorkerExternalRequestTimeoutType::kDefault;function->set_service_worker_keepalive(std::make_unique<ServiceWorkerKeepalive>(browser_context_, *function->worker_id(), timeout_type,Activity::API_FUNCTION, function->name()));} else {process_manager->IncrementLazyKeepaliveCount(function->extension(), Activity::API_FUNCTION, function->name());}
}

3、在ContextMenusCreateFunction::Run()响应

chrome\browser\extensions\api\context_menus\context_menus_api.cc

ExtensionFunction::ResponseAction ContextMenusCreateFunction::Run() {MenuItem::Id id(browser_context()->IsOffTheRecord(),MenuItem::ExtensionKey(extension_id()));absl::optional<api::context_menus::Create::Params> params =api::context_menus::Create::Params::Create(args());EXTENSION_FUNCTION_VALIDATE(params);if (params->create_properties.id) {id.string_uid = *params->create_properties.id;} else {if (BackgroundInfo::HasLazyContext(extension()))return RespondNow(Error(kIdRequiredError));// The Generated Id is added by context_menus_custom_bindings.js.EXTENSION_FUNCTION_VALIDATE(args().size() >= 1);EXTENSION_FUNCTION_VALIDATE(args()[0].is_dict());const base::Value& properties = args()[0];absl::optional<int> result = properties.GetDict().FindInt(extensions::context_menus_api_helpers::kGeneratedIdKey);EXTENSION_FUNCTION_VALIDATE(result);id.uid = *result;}std::string error;if (!extensions::context_menus_api_helpers::CreateMenuItem(params->create_properties, browser_context(), extension(), id,&error)) {return RespondNow(Error(std::move(error)));}return RespondNow(NoArguments());
}

六、总结

      至此 扩展调用chrome.contextMenus.create c++接口分析完毕。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57435.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R语言机器学习算法实战系列(十二)线性判别分析分类算法 (Linear Discriminant Analysis)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍LDA的原理LDA的步骤教程下载数据加载R包导入数据数据预处理数据描述数据切割构建模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve保存模型总结优点:缺…

Ubuntu(22.04)本地部署Appsmith

Ubuntu&#xff08;22.04&#xff09;安装Appsmith 简要介绍 Appsmith 是一个开源的低代码开发平台&#xff0c;旨在帮助开发者和非开发者快速构建定制化的内部应用程序和管理工具。通过直观的拖拽界面和丰富的预配置组件&#xff0c;Appsmith 让用户无需编写大量代码即可创建…

Postman使用-基础篇

前言 本教程将结合业界广为推崇和使用的RestAPI设计典范Github API&#xff0c;详细介绍Postman接口测试工具的使用方法和实战技巧。 在开始这个教程之前&#xff0c;先聊一下为什么接口测试在现软件行业如此重要&#xff1f; 为什么我们要学习Postman&#xff1f; 现代软件…

数据轻松上云——Mbox边缘计算网关

随着工业4.0时代的到来&#xff0c;工厂数字化转型已成为提升生产效率、优化资源配置、增强企业竞争力的关键。我们凭借其先进的边缘计算网关与云平台技术&#xff0c;为工厂提供了高效、稳定的数据采集与上云解决方案。本文将为您介绍Mbox边缘计算网关如何配合明达云平台&…

基于SpringBoot微信小程序的书院预约系统【附源码】

基于SpringBoot微信小程序的书院预约系统 效果如下&#xff1a; 微信小程序首页界面 用户登录界面 书院信息界面 会议室界面 管理员登录界面 管理员主界面 用户界面 书院信息界面 会议室界面 会议室预约界面 研究背景 随着社会的快速发展&#xff0c;计算机技术的影响是全面…

SpringBoot 单元测试 - 登录认证在 Spring Boot 上的标准单元测试写法。

&#x1f449; 请投票支持这款 全新设计的脚手架 &#xff0c;让 Java 再次伟大&#xff01; 不要使用 SpringBootTest 使用 SpringBootTest 进行单元测试会启动整个 Spring Boot 容器&#xff0c;并引入整个项目的 development&test 依赖。缺点是速度慢、体积大、测试目标…

HarmonyOS Next应用开发——图像PixelMap变换

【高心星出品】 图像变换 图片处理指对PixelMap进行相关的操作&#xff0c;如获取图片信息、裁剪、缩放、偏移、旋转、翻转、设置透明度、读写像素数据等。图片处理主要包括图像变换、位图操作&#xff0c;本文介绍图像变换。 图形裁剪 // 裁剪图片 x&#xff0c;y为裁剪的起…

【element-tiptap】如何把分隔线改造成下拉框的形式?

当前的分隔线只有细横线这一种形式 但是咱们可以看一下wps中的分隔线&#xff0c;花里胡哨的 这些在wps里都需要使用快捷键打出来&#xff0c;真没找到菜单在哪里 那么这篇文章咱们就来看一下如何改造分隔线组件&#xff0c;改造成下拉框的形式&#xff0c;并且把咱们想要的分…

如何调试浏览器中的内存泄漏?

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介⭐ 如何调试浏览器中的内存泄漏&#xff1f;1. 什么是内存泄漏&#xff1f;2. 调试内存泄漏的工具3. 如何使用 Memory 面板进行内存调试3.1 获取内存快照&#xff08;Heap Snapshot&#xff09;获取内存快照的步骤&#xff1a;快照…

【二刷hot100】day 4

终于有时间刷刷力扣&#xff0c;求实习中。。。。 目录 1.最大子数组和 2.合并区间 3.轮转数组 4.除自身以外数组的乘积 1.最大子数组和 class Solution {public int maxSubArray(int[] nums) {//就是说可以转换为计算左边的最大值&#xff0c;加上中间的值&#xff0c…

1.6,unity动画Animator屏蔽某个部位,动画组合

动画组合 一边跑一边攻击 using System.Collections; using System.Collections.Generic; using UnityEngine;public class One : MonoBehaviour {private Animator anim;// Start is called before the first frame updatevoid Start(){anim GetComponent<Animator>();…

PPT自动化:Python如何修改PPT文字和样式!

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 使用 Python 修改 PPT 文本内容📝 遍历所有幻灯片和文本框📝 设置和修改文本样式📝 复制和保留文本样式⚓️ 相关链接 ⚓️📖 介绍 📖 在日常工作中,PPT 的文字内容和样式修改似乎是一项永无止境的…

渗透测试实战—教育攻防演练中突破网络隔离

免责声明&#xff1a;文章来源于真实渗透测试&#xff0c;已获得授权&#xff0c;且关键信息已经打码处理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本…

基准线markLine的值超过坐标轴范围导致markline不显示

解决问题&#xff1a;动态设置yAxis的max值&#xff08;解决基准线不在y轴范围&#xff09; yAxis: [{name: 单位&#xff1a;千,...yAxis,nameTextStyle:{...yAxis.nameTextStyle,padding: [0,26,0,24]},paddingLeft:24,paddingRight:26},{name: 单位&#xff1a;百分比,...yA…

基金好书入门阅读笔记《基金作战笔记:从投基新手到配置高手的进阶之路》笔记3

公募基金的分类方式按投资范围分 80%以上资产投资于股票的&#xff0c;叫股票基金&#xff1b;80%以上资产投资于债券的&#xff0c;叫债券基金&#xff1b;80% 以上资产投资于其他基金的&#xff0c;叫FOF; 80%以上资产投资于货币市场的&#xff0c;叫货币基金&#xff1b;以上…

建库建表练习

目录 根据以下需求完成图书管理系统数据库及表设计&#xff0c;并建库建表&#xff0c;并截图创建表的详细信息(desc 表名),不用添加数据 1. 用户表: 字段: 姓名&#xff0c;用户名&#xff0c;密码&#xff0c;电话&#xff0c;住址&#xff0c;专业及年级 2. 图书表: 字段: 图…

大数据新视界 -- 大数据大厂之 AI 驱动的大数据分析:智能决策的新引擎

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Flux.all 使用说明书

all public final Mono<Boolean> all(Predicate<? super T> predicate)Emit a single boolean true if all values of this sequence match the Predicate. 如果该序列中的所有值都匹配给定的谓词&#xff08;Predicate&#xff09;&#xff0c;则发出一个布尔值…

大模型之三十二-语音合成TTS(coqui) 之二 fine-tune

在 大模型之三十-语音合成TTS(coqui)[shichaog CSDN]中提到了xttsv2的fine-tune。 数据情况&#xff1a; 我是从bilibili up主小Lin说提取了一些视频&#xff0c;然后进行了重新的fine-tune。 训练结果 如下图所示&#xff0c;上面波形幅度较大的是xttsv2原始模型的结果&am…

tauri打包失败Error failed to bundle project: error running light.exe解决办法

Running light to produce D:\a\PakePlus\PakePlus\src-tauri\target\release\bundle\msi\快手_0.0.1_x64_en-US.msi Error failed to bundle project: error running light.exe ELIFECYCLE  Command failed with exit code 1. Error: Command failed with exit code 1: pnpm …