首页 > 编程语言 >Chromium 中chrome.contextMenus扩展接口实现分析c++

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

时间:2024-10-20 17:20:37浏览次数:7  
标签:function mojom extension chrome c++ context contextMenus type id

一、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

相关文章

  • 【蓝桥杯】C++ 第20场 小白入门赛
    一、四个亲戚题目四个亲戚 题目分析字面意思:Daiyu+‘kind’代码#include<iostream>usingnamespacestd;intmain(){cout<<"Daiyu'kind'";return0;}二、黛玉泡茶题目黛玉泡茶 题目分析1.我们可以c2.然后c3.计算c,如果不能,整除后的答案还要加1 ......
  • 基于C++的 BP/CNN神经网络算法(不调包)
    目前玩深度学习的小伙伴,上来就是使用现有的深度学习框架(TensorFlow,keras,pytorch,caffe),增加网络层,就像搭积木似的,看似方便,实则有时不利于个人能力发展,要知道现在公司需要的算法工程师,不仅仅只是会搭积木(这种工作,入门几个月的人就可以干了),而是要深入底层,能优化代码,能自己搭。本文......
  • qt图像算法—图像的缩放之c++实现(不调包)
     1.基本原理  图像的缩放一般使用插值算法,而本章将介绍两种常用插值算法:最临近插值法和双线性插值法  1.最临近插值法  将浮点数的位置坐标,进行四舍五入找到原图像的整型坐标即可,具体操作可见下面的公式,其中原图像坐标为(x,y),输出图像坐标为(i,j),比例系数为fx和fy。......
  • qt图像算法—图像的种子算法之c++实现(不调包)
     1.基本原理  相互连通且颜色相近的像素集合可以被看成图像的区域,而区域填充就是将每一块图像区域用指定颜色填充,填充的算法有很多种,但今天的猪脚是种子算法。在使用种子算法的时候,我们要注意两点,第一点:连通像素的搜索分为四方向和八方向,根据应用自己选择就行;第二点:边界......
  • 使用 C++ 实现验证码识别与自动化登录
    安装所需依赖确保你已经安装以下库:libcurl:用于发送HTTP请求。OpenCV:用于图像处理。Tesseract:用于OCR识别。在Ubuntu系统中,你可以使用以下命令安装这些依赖:bashsudoapt-getinstalllibcurl4-openssl-devsudoapt-getinstalllibopencv-devsudoapt-getinstall......
  • 2.1.2 话题通信基本操作A(C++)
    需求:编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。分析:在模型实现中,ROSmaster不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:发布方接收方数据(此处为普通文本)流程:编写发布方实现;编写订阅......
  • C++编程-贪心算法2
    目录先言例题三:删数问题(NOI1994)题目描述算法分析标准程序-字符串String例题四:拦截导弹问题题目描述算法分析主要框架(标准程序)例题五:活动选择题目描述算法分析标准程序先言今天讲贪心算法的第3~5例题例题三:删数问题(NOI1994)题目描述【题目描述】输......
  • c++跑酷(技能升级版,升级火,镖,水)
    #include<bits/stdc++.h> #include<windows.h>#include<stdio.h>#include<conio.h>#include<time.h>#defineNorif(B[b].x<5)B[b].x=5;#defineOut1Bx1-Bvx1<=6||Bx1-Bvx1>=28||By1-Bvy1<=7||By1-Bvy1>=27#defineOut......
  • 用C++实现自己的智能指针:深入探讨内存管理与RAII模式
    解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界C++中的内存管理一直以来是程序员的一个难点,尤其是在处理动态内存分配时。智能指针(如std::unique_ptr和std::shared_ptr)通过RAII(资源获取即初始化)的设计理念,极大地简化了动态内存的管理,减少了内存泄漏的风险。然......
  • 用C++编写一个简单的游戏引擎:从游戏循环到物理与渲染的全面解析
    解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界构建一个基础的2D游戏引擎是一项富有挑战性但极具学习价值的任务。本文将通过从零开始的方式,逐步讲解如何使用C++开发一个简单的游戏引擎。内容涵盖了游戏引擎的核心架构设计,包括游戏循环、物理引擎和图形渲染等......