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边缘计算网关如何配合明达云平台&…

React 基础阶段学习计划

React 基础阶段学习计划 目标 能够创建和使用React组件。理解并使用State和Props。掌握事件处理和表单处理。 学习内容 环境搭建 安装Node.js和npm 访问 Node.js官网 下载并安装最新版本的Node.js。打开终端或命令行工具&#xff0c;输入 node -v 和 npm -v 检查是否安装…

【Python】爬虫

Python爬虫是一种自动化下载网页内容的程序。以下是一个简单的Python爬虫示例&#xff0c;使用requests库获取网页&#xff0c;并用BeautifulSoup解析网页。 首先&#xff0c;你需要安装必要的库&#xff1a; pip install requests pip install beautifulsoup4 以下是一个简…

基于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;快照…

【ShuQiHere】深入解析数字电路中的锁存器与触发器

深入解析数字电路中的锁存器与触发器 &#x1f916;&#x1f50c; 在数字电路设计中&#xff0c;**锁存器&#xff08;Latch&#xff09;和触发器&#xff08;Flip-Flop&#xff09;**是实现时序逻辑的基本元件。它们能够存储状态&#xff0c;是构建复杂数字系统的关键。本文将…

Dockerfile 中关于 RUN 的奇怪写法 -- 以 | 开头

在一个大型的官方镜像中 &#xff0c;我通过 docker history --no-trunc <image_id> 看到&#xff0c;该镜像某一步的构建过程是&#xff1a; RUN |3 CUDA_VERSION12.4.1.003 CUDA_DRIVER_VERSION550.54.15 JETPACK_HOST_MOUNTS /bin/sh -c if [ -n "${JETPACK_HOS…

如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)

文章目录 一、什么是 Spring Boot Starter&#xff1f;二、为什么要自定义 Starter&#xff1f;三、自定义 Starter 的基本步骤1. 创建 Maven 项目2. 配置 pom.xml3. 创建自动配置类4. 创建业务逻辑类5. 创建 spring.factories 四、使用自定义 Starter五、总结推荐阅读文章 在使…

Android广播限制Background execution not allowed: receiving Intent { act=

“Background execution not allowed: receiving Intent”这个错误信息通常出现在Android应用开发中&#xff0c;特别是在处理后台任务或接收广播&#xff08;Broadcast&#xff09;时。这个错误表明应用试图在后台执行某些操作&#xff0c;但Android系统出于电池优化和用户体验…

【二刷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>();…

Scala中的reduce

作用&#xff1a;reduce是一种集合操作&#xff0c;用于对集合中的元素进行聚合操作&#xff0c;返回一个单一的结果。它通过指定的二元操作&#xff08;即取两个元素进行操作&#xff09;对集合中所有的元素进行递归处理&#xff0c;并最终将其合并为一个值。 语法&#xff1…

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

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

每日算法一练:剑指offer——数组篇(3)

1.报数 实现一个十进制数字报数程序&#xff0c;请按照数字从小到大的顺序返回一个整数数列&#xff0c;该数列从数字 1 开始&#xff0c;到最大的正整数 cnt 位数字结束。 示例 1: 输入&#xff1a;cnt 2 输出&#xff1a;[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,1…