一、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 function
console.log('Radio item clicked. Status:', info.checked);
break;
case 'checkbox':
// Checkbox item function
console.log('Checkbox item clicked. Status:', info.checked);
break;
default:
// Standard context menu item function
console.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>");
});
#endif
const 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");
#endif
std::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 content
namespace 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,
#endif
public 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);
#endif
static 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();
else
url = 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 calls
if (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++接口分析完毕。
标签:function,mojom,extension,chrome,c++,context,contextMenus,type,id From: https://blog.csdn.net/jangdong/article/details/143083829