Page 1 of 2

window.cefQuery is not a function

PostPosted: Mon Jan 30, 2023 11:33 am
by BorjaPascual
Greetings,

I am afraid I'm a CEF newbie here. I'm sure this has been asked a few times, but I'm having a hard time finding a solution. I have been modifying the cefsimple project so that it accepts a custom handler, pointing to local files (which works like charm), as well as messaging between C++ and JS. This last part is where I am having some trouble. I'm getting the dreaded "window.cefQuery is not a function" and, no matter how much I compare my code and the one in the message_router project, I cannot find where I'm failing.

Here is my client:

Code: Select all
void PlatformTitleChange(CefRefPtr<CefBrowser> browser,
                         const CefString& title) {
  CefWindowHandle hwnd = browser->GetHost()->GetWindowHandle();
  SetWindowText(hwnd, std::wstring(title).c_str());
}

// Handle messages in the browser process.
class MessageHandler : public CefMessageRouterBrowserSide::Handler {
 public:
  explicit MessageHandler() {}

  // Called due to cefQuery execution in message_router.html.
  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    const std::string& message_name = request;
    if (message_name.find("testMessage") == 0) {
      // Return
      callback->Success("This is a response message from CEF");
      return true;
    }

    return false;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(MessageHandler);
};

namespace minimal {

Client::Client() {}

void Client::OnTitleChange(CefRefPtr<CefBrowser> browser,
                           const CefString& title) {
  CEF_REQUIRE_UI_THREAD();

#if defined(OS_WIN) || defined(OS_LINUX)
  // The Views framework is currently only supported on Windows and Linux.
  CefRefPtr<CefBrowserView> browser_view =
      CefBrowserView::GetForBrowser(browser);
  if (browser_view) {
    // Set the title of the window using the Views framework.
    CefRefPtr<CefWindow> window = browser_view->GetWindow();
    if (window)
      window->SetTitle(title);
  } else
#endif
  {
    // Set the title of the window using platform APIs.
    PlatformTitleChange(browser, title);
  }
}

bool Client::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
                                      CefRefPtr<CefFrame> frame,
                                      CefProcessId source_process,
                                      CefRefPtr<CefProcessMessage> message) {
  CEF_REQUIRE_UI_THREAD();

  return message_router_->OnProcessMessageReceived(browser, frame,
                                                   source_process, message);
}

void Client::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();

  if (!message_router_) {
    // Create the browser-side router for query handling.
    CefMessageRouterConfig config;
    config.js_query_function = "cefQuery";
    config.js_cancel_function = "cefQueryCancel";
    message_router_ = CefMessageRouterBrowserSide::Create(config);

    // Register handlers with the router.
    message_handler_.reset(new MessageHandler());
    message_router_->AddHandler(message_handler_.get(), false);
  }

  browser_ct_++;

  // Add to the list of existing browsers.
  ClientManager::GetInstance()->OnAfterCreated(browser);
}

bool Client::OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
                            CefRefPtr<CefFrame> frame,
                            CefRefPtr<CefRequest> request,
                            bool user_gesture,
                            bool is_redirect) {
  CEF_REQUIRE_UI_THREAD();

  message_router_->OnBeforeBrowse(browser, frame);
  return false;
}

bool Client::DoClose(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();

  // Closing the main window requires special handling. See the DoClose()
  // documentation in the CEF header for a detailed destription of this
  // process.
  ClientManager::GetInstance()->DoClose(browser);

  // Allow the close. For windowed browsers this will result in the OS close
  // event being sent.
  return false;
}

void Client::OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
                                       TerminationStatus status) {
  CEF_REQUIRE_UI_THREAD();

  message_router_->OnRenderProcessTerminated(browser);
}

CefRefPtr<CefResourceRequestHandler> Client::GetResourceRequestHandler(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefRequest> request,
    bool is_navigation,
    bool is_download,
    const CefString& request_initiator,
    bool& disable_default_handling) {
  CEF_REQUIRE_IO_THREAD();
  return this;
}

void Client::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
 
  if (--browser_ct_ == 0) {
    // Free the router when the last browser is closed.
    message_router_->RemoveHandler(message_handler_.get());
    message_handler_.reset();
    message_router_ = nullptr;
  }
  // Remove from the list of existing browsers.
  ClientManager::GetInstance()->OnBeforeClose(browser);
}

}


What may I be doing wrong? Thank you very much in advance.

Re: window.cefQuery is not a function

PostPosted: Mon Jan 30, 2023 11:41 am
by magreenblatt
It looks like you're missing the renderer side of the changes. The part in app_renderer_impl.cc.

Re: window.cefQuery is not a function

PostPosted: Mon Jan 30, 2023 3:34 pm
by BorjaPascual
magreenblatt wrote:It looks like you're missing the renderer side of the changes. The part in app_renderer_impl.cc.


As a matter of fact, I implemented it, here's the code:

Code: Select all
class RendererApp : public CefApp, public CefRenderProcessHandler {
 public:
  RendererApp() {}

  // CefApp methods:
  CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override {
    return this;
  }

  void OnRegisterCustomSchemes(CefRawPtr<CefSchemeRegistrar> registrar) {
    CefRegisterSchemeHandlerFactory("https", "test",
                                    new BackendSchemeHandlerFactory());
    // Register the custom scheme as standard and secure.
    // Must be the same implementation in all processes.
    registrar->AddCustomScheme("https", CEF_SCHEME_OPTION_STANDARD |
                                            CEF_SCHEME_OPTION_SECURE |
                                            CEF_SCHEME_OPTION_CORS_ENABLED |
                                            CEF_SCHEME_OPTION_FETCH_ENABLED);
  }

  // CefRenderProcessHandler methods:
  void OnWebKitInitialized() override {
    // Create the renderer-side router for query handling.
    CefMessageRouterConfig config;
    config.js_query_function = "cefQuery";
    config.js_cancel_function = "cefQueryCancel";
    message_router_ = CefMessageRouterRendererSide::Create(config);
  }

  void OnContextCreated(CefRefPtr<CefBrowser> browser,
                        CefRefPtr<CefFrame> frame,
                        CefRefPtr<CefV8Context> context) override {
    message_router_->OnContextCreated(browser, frame, context);
  }

  void OnContextReleased(CefRefPtr<CefBrowser> browser,
                         CefRefPtr<CefFrame> frame,
                         CefRefPtr<CefV8Context> context) override {
    message_router_->OnContextReleased(browser, frame, context);
  }

  bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
                                CefRefPtr<CefFrame> frame,
                                CefProcessId source_process,
                                CefRefPtr<CefProcessMessage> message) override {
    return message_router_->OnProcessMessageReceived(browser, frame,
                                                     source_process, message);
  }

 private:
  // Handles the renderer side of query routing.
  CefRefPtr<CefMessageRouterRendererSide> message_router_;

  IMPLEMENT_REFCOUNTING(RendererApp);
  DISALLOW_COPY_AND_ASSIGN(RendererApp);
};


I placed a breakpoint in the constructor and didn't fire, tho. Tried placing it in app_renderer_impl.cc and also didn't fire. My main is a literal copypaste of shared:

Code: Select all
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
                      HINSTANCE hPrevInstance,
                      LPTSTR lpCmdLine,
                      int nCmdShow) {
  UNREFERENCED_PARAMETER(hPrevInstance);
  UNREFERENCED_PARAMETER(lpCmdLine);

  // Enable High-DPI support on Windows 7 or newer.
  CefEnableHighDPISupport();

  void* sandbox_info = nullptr;

#if defined(CEF_USE_SANDBOX)
  // Manage the life span of the sandbox information object. This is necessary
  // for sandbox support on Windows. See cef_sandbox_win.h for complete details.
  CefScopedSandboxInfo scoped_sandbox;
  sandbox_info = scoped_sandbox.sandbox_info();
#endif

  // Provide CEF with command-line arguments.
  CefMainArgs main_args(hInstance);

  // CEF applications have multiple sub-processes (render, GPU, etc) that share
  // the same executable. This function checks the command-line and, if this is
  // a sub-process, executes the appropriate logic.
  int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
  if (exit_code >= 0) {
    // The sub-process has completed so return here.
    return exit_code;
  }

  // Parse command-line arguments for use in this method.
  CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
  command_line->InitFromString(::GetCommandLineW());

  // Create the singleton manager instance.
  ClientManager manager;

  // Specify CEF global settings here.
  CefSettings settings;

  if (command_line->HasSwitch("enable-chrome-runtime")) {
    // Enable experimental Chrome runtime. See issue #2969 for details.
    settings.chrome_runtime = true;
  }

#if !defined(CEF_USE_SANDBOX)
  settings.no_sandbox = true;
#endif

  // SimpleApp implements application-level callbacks for the browser process.
  // It will create the first browser instance in OnContextInitialized() after
  // CEF has initialized.
  //CefRefPtr<SimpleApp> app(new SimpleApp);
  // Create a CefApp of the correct process type.
  CefRefPtr<CefApp> app;
  switch (GetProcessType(command_line)) {
    case PROCESS_TYPE_BROWSER:
      app = CreateBrowserProcessApp();
      break;
    case PROCESS_TYPE_RENDERER:
      app = CreateRendererProcessApp();
      break;
    case PROCESS_TYPE_OTHER:
      app = CreateOtherProcessApp();
      break;
  }

  // Initialize CEF.
  CefInitialize(main_args, settings, app.get(), sandbox_info);

  // Run the CEF message loop. This will block until CefQuitMessageLoop() is
  // called.
  CefRunMessageLoop();

  // Shut down CEF.
  CefShutdown();

  return 0;
}


What may I be missing?

Re: window.cefQuery is not a function

PostPosted: Mon Jan 30, 2023 4:36 pm
by magreenblatt
Code: Select all
// Register the custom scheme as standard and secure.
    // Must be the same implementation in all processes.
    registrar->AddCustomScheme("https", CEF_SCHEME_OPTION_STANDARD |
                                            CEF_SCHEME_OPTION_SECURE |
                                            CEF_SCHEME_OPTION_CORS_ENABLED |
                                            CEF_SCHEME_OPTION_FETCH_ENABLED);

HTTPS is not a custom scheme, so you don't have to register it.

I placed a breakpoint in the constructor and didn't fire, tho. Tried placing it in app_renderer_impl.cc and also didn't fire.

The renderer process is a different process. You need to explicitly attach the debugger to it. Debug with Visual Studio and add "--wait-for-debugger-children=renderer" on the command-line. Debugging tips are available at https://www.chromium.org/developers/how ... n-windows/

Re: window.cefQuery is not a function

PostPosted: Tue Jan 31, 2023 1:57 am
by BorjaPascual
magreenblatt wrote:HTTPS is not a custom scheme, so you don't have to register it.


I created a custom scheme for https so it loads local files. Tried with a custom protocol called "backend:" but, for some reason, it wasn't able to run JS.

Re: window.cefQuery is not a function

PostPosted: Tue Jan 31, 2023 2:31 am
by BorjaPascual
magreenblatt wrote:The renderer process is a different process. You need to explicitly attach the debugger to it. Debug with Visual Studio and add "--wait-for-debugger-children=renderer" on the command-line. Debugging tips are available at https://www.chromium.org/developers/how ... n-windows/


I have tried with
Code: Select all
command_line->AppendSwitch("wait-for-debugger-children=renderer");


but nothing appened still.

Re: window.cefQuery is not a function

PostPosted: Tue Jan 31, 2023 6:06 am
by ndesktop
You should call AppendSwitchWithValue("wait-for-debugger-children", "renderer"), not AppendSwitch.
AppendSwitch will interpret "wait-for-debugger-children=renderer" as a switch name and obviously will not be found.

Re: window.cefQuery is not a function

PostPosted: Tue Jan 31, 2023 6:42 am
by BorjaPascual
ndesktop wrote:You should call AppendSwitchWithValue("wait-for-debugger-children", "renderer"), not AppendSwitch.
AppendSwitch will interpret "wait-for-debugger-children=renderer" as a switch name and obviously will not be found.


Thanks. I tried that too, but nothing. In the end, I have been able to do it via VS project properties. Either way, the main problem still is the whole "window.cefQuery is not a function" situation :(

Re: window.cefQuery is not a function

PostPosted: Tue Jan 31, 2023 10:10 am
by BorjaPascual
So here's another update:

I ran the "message_router" example with --renderer-process-limit=1 --renderer-startup-dialog --no-sandbox and hit some breakpoints in the renderer. Did the same in my project and no breakpoint was triggered. Could this mean my render handler is not being instantiated properly?

Re: window.cefQuery is not a function

PostPosted: Tue Jan 31, 2023 1:02 pm
by ndesktop
I think you should start from debugger with --wait-for-debugger-children=renderer directly on the arguments.
AppendSwitch(WithValue) probably occurs later when the renderer is already started.