feat: add webContents.setWindowOpenHandler API (#24517)

Co-authored-by: Jeremy Rose <jeremya@chromium.org>
This commit is contained in:
loc 2020-11-10 09:06:03 -08:00 committed by GitHub
parent 6b222a2d8a
commit 0b85fdf26c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 2087 additions and 885 deletions

View file

@ -784,14 +784,13 @@ void Session::CreateInterruptedDownload(const gin_helper::Dictionary& options) {
length, last_modified, etag, base::Time::FromDoubleT(start_time)));
}
void Session::SetPreloads(
const std::vector<base::FilePath::StringType>& preloads) {
void Session::SetPreloads(const std::vector<base::FilePath>& preloads) {
auto* prefs = SessionPreferences::FromBrowserContext(browser_context());
DCHECK(prefs);
prefs->set_preloads(preloads);
}
std::vector<base::FilePath::StringType> Session::GetPreloads() const {
std::vector<base::FilePath> Session::GetPreloads() const {
auto* prefs = SessionPreferences::FromBrowserContext(browser_context());
DCHECK(prefs);
return prefs->preloads();

View file

@ -115,8 +115,8 @@ class Session : public gin::Wrappable<Session>,
const std::string& uuid);
void DownloadURL(const GURL& url);
void CreateInterruptedDownload(const gin_helper::Dictionary& options);
void SetPreloads(const std::vector<base::FilePath::StringType>& preloads);
std::vector<base::FilePath::StringType> GetPreloads() const;
void SetPreloads(const std::vector<base::FilePath>& preloads);
std::vector<base::FilePath> GetPreloads() const;
v8::Local<v8::Value> Cookies(v8::Isolate* isolate);
v8::Local<v8::Value> Protocol(v8::Isolate* isolate);
v8::Local<v8::Value> ServiceWorkerContext(v8::Isolate* isolate);

View file

@ -639,7 +639,11 @@ void WebContents::InitWithSessionAndOptions(
prefs->caret_blink_interval = *interval;
// Save the preferences in C++.
new WebContentsPreferences(web_contents(), options);
// If there's already a WebContentsPreferences object, we created it as part
// of the webContents.setWindowOpenHandler path, so don't overwrite it.
if (!WebContentsPreferences::From(web_contents())) {
new WebContentsPreferences(web_contents(), options);
}
// Trigger re-calculation of webkit prefs.
web_contents()->NotifyPreferencesChanged();
@ -778,18 +782,44 @@ void WebContents::WebContentsCreatedWithFullParams(
tracker->referrer = params.referrer.To<content::Referrer>();
tracker->raw_features = params.raw_features;
tracker->body = params.body;
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
gin_helper::Dictionary dict;
gin::ConvertFromV8(isolate, pending_child_web_preferences_.Get(isolate),
&dict);
pending_child_web_preferences_.Reset();
// Associate the preferences passed in via `setWindowOpenHandler` with the
// content::WebContents that was just created for the child window. These
// preferences will be picked up by the RenderWidgetHost via its call to the
// delegate's OverrideWebkitPrefs.
new WebContentsPreferences(new_contents, dict);
}
bool WebContents::IsWebContentsCreationOverridden(
content::SiteInstance* source_site_instance,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url) {
if (Emit("-will-add-new-contents", target_url, frame_name)) {
return true;
}
return false;
const content::mojom::CreateNewWindowParams& params) {
bool default_prevented = Emit("-will-add-new-contents", params.target_url,
params.frame_name, params.raw_features);
// If the app prevented the default, redirect to CreateCustomWebContents,
// which always returns nullptr, which will result in the window open being
// prevented (window.open() will return null in the renderer).
return default_prevented;
}
void WebContents::SetNextChildWebPreferences(
const gin_helper::Dictionary preferences) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
// Store these prefs for when Chrome calls WebContentsCreatedWithFullParams
// with the new child contents.
pending_child_web_preferences_.Reset(isolate, preferences.GetHandle());
}
content::WebContents* WebContents::CreateCustomWebContents(
@ -1076,7 +1106,7 @@ void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {
if (web_contents()->GetRenderViewHost() == render_view_host) {
// When the RVH that has been deleted is the current RVH it means that the
// the web contents are being closed. This is communicated by this event.
// Currently tracked by guest-window-manager.js to destroy the
// Currently tracked by guest-window-manager.ts to destroy the
// BrowserWindow.
Emit("current-render-view-deleted",
render_view_host->GetProcess()->GetID());
@ -2738,11 +2768,11 @@ void WebContents::DoGetZoomLevel(DoGetZoomLevelCallback callback) {
std::move(callback).Run(GetZoomLevel());
}
std::vector<base::FilePath::StringType> WebContents::GetPreloadPaths() const {
std::vector<base::FilePath> WebContents::GetPreloadPaths() const {
auto result = SessionPreferences::GetValidPreloads(GetBrowserContext());
if (auto* web_preferences = WebContentsPreferences::From(web_contents())) {
base::FilePath::StringType preload;
base::FilePath preload;
if (web_preferences->GetPreloadPath(&preload)) {
result.emplace_back(preload);
}
@ -3017,6 +3047,8 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
.SetMethod("_getPrinters", &WebContents::GetPrinterList)
.SetMethod("_printToPDF", &WebContents::PrintToPDF)
#endif
.SetMethod("_setNextChildWebPreferences",
&WebContents::SetNextChildWebPreferences)
.SetMethod("addWorkSpace", &WebContents::AddWorkSpace)
.SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace)
.SetMethod("showDefinitionForSelection",

View file

@ -252,6 +252,8 @@ class WebContents : public gin::Wrappable<WebContents>,
v8::Local<v8::Promise> PrintToPDF(base::DictionaryValue settings);
#endif
void SetNextChildWebPreferences(const gin_helper::Dictionary);
// DevTools workspace api.
void AddWorkSpace(gin::Arguments* args, const base::FilePath& path);
void RemoveWorkSpace(gin::Arguments* args, const base::FilePath& path);
@ -354,7 +356,7 @@ class WebContents : public gin::Wrappable<WebContents>,
const scoped_refptr<network::ResourceRequestBody>& body);
// Returns the preload script path of current WebContents.
std::vector<base::FilePath::StringType> GetPreloadPaths() const;
std::vector<base::FilePath> GetPreloadPaths() const;
// Returns the web preferences of current WebContents.
v8::Local<v8::Value> GetWebPreferences(v8::Isolate* isolate) const;
@ -456,8 +458,7 @@ class WebContents : public gin::Wrappable<WebContents>,
content::SiteInstance* source_site_instance,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url) override;
const content::mojom::CreateNewWindowParams& params) override;
content::WebContents* CreateCustomWebContents(
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
@ -679,6 +680,8 @@ class WebContents : public gin::Wrappable<WebContents>,
// Observers of this WebContents.
base::ObserverList<ExtendedWebContentsObserver> observers_;
v8::Global<v8::Value> pending_child_web_preferences_;
bool initially_shown_ = true;
service_manager::BinderRegistryWithArgs<content::RenderFrameHost*> registry_;

View file

@ -225,12 +225,6 @@ void BindNetworkHintsHandler(
NetworkHintsHandlerImpl::Create(frame_host, std::move(receiver));
}
#if defined(OS_WIN)
const base::FilePath::StringPieceType kPathDelimiter = FILE_PATH_LITERAL(";");
#else
const base::FilePath::StringPieceType kPathDelimiter = FILE_PATH_LITERAL(":");
#endif
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
// Used by the GetPrivilegeRequiredByUrl() and GetProcessPrivilege() functions
// below. Extension, and isolated apps require different privileges to be
@ -610,13 +604,21 @@ void ElectronBrowserClient::OverrideWebkitPrefs(
? blink::mojom::PreferredColorScheme::kDark
: blink::mojom::PreferredColorScheme::kLight;
auto* web_contents = content::WebContents::FromRenderViewHost(host);
auto preloads =
SessionPreferences::GetValidPreloads(web_contents->GetBrowserContext());
if (!preloads.empty())
prefs->preloads = preloads;
if (CanUseCustomSiteInstance())
prefs->disable_electron_site_instance_overrides = true;
SetFontDefaults(prefs);
// Custom preferences of guest page.
auto* web_contents = content::WebContents::FromRenderViewHost(host);
auto* web_preferences = WebContentsPreferences::From(web_contents);
if (web_preferences)
if (web_preferences) {
web_preferences->OverrideWebkitPrefs(prefs);
}
}
void ElectronBrowserClient::SetCanUseCustomSiteInstance(bool should_disable) {
@ -803,16 +805,6 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches(
if (web_preferences)
web_preferences->AppendCommandLineSwitches(
command_line, IsRendererSubFrame(process_id));
auto preloads = SessionPreferences::GetValidPreloads(
web_contents->GetBrowserContext());
if (!preloads.empty())
command_line->AppendSwitchNative(
switches::kPreloadScripts,
base::JoinString(preloads, kPathDelimiter));
if (CanUseCustomSiteInstance()) {
command_line->AppendSwitch(
switches::kDisableElectronSiteInstanceOverrides);
}
}
}
}

View file

@ -21,6 +21,7 @@ template <typename T>
class EventEmitterMixin {
public:
// this.emit(name, new Event(), args...);
// Returns true if event.preventDefault() was called during processing.
template <typename... Args>
bool Emit(base::StringPiece name, Args&&... args) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();

View file

@ -25,13 +25,13 @@ SessionPreferences* SessionPreferences::FromBrowserContext(
}
// static
std::vector<base::FilePath::StringType> SessionPreferences::GetValidPreloads(
std::vector<base::FilePath> SessionPreferences::GetValidPreloads(
content::BrowserContext* context) {
std::vector<base::FilePath::StringType> result;
std::vector<base::FilePath> result;
if (auto* self = FromBrowserContext(context)) {
for (const auto& preload : self->preloads()) {
if (base::FilePath(preload).IsAbsolute()) {
if (preload.IsAbsolute()) {
result.emplace_back(preload);
} else {
LOG(ERROR) << "preload script must have absolute path: " << preload;

View file

@ -17,24 +17,22 @@ class SessionPreferences : public base::SupportsUserData::Data {
public:
static SessionPreferences* FromBrowserContext(
content::BrowserContext* context);
static std::vector<base::FilePath::StringType> GetValidPreloads(
static std::vector<base::FilePath> GetValidPreloads(
content::BrowserContext* context);
explicit SessionPreferences(content::BrowserContext* context);
~SessionPreferences() override;
void set_preloads(const std::vector<base::FilePath::StringType>& preloads) {
void set_preloads(const std::vector<base::FilePath>& preloads) {
preloads_ = preloads;
}
const std::vector<base::FilePath::StringType>& preloads() const {
return preloads_;
}
const std::vector<base::FilePath>& preloads() const { return preloads_; }
private:
// The user data key.
static int kLocatorKey;
std::vector<base::FilePath::StringType> preloads_;
std::vector<base::FilePath> preloads_;
};
} // namespace electron

View file

@ -241,22 +241,22 @@ bool WebContentsPreferences::GetPreference(base::StringPiece name,
return GetAsString(&preference_, name, value);
}
bool WebContentsPreferences::GetPreloadPath(
base::FilePath::StringType* path) const {
bool WebContentsPreferences::GetPreloadPath(base::FilePath* path) const {
DCHECK(path);
base::FilePath::StringType preload;
if (GetAsString(&preference_, options::kPreloadScript, &preload)) {
if (base::FilePath(preload).IsAbsolute()) {
base::FilePath::StringType preload_path;
if (GetAsString(&preference_, options::kPreloadScript, &preload_path)) {
base::FilePath preload(preload_path);
if (preload.IsAbsolute()) {
*path = std::move(preload);
return true;
} else {
LOG(ERROR) << "preload script must have absolute path.";
}
} else if (GetAsString(&preference_, options::kPreloadURL, &preload)) {
} else if (GetAsString(&preference_, options::kPreloadURL, &preload_path)) {
// Translate to file path if there is "preload-url" option.
base::FilePath preload_path;
if (net::FileURLToFilePath(GURL(preload), &preload_path)) {
*path = std::move(preload_path.value());
base::FilePath preload;
if (net::FileURLToFilePath(GURL(preload_path), &preload)) {
*path = std::move(preload);
return true;
} else {
LOG(ERROR) << "preload url must be file:// protocol.";
@ -287,36 +287,16 @@ WebContentsPreferences* WebContentsPreferences::From(
void WebContentsPreferences::AppendCommandLineSwitches(
base::CommandLine* command_line,
bool is_subframe) {
// Check if plugins are enabled.
if (IsEnabled(options::kPlugins))
command_line->AppendSwitch(switches::kEnablePlugins);
// Experimental flags.
if (IsEnabled(options::kExperimentalFeatures))
command_line->AppendSwitch(
::switches::kEnableExperimentalWebPlatformFeatures);
// Check if we have node integration specified.
if (IsEnabled(options::kNodeIntegration))
command_line->AppendSwitch(switches::kNodeIntegration);
// Whether to enable node integration in Worker.
if (IsEnabled(options::kNodeIntegrationInWorker))
command_line->AppendSwitch(switches::kNodeIntegrationInWorker);
// Check if webview tag creation is enabled, default to nodeIntegration value.
if (IsEnabled(options::kWebviewTag))
command_line->AppendSwitch(switches::kWebviewTag);
// Sandbox can be enabled for renderer processes hosting cross-origin frames
// unless nodeIntegrationInSubFrames is enabled
bool can_sandbox_frame =
is_subframe && !IsEnabled(options::kNodeIntegrationInSubFrames);
// If the `sandbox` option was passed to the BrowserWindow's webPreferences,
// pass `--enable-sandbox` to the renderer so it won't have any node.js
// integration. Otherwise disable Chromium sandbox, unless app.enableSandbox()
// was called.
if (IsEnabled(options::kSandbox) || can_sandbox_frame) {
command_line->AppendSwitch(switches::kEnableSandbox);
} else if (!command_line->HasSwitch(switches::kEnableSandbox)) {
@ -324,15 +304,6 @@ void WebContentsPreferences::AppendCommandLineSwitches(
command_line->AppendSwitch(::switches::kNoZygote);
}
// Check if nativeWindowOpen is enabled.
if (IsEnabled(options::kNativeWindowOpen))
command_line->AppendSwitch(switches::kNativeWindowOpen);
// The preload script.
base::FilePath::StringType preload;
if (GetPreloadPath(&preload))
command_line->AppendSwitchNative(switches::kPreloadScript, preload);
// Custom args for renderer process
auto* customArgs =
preference_.FindKeyOfType(options::kCustomArgs, base::Value::Type::LIST);
@ -343,46 +314,13 @@ void WebContentsPreferences::AppendCommandLineSwitches(
}
}
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
// Whether to enable the remote module
if (IsEnabled(options::kEnableRemoteModule, false))
command_line->AppendSwitch(switches::kEnableRemoteModule);
#endif
// Run Electron APIs and preload script in isolated world
if (IsEnabled(options::kContextIsolation))
command_line->AppendSwitch(switches::kContextIsolation);
if (IsEnabled(options::kWorldSafeExecuteJavaScript))
command_line->AppendSwitch(switches::kWorldSafeExecuteJavaScript);
// --background-color.
std::string s;
if (GetAsString(&preference_, options::kBackgroundColor, &s)) {
command_line->AppendSwitchASCII(switches::kBackgroundColor, s);
} else if (!IsEnabled(options::kOffscreen)) {
// For non-OSR WebContents, we expect to have white background, see
// https://github.com/electron/electron/issues/13764 for more.
command_line->AppendSwitchASCII(switches::kBackgroundColor, "#fff");
}
// --offscreen
// TODO(loc): Offscreen is duplicated in WebPreferences because it's needed
// earlier than we can get WebPreferences at the moment.
if (IsEnabled(options::kOffscreen)) {
command_line->AppendSwitch(options::kOffscreen);
}
// --guest-instance-id, which is used to identify guest WebContents.
int guest_instance_id = 0;
if (GetAsInteger(&preference_, options::kGuestInstanceID, &guest_instance_id))
command_line->AppendSwitchASCII(switches::kGuestInstanceID,
base::NumberToString(guest_instance_id));
// Pass the opener's window id.
int opener_id;
if (GetAsInteger(&preference_, options::kOpenerID, &opener_id))
command_line->AppendSwitchASCII(switches::kOpenerID,
base::NumberToString(opener_id));
#if defined(OS_MAC)
// Enable scroll bounce.
if (IsEnabled(options::kScrollBounce))
@ -402,6 +340,7 @@ void WebContentsPreferences::AppendCommandLineSwitches(
}
}
std::string s;
// Enable blink features.
if (GetAsString(&preference_, options::kEnableBlinkFeatures, &s))
command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures, s);
@ -410,39 +349,8 @@ void WebContentsPreferences::AppendCommandLineSwitches(
if (GetAsString(&preference_, options::kDisableBlinkFeatures, &s))
command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures, s);
if (guest_instance_id) {
// Webview `document.visibilityState` tracks window visibility so we need
// to let it know if the window happens to be hidden right now.
auto* manager = WebViewManager::GetWebViewManager(web_contents_);
if (manager) {
auto* embedder = manager->GetEmbedder(guest_instance_id);
if (embedder) {
auto* relay = NativeWindowRelay::FromWebContents(embedder);
if (relay) {
auto* window = relay->GetNativeWindow();
if (window) {
const bool visible = window->IsVisible() && !window->IsMinimized();
if (!visible) {
command_line->AppendSwitch(switches::kHiddenPage);
}
}
}
}
}
}
if (IsEnabled(options::kNodeIntegrationInSubFrames))
command_line->AppendSwitch(switches::kNodeIntegrationInSubFrames);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
if (IsEnabled(options::kSpellcheck)) {
command_line->AppendSwitch(switches::kEnableSpellcheck);
}
#endif
// Whether to allow the WebSQL api
if (IsEnabled(options::kEnableWebSQL))
command_line->AppendSwitch(switches::kEnableWebSQL);
if (IsEnabled(options::kNodeIntegrationInWorker))
command_line->AppendSwitch(switches::kNodeIntegrationInWorker);
// We are appending args to a webContents so let's save the current state
// of our preferences object so that during the lifetime of the WebContents
@ -507,6 +415,87 @@ void WebContentsPreferences::OverrideWebkitPrefs(
if (GetAsString(&preference_, "defaultEncoding", &encoding))
prefs->default_encoding = encoding;
// --background-color.
std::string color;
if (GetAsString(&preference_, options::kBackgroundColor, &color)) {
prefs->background_color = color;
} else if (!IsEnabled(options::kOffscreen)) {
prefs->background_color = "#fff";
}
// Pass the opener's window id.
int opener_id;
if (GetAsInteger(&preference_, options::kOpenerID, &opener_id))
prefs->opener_id = opener_id;
// Run Electron APIs and preload script in isolated world
prefs->context_isolation = IsEnabled(options::kContextIsolation);
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
// Whether to enable the remote module
prefs->enable_remote_module = IsEnabled(options::kEnableRemoteModule, false);
#endif
prefs->world_safe_execute_javascript =
IsEnabled(options::kWorldSafeExecuteJavaScript);
int guest_instance_id = 0;
if (GetAsInteger(&preference_, options::kGuestInstanceID, &guest_instance_id))
prefs->guest_instance_id = guest_instance_id;
prefs->hidden_page = false;
if (guest_instance_id) {
// Webview `document.visibilityState` tracks window visibility so we need
// to let it know if the window happens to be hidden right now.
auto* manager = WebViewManager::GetWebViewManager(web_contents_);
if (manager) {
auto* embedder = manager->GetEmbedder(guest_instance_id);
if (embedder) {
auto* relay = NativeWindowRelay::FromWebContents(embedder);
if (relay) {
auto* window = relay->GetNativeWindow();
if (window) {
const bool visible = window->IsVisible() && !window->IsMinimized();
if (!visible) {
prefs->hidden_page = true;
}
}
}
}
}
}
prefs->offscreen = IsEnabled(options::kOffscreen);
// The preload script.
GetPreloadPath(&prefs->preload);
// Check if nativeWindowOpen is enabled.
prefs->native_window_open = IsEnabled(options::kNativeWindowOpen);
// Check if we have node integration specified.
prefs->node_integration = IsEnabled(options::kNodeIntegration);
// Whether to enable node integration in Worker.
prefs->node_integration_in_worker =
IsEnabled(options::kNodeIntegrationInWorker);
prefs->node_integration_in_sub_frames =
IsEnabled(options::kNodeIntegrationInSubFrames);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
prefs->enable_spellcheck = IsEnabled(options::kSpellcheck);
#endif
// Check if plugins are enabled.
prefs->enable_plugins = IsEnabled(options::kPlugins);
// Check if webview tag creation is enabled, default to nodeIntegration value.
prefs->webview_tag = IsEnabled(options::kWebviewTag);
// Whether to allow the WebSQL api
prefs->enable_websql = IsEnabled(options::kEnableWebSQL);
std::string v8_cache_options;
if (GetAsString(&preference_, "v8CacheOptions", &v8_cache_options)) {
if (v8_cache_options == "none") {

View file

@ -59,7 +59,7 @@ class WebContentsPreferences
bool GetPreference(base::StringPiece name, std::string* value) const;
// Returns the preload script path.
bool GetPreloadPath(base::FilePath::StringType* path) const;
bool GetPreloadPath(base::FilePath* path) const;
// Returns the web preferences.
base::Value* preference() { return &preference_; }

View file

@ -40,6 +40,10 @@ void WebViewGuestDelegate::AttachToIframe(
content::WebContents* guest_web_contents = api_web_contents_->web_contents();
// Force a refresh of the webPreferences so that OverrideWebkitPrefs runs on
// the new web contents before the renderer process initializes.
// guest_web_contents->NotifyPreferencesChanged();
// Attach this inner WebContents |guest_web_contents| to the outer
// WebContents |embedder_web_contents|. The outer WebContents's
// frame |embedder_frame| hosts the inner WebContents.