Repository: electron/electron
Pull request: fix: `InspectorFrontendHost` override in embedded windows

#### Description of Change

Closes https://github.com/electron/electron/issues/49753

Adds a path to `_overrideGlobalValueFromIsolatedWorld` that allows for silent failure when a given property doesn't exist on an object without hard CHECKing.

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed an issue where Chrome Devtools menus may not appear in certain embedded windows.

Changed files:
filename: lib/renderer/api/context-bridge.ts
patch: @@ -23,11 +23,14 @@ export default contextBridge;
 
 export const internalContextBridge = {
   contextIsolationEnabled: process.contextIsolated,
+  tryOverrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => {
+    return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true, true);
+  },
   overrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => {
-    return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false);
+    return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false, false);
   },
   overrideGlobalValueWithDynamicPropsFromIsolatedWorld: (keys: string[], value: any) => {
-    return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true);
+    return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true, false);
   },
   overrideGlobalPropertyFromIsolatedWorld: (keys: string[], getter: Function, setter?: Function) => {
     return binding._overrideGlobalPropertyFromIsolatedWorld(keys, getter, setter || null);

filename: lib/renderer/inspector.ts
patch: @@ -11,14 +11,12 @@ const { contextIsolationEnabled } = internalContextBridge;
 * 1) Use menu API to show context menu.
 */
 window.onload = function () {
-  if (window.InspectorFrontendHost) {
-    if (contextIsolationEnabled) {
-      internalContextBridge.overrideGlobalValueFromIsolatedWorld([
-        'InspectorFrontendHost', 'showContextMenuAtPoint'
-      ], createMenu);
-    } else {
-      window.InspectorFrontendHost.showContextMenuAtPoint = createMenu;
-    }
+  if (contextIsolationEnabled) {
+    internalContextBridge.tryOverrideGlobalValueFromIsolatedWorld([
+      'InspectorFrontendHost', 'showContextMenuAtPoint'
+    ], createMenu);
+  } else {
+    window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
   }
 };
 

filename: shell/renderer/api/electron_api_context_bridge.cc
patch: @@ -5,6 +5,7 @@
 #include "shell/renderer/api/electron_api_context_bridge.h"
 
 #include <memory>
+#include <optional>
 #include <set>
 #include <string>
 #include <utility>
@@ -827,13 +828,18 @@ void ExposeAPIInWorld(v8::Isolate* isolate,
   ExposeAPI(isolate, source_context, target_isolate, target_context, key, api);
 }
 
-gin_helper::Dictionary TraceKeyPath(const gin_helper::Dictionary& start,
-                                    const std::vector<std::string>& key_path) {
+std::optional<gin_helper::Dictionary> TraceKeyPath(
+    const gin_helper::Dictionary& start,
+    const std::vector<std::string>& key_path,
+    bool allow_silent_failure) {
   gin_helper::Dictionary current = start;
   for (size_t i = 0; i < key_path.size() - 1; i++) {
-    CHECK(current.Get(key_path[i], &current))
-        << "Failed to get property '" << key_path[i] << "' at index " << i
-        << " in key path";
+    if (!current.Get(key_path[i], &current)) {
+      if (allow_silent_failure)
+        return std::nullopt;
+      CHECK(false) << "Failed to get property '" << key_path[i] << "' at index "
+                   << i << " in key path";
+    }
   }
   return current;
 }
@@ -842,7 +848,8 @@ void OverrideGlobalValueFromIsolatedWorld(
     v8::Isolate* isolate,
     const std::vector<std::string>& key_path,
     v8::Local<v8::Object> value,
-    bool support_dynamic_properties) {
+    bool support_dynamic_properties,
+    bool allow_silent_failure) {
   if (key_path.empty())
     return;
 
@@ -854,7 +861,11 @@ void OverrideGlobalValueFromIsolatedWorld(
   gin_helper::Dictionary global(isolate, main_context->Global());
 
   const std::string final_key = key_path[key_path.size() - 1];
-  gin_helper::Dictionary target_object = TraceKeyPath(global, key_path);
+  auto maybe_target_object =
+      TraceKeyPath(global, key_path, allow_silent_failure);
+  if (!maybe_target_object.has_value())
+    return;
+  gin_helper::Dictionary target_object = maybe_target_object.value();
 
   {
     v8::Context::Scope main_context_scope(main_context);
@@ -887,8 +898,8 @@ bool OverrideGlobalPropertyFromIsolatedWorld(
   gin_helper::Dictionary global(isolate, main_context->Global());
 
   const std::string final_key = key_path[key_path.size() - 1];
-  v8::Local<v8::Object> target_object =
-      TraceKeyPath(global, key_path).GetHandle();
+  auto target_dict = TraceKeyPath(global, key_path, false);
+  v8::Local<v8::Object> target_object = target_dict.value().GetHandle();
 
   {
     v8::Context::Scope main_context_scope(main_context);