feat: I guess it's esm (#37535)
* fix: allow ESM loads from within ASAR files * fix: ensure that ESM entry points finish loading before app ready * fix: allow loading ESM entrypoints via default_app * fix: allow ESM loading for renderer preloads * docs: document current known limitations of esm * chore: add patches to support blending esm handlers * refactor: use SetDefersLoading instead of JoinAppCode in renderers Blink has it's own event loop so pumping the uv loop in the renderer is not enough, luckily in blink we can suspend the loading of the frame while we do additional work. * chore: add patch to expose SetDefersLoading * fix: use fileURLToPath instead of pathname * chore: update per PR feedback * fix: fs.exists/existsSync should never throw * fix: convert path to file url before importing * fix: oops * fix: oops * Update docs/tutorial/esm-limitations.md Co-authored-by: Jeremy Rose <jeremya@chromium.org> * windows... * windows... * chore: update patches * spec: fix tests and document empty body edge case * Apply suggestions from code review Co-authored-by: Daniel Scalzi <d_scalzi@yahoo.com> Co-authored-by: Jeremy Rose <jeremya@chromium.org> * spec: add tests for esm * spec: windows * chore: update per PR feedback * chore: update patches * Update shell/common/node_bindings.h Co-authored-by: Jeremy Rose <jeremya@chromium.org> * chore: update patches * rebase * use cjs loader by default for preload scripts * chore: fix lint * chore: update patches * chore: update patches * chore: fix patches * build: debug depshash * ? * Revert "build: debug depshash" This reverts commit 0de82523fb93f475226356b37418ce4b69acdcdf. * chore: allow electron as builtin protocol in esm loader * Revert "Revert "build: debug depshash"" This reverts commit ff86b1243ca6d05c9b3b38e0a6d717fb380343a4. * chore: fix esm doc * chore: update node patches --------- Co-authored-by: Jeremy Rose <jeremya@chromium.org> Co-authored-by: electron-patch-conflict-fixer[bot] <83340002+electron-patch-conflict-fixer[bot]@users.noreply.github.com> Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com> Co-authored-by: Daniel Scalzi <d_scalzi@yahoo.com>
This commit is contained in:
parent
b8ac798344
commit
ac031bf8de
36 changed files with 910 additions and 57 deletions
|
@ -286,6 +286,9 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
|
|||
// Load everything.
|
||||
node_bindings_->LoadEnvironment(node_env_.get());
|
||||
|
||||
// Wait for app
|
||||
node_bindings_->JoinAppCode();
|
||||
|
||||
// We already initialized the feature list in PreEarlyInitialization(), but
|
||||
// the user JS script would not have had a chance to alter the command-line
|
||||
// switches at that point. Lets reinitialize it here to pick up the
|
||||
|
|
|
@ -234,7 +234,6 @@ void WebContentsPreferences::SetFromDictionary(
|
|||
disable_blink_features_ = disable_blink_features;
|
||||
|
||||
base::FilePath::StringType preload_path;
|
||||
std::string preload_url_str;
|
||||
if (web_preferences.Get(options::kPreloadScript, &preload_path)) {
|
||||
base::FilePath preload(preload_path);
|
||||
if (preload.IsAbsolute()) {
|
||||
|
|
|
@ -28,6 +28,7 @@ class Locker {
|
|||
std::unique_ptr<v8::Locker> locker_;
|
||||
|
||||
static bool g_is_browser_process;
|
||||
static bool g_is_renderer_process;
|
||||
};
|
||||
|
||||
} // namespace gin_helper
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "shell/browser/api/electron_api_app.h"
|
||||
#include "shell/common/api/electron_bindings.h"
|
||||
#include "shell/common/electron_command_line.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/event.h"
|
||||
|
@ -33,8 +34,11 @@
|
|||
#include "shell/common/gin_helper/locker.h"
|
||||
#include "shell/common/gin_helper/microtasks_scope.h"
|
||||
#include "shell/common/mac/main_application_bundle.h"
|
||||
#include "shell/common/world_ids.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h" // nogncheck
|
||||
#include "third_party/electron_node/src/debug_utils.h"
|
||||
#include "third_party/electron_node/src/module_wrap.h"
|
||||
|
||||
#if !IS_MAS_BUILD()
|
||||
#include "shell/common/crash_keys.h"
|
||||
|
@ -171,6 +175,64 @@ bool AllowWasmCodeGenerationCallback(v8::Local<v8::Context> context,
|
|||
return node::AllowWasmCodeGenerationCallback(context, source);
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Promise> HostImportModuleDynamically(
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Data> v8_host_defined_options,
|
||||
v8::Local<v8::Value> v8_referrer_resource_url,
|
||||
v8::Local<v8::String> v8_specifier,
|
||||
v8::Local<v8::FixedArray> v8_import_assertions) {
|
||||
if (node::Environment::GetCurrent(context) == nullptr) {
|
||||
if (electron::IsBrowserProcess() || electron::IsUtilityProcess())
|
||||
return v8::MaybeLocal<v8::Promise>();
|
||||
return blink::V8Initializer::HostImportModuleDynamically(
|
||||
context, v8_host_defined_options, v8_referrer_resource_url,
|
||||
v8_specifier, v8_import_assertions);
|
||||
}
|
||||
|
||||
// If we're running with contextIsolation enabled in the renderer process,
|
||||
// fall back to Blink's logic.
|
||||
if (electron::IsRendererProcess()) {
|
||||
blink::WebLocalFrame* frame =
|
||||
blink::WebLocalFrame::FrameForContext(context);
|
||||
if (!frame || frame->GetScriptContextWorldId(context) !=
|
||||
electron::WorldIDs::ISOLATED_WORLD_ID) {
|
||||
return blink::V8Initializer::HostImportModuleDynamically(
|
||||
context, v8_host_defined_options, v8_referrer_resource_url,
|
||||
v8_specifier, v8_import_assertions);
|
||||
}
|
||||
}
|
||||
|
||||
return node::loader::ImportModuleDynamically(
|
||||
context, v8_host_defined_options, v8_referrer_resource_url, v8_specifier,
|
||||
v8_import_assertions);
|
||||
}
|
||||
|
||||
void HostInitializeImportMetaObject(v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Module> module,
|
||||
v8::Local<v8::Object> meta) {
|
||||
if (node::Environment::GetCurrent(context) == nullptr) {
|
||||
if (electron::IsBrowserProcess() || electron::IsUtilityProcess())
|
||||
return;
|
||||
return blink::V8Initializer::HostGetImportMetaProperties(context, module,
|
||||
meta);
|
||||
}
|
||||
|
||||
// If we're running with contextIsolation enabled in the renderer process,
|
||||
// fall back to Blink's logic.
|
||||
if (electron::IsRendererProcess()) {
|
||||
blink::WebLocalFrame* frame =
|
||||
blink::WebLocalFrame::FrameForContext(context);
|
||||
if (!frame || frame->GetScriptContextWorldId(context) !=
|
||||
electron::WorldIDs::ISOLATED_WORLD_ID) {
|
||||
return blink::V8Initializer::HostGetImportMetaProperties(context, module,
|
||||
meta);
|
||||
}
|
||||
}
|
||||
|
||||
return node::loader::ModuleWrap::HostInitializeImportMetaObjectCallback(
|
||||
context, module, meta);
|
||||
}
|
||||
|
||||
v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings(
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Value> source,
|
||||
|
@ -481,7 +543,8 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
|||
v8::Handle<v8::Context> context,
|
||||
node::MultiIsolatePlatform* platform,
|
||||
std::vector<std::string> args,
|
||||
std::vector<std::string> exec_args) {
|
||||
std::vector<std::string> exec_args,
|
||||
absl::optional<base::RepeatingCallback<void()>> on_app_code_ready) {
|
||||
// Feed node the path to initialization script.
|
||||
std::string process_type;
|
||||
switch (browser_env_) {
|
||||
|
@ -529,7 +592,8 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
|||
node::Environment* env;
|
||||
uint64_t flags = node::EnvironmentFlags::kDefaultFlags |
|
||||
node::EnvironmentFlags::kHideConsoleWindows |
|
||||
node::EnvironmentFlags::kNoGlobalSearchPaths;
|
||||
node::EnvironmentFlags::kNoGlobalSearchPaths |
|
||||
node::EnvironmentFlags::kNoRegisterESMLoader;
|
||||
|
||||
if (browser_env_ == BrowserEnvironment::kRenderer ||
|
||||
browser_env_ == BrowserEnvironment::kWorker) {
|
||||
|
@ -541,8 +605,7 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
|||
// for processes that already have these defined by DOM.
|
||||
// Check //third_party/electron_node/lib/internal/bootstrap/node.js
|
||||
// for the list of overrides on globalThis.
|
||||
flags |= node::EnvironmentFlags::kNoRegisterESMLoader |
|
||||
node::EnvironmentFlags::kNoBrowserGlobals |
|
||||
flags |= node::EnvironmentFlags::kNoBrowserGlobals |
|
||||
node::EnvironmentFlags::kNoCreateInspector;
|
||||
}
|
||||
|
||||
|
@ -635,6 +698,10 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
|||
}
|
||||
|
||||
node::SetIsolateUpForNode(context->GetIsolate(), is);
|
||||
context->GetIsolate()->SetHostImportModuleDynamicallyCallback(
|
||||
HostImportModuleDynamically);
|
||||
context->GetIsolate()->SetHostInitializeImportMetaObjectCallback(
|
||||
HostInitializeImportMetaObject);
|
||||
|
||||
gin_helper::Dictionary process(context->GetIsolate(), env->process_object());
|
||||
process.SetReadOnly("type", process_type);
|
||||
|
@ -644,6 +711,17 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
|||
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
|
||||
process.Set("helperExecPath", helper_exec_path);
|
||||
|
||||
if (browser_env_ == BrowserEnvironment::kBrowser ||
|
||||
browser_env_ == BrowserEnvironment::kRenderer) {
|
||||
if (on_app_code_ready) {
|
||||
process.SetMethod("appCodeLoaded", std::move(*on_app_code_ready));
|
||||
} else {
|
||||
process.SetMethod("appCodeLoaded",
|
||||
base::BindRepeating(&NodeBindings::SetAppCodeLoaded,
|
||||
base::Unretained(this)));
|
||||
}
|
||||
}
|
||||
|
||||
auto env_deleter = [isolate, isolate_data,
|
||||
context = v8::Global<v8::Context>{isolate, context}](
|
||||
node::Environment* nenv) mutable {
|
||||
|
@ -664,7 +742,8 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
|||
|
||||
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
||||
v8::Handle<v8::Context> context,
|
||||
node::MultiIsolatePlatform* platform) {
|
||||
node::MultiIsolatePlatform* platform,
|
||||
absl::optional<base::RepeatingCallback<void()>> on_app_code_ready) {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
auto& electron_args = ElectronCommandLine::argv();
|
||||
std::vector<std::string> args(electron_args.size());
|
||||
|
@ -673,7 +752,7 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
|||
#else
|
||||
auto args = ElectronCommandLine::argv();
|
||||
#endif
|
||||
return CreateEnvironment(context, platform, args, {});
|
||||
return CreateEnvironment(context, platform, args, {}, on_app_code_ready);
|
||||
}
|
||||
|
||||
void NodeBindings::LoadEnvironment(node::Environment* env) {
|
||||
|
@ -716,6 +795,37 @@ void NodeBindings::StartPolling() {
|
|||
UvRunOnce();
|
||||
}
|
||||
|
||||
void NodeBindings::SetAppCodeLoaded() {
|
||||
app_code_loaded_ = true;
|
||||
}
|
||||
|
||||
void NodeBindings::JoinAppCode() {
|
||||
// We can only "join" app code to the main thread in the browser process
|
||||
if (browser_env_ != BrowserEnvironment::kBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* browser = Browser::Get();
|
||||
node::Environment* env = uv_env();
|
||||
|
||||
if (!env)
|
||||
return;
|
||||
|
||||
v8::HandleScope handle_scope(env->isolate());
|
||||
// Enter node context while dealing with uv events.
|
||||
v8::Context::Scope context_scope(env->context());
|
||||
|
||||
// Pump the event loop until we get the signal that the app code has finished
|
||||
// loading
|
||||
while (!app_code_loaded_ && !browser->is_shutting_down()) {
|
||||
int r = uv_run(uv_loop_, UV_RUN_ONCE);
|
||||
if (r == 0) {
|
||||
base::RunLoop().QuitWhenIdle(); // Quit from uv.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeBindings::UvRunOnce() {
|
||||
node::Environment* env = uv_env();
|
||||
|
||||
|
|
|
@ -11,12 +11,14 @@
|
|||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/raw_ptr_exclusion.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "gin/public/context_holder.h"
|
||||
#include "gin/public/gin_embedders.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
#include "uv.h" // NOLINT(build/include_directory)
|
||||
#include "v8/include/v8.h"
|
||||
|
||||
|
@ -94,11 +96,15 @@ class NodeBindings {
|
|||
v8::Handle<v8::Context> context,
|
||||
node::MultiIsolatePlatform* platform,
|
||||
std::vector<std::string> args,
|
||||
std::vector<std::string> exec_args);
|
||||
std::vector<std::string> exec_args,
|
||||
absl::optional<base::RepeatingCallback<void()>> on_app_code_ready =
|
||||
absl::nullopt);
|
||||
|
||||
std::shared_ptr<node::Environment> CreateEnvironment(
|
||||
v8::Handle<v8::Context> context,
|
||||
node::MultiIsolatePlatform* platform);
|
||||
node::MultiIsolatePlatform* platform,
|
||||
absl::optional<base::RepeatingCallback<void()>> on_app_code_ready =
|
||||
absl::nullopt);
|
||||
|
||||
// Load node.js in the environment.
|
||||
void LoadEnvironment(node::Environment* env);
|
||||
|
@ -134,6 +140,10 @@ class NodeBindings {
|
|||
NodeBindings(const NodeBindings&) = delete;
|
||||
NodeBindings& operator=(const NodeBindings&) = delete;
|
||||
|
||||
// Blocks until app code is signaled to be loaded via |SetAppCodeLoaded|.
|
||||
// Only has an effect if called in the browser process
|
||||
void JoinAppCode();
|
||||
|
||||
protected:
|
||||
explicit NodeBindings(BrowserEnvironment browser_env);
|
||||
|
||||
|
@ -168,9 +178,17 @@ class NodeBindings {
|
|||
// Thread to poll uv events.
|
||||
static void EmbedThreadRunner(void* arg);
|
||||
|
||||
// Default callback to indicate when the node environment has finished
|
||||
// initializing and the primary import chain is fully resolved and executed
|
||||
void SetAppCodeLoaded();
|
||||
|
||||
// Indicates whether polling thread has been created.
|
||||
bool initialized_ = false;
|
||||
|
||||
// Indicates whether the app code has finished loading
|
||||
// for ESM this is async after the module is loaded
|
||||
bool app_code_loaded_ = false;
|
||||
|
||||
// Whether the libuv loop has ended.
|
||||
bool embed_closed_ = false;
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "third_party/blink/public/web/web_document.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" // nogncheck
|
||||
|
||||
namespace electron {
|
||||
|
||||
|
@ -61,6 +62,11 @@ void ElectronRendererClient::RunScriptsAtDocumentEnd(
|
|||
"document-end");
|
||||
}
|
||||
|
||||
void ElectronRendererClient::UndeferLoad(content::RenderFrame* render_frame) {
|
||||
render_frame->GetWebFrame()->GetDocumentLoader()->SetDefersLoading(
|
||||
blink::LoaderFreezeMode::kNone);
|
||||
}
|
||||
|
||||
void ElectronRendererClient::DidCreateScriptContext(
|
||||
v8::Handle<v8::Context> renderer_context,
|
||||
content::RenderFrame* render_frame) {
|
||||
|
@ -88,8 +94,17 @@ void ElectronRendererClient::DidCreateScriptContext(
|
|||
v8::Maybe<bool> initialized = node::InitializeContext(renderer_context);
|
||||
CHECK(!initialized.IsNothing() && initialized.FromJust());
|
||||
|
||||
std::shared_ptr<node::Environment> env =
|
||||
node_bindings_->CreateEnvironment(renderer_context, nullptr);
|
||||
// Before we load the node environment, let's tell blink to hold off on
|
||||
// loading the body of this frame. We will undefer the load once the preload
|
||||
// script has finished. This allows our preload script to run async (E.g.
|
||||
// with ESM) without the preload being in a race
|
||||
render_frame->GetWebFrame()->GetDocumentLoader()->SetDefersLoading(
|
||||
blink::LoaderFreezeMode::kStrict);
|
||||
|
||||
std::shared_ptr<node::Environment> env = node_bindings_->CreateEnvironment(
|
||||
renderer_context, nullptr,
|
||||
base::BindRepeating(&ElectronRendererClient::UndeferLoad,
|
||||
base::Unretained(this), render_frame));
|
||||
|
||||
// If we have disabled the site instance overrides we should prevent loading
|
||||
// any non-context aware native module.
|
||||
|
|
|
@ -35,6 +35,8 @@ class ElectronRendererClient : public RendererClientBase {
|
|||
content::RenderFrame* render_frame) override;
|
||||
|
||||
private:
|
||||
void UndeferLoad(content::RenderFrame* render_frame);
|
||||
|
||||
// content::ContentRendererClient:
|
||||
void RenderFrameCreated(content::RenderFrame*) override;
|
||||
void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue