// Copyright (c) 2014 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "shell/browser/api/electron_api_web_contents.h" #include #include #include #include #include #include #include #include "base/containers/id_map.h" #include "base/files/file_util.h" #include "base/ignore_result.h" #include "base/json/json_reader.h" #include "base/no_destructor.h" #include "base/strings/utf_string_conversions.h" #include "base/task/current_thread.h" #include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "base/threading/scoped_blocking_call.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" #include "chrome/browser/ui/views/eye_dropper/eye_dropper.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/security_state/content/content_utils.h" #include "components/security_state/core/security_state.h" #include "content/browser/renderer_host/frame_tree_node.h" // nogncheck #include "content/browser/renderer_host/render_frame_host_manager.h" // nogncheck #include "content/browser/renderer_host/render_widget_host_impl.h" // nogncheck #include "content/browser/renderer_host/render_widget_host_view_base.h" // nogncheck #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/context_menu_params.h" #include "content/public/browser/desktop_media_id.h" #include "content/public/browser/desktop_streams_registry.h" #include "content/public/browser/download_request_utils.h" #include "content/public/browser/favicon_status.h" #include "content/public/browser/file_select_listener.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/service_worker_context.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/common/referrer_type_converters.h" #include "content/public/common/webplugininfo.h" #include "electron/buildflags/buildflags.h" #include "electron/shell/common/api/api.mojom.h" #include "gin/arguments.h" #include "gin/data_object_builder.h" #include "gin/handle.h" #include "gin/object_template_builder.h" #include "gin/wrappable.h" #include "mojo/public/cpp/bindings/associated_remote.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/system/platform_handle.h" #include "ppapi/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h" #include "printing/print_job_constants.h" #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h" #include "services/service_manager/public/cpp/interface_provider.h" #include "shell/browser/api/electron_api_browser_window.h" #include "shell/browser/api/electron_api_debugger.h" #include "shell/browser/api/electron_api_session.h" #include "shell/browser/api/electron_api_web_frame_main.h" #include "shell/browser/api/message_port.h" #include "shell/browser/browser.h" #include "shell/browser/child_web_contents_tracker.h" #include "shell/browser/electron_autofill_driver_factory.h" #include "shell/browser/electron_browser_client.h" #include "shell/browser/electron_browser_context.h" #include "shell/browser/electron_browser_main_parts.h" #include "shell/browser/electron_javascript_dialog_manager.h" #include "shell/browser/electron_navigation_throttle.h" #include "shell/browser/file_select_helper.h" #include "shell/browser/native_window.h" #include "shell/browser/session_preferences.h" #include "shell/browser/ui/drag_util.h" #include "shell/browser/ui/file_dialog.h" #include "shell/browser/ui/inspectable_web_contents.h" #include "shell/browser/ui/inspectable_web_contents_view.h" #include "shell/browser/web_contents_permission_helper.h" #include "shell/browser/web_contents_preferences.h" #include "shell/browser/web_contents_zoom_controller.h" #include "shell/browser/web_view_guest_delegate.h" #include "shell/browser/web_view_manager.h" #include "shell/common/api/electron_api_native_image.h" #include "shell/common/api/electron_bindings.h" #include "shell/common/color_util.h" #include "shell/common/electron_constants.h" #include "shell/common/gin_converters/base_converter.h" #include "shell/common/gin_converters/blink_converter.h" #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/content_converter.h" #include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_converters/frame_converter.h" #include "shell/common/gin_converters/gfx_converter.h" #include "shell/common/gin_converters/gurl_converter.h" #include "shell/common/gin_converters/image_converter.h" #include "shell/common/gin_converters/net_converter.h" #include "shell/common/gin_converters/value_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/object_template_builder.h" #include "shell/common/language_util.h" #include "shell/common/mouse_util.h" #include "shell/common/node_includes.h" #include "shell/common/options_switches.h" #include "shell/common/process_util.h" #include "shell/common/v8_value_serializer.h" #include "storage/browser/file_system/isolated_context.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/blink/public/common/input/web_input_event.h" #include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h" #include "third_party/blink/public/common/page/page_zoom.h" #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h" #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h" #include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h" #include "third_party/blink/public/mojom/renderer_preferences.mojom.h" #include "ui/base/cursor/cursor.h" #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" #include "ui/display/screen.h" #include "ui/events/base_event_utils.h" #if BUILDFLAG(ENABLE_OSR) #include "shell/browser/osr/osr_render_widget_host_view.h" #include "shell/browser/osr/osr_web_contents_view.h" #endif #if !defined(OS_MAC) #include "ui/aura/window.h" #else #include "ui/base/cocoa/defaults_utils.h" #endif #if defined(OS_LINUX) #include "ui/views/linux_ui/linux_ui.h" #endif #if defined(OS_LINUX) || defined(OS_WIN) #include "ui/gfx/font_render_params.h" #endif #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) #include "extensions/browser/script_executor.h" #include "extensions/browser/view_type_utils.h" #include "extensions/common/mojom/view_type.mojom.h" #include "shell/browser/extensions/electron_extension_web_contents_observer.h" #endif #if BUILDFLAG(ENABLE_PRINTING) #include "components/printing/browser/print_manager_utils.h" #include "printing/backend/print_backend.h" // nogncheck #include "printing/mojom/print.mojom.h" #include "shell/browser/printing/print_preview_message_handler.h" #include "shell/browser/printing/print_view_manager_electron.h" #if defined(OS_WIN) #include "printing/backend/win_helper.h" #endif #endif #if BUILDFLAG(ENABLE_PICTURE_IN_PICTURE) #include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h" #endif #if BUILDFLAG(ENABLE_PDF_VIEWER) #include "components/pdf/browser/pdf_web_contents_helper.h" // nogncheck #include "shell/browser/electron_pdf_web_contents_helper_client.h" #endif #if BUILDFLAG(ENABLE_PLUGINS) #include "content/public/browser/plugin_service.h" #endif #ifndef MAS_BUILD #include "chrome/browser/hang_monitor/hang_crash_dump.h" // nogncheck #endif namespace gin { #if BUILDFLAG(ENABLE_PRINTING) template <> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, printing::mojom::MarginType* out) { std::string type; if (ConvertFromV8(isolate, val, &type)) { if (type == "default") { *out = printing::mojom::MarginType::kDefaultMargins; return true; } if (type == "none") { *out = printing::mojom::MarginType::kNoMargins; return true; } if (type == "printableArea") { *out = printing::mojom::MarginType::kPrintableAreaMargins; return true; } if (type == "custom") { *out = printing::mojom::MarginType::kCustomMargins; return true; } } return false; } }; template <> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, printing::mojom::DuplexMode* out) { std::string mode; if (ConvertFromV8(isolate, val, &mode)) { if (mode == "simplex") { *out = printing::mojom::DuplexMode::kSimplex; return true; } if (mode == "longEdge") { *out = printing::mojom::DuplexMode::kLongEdge; return true; } if (mode == "shortEdge") { *out = printing::mojom::DuplexMode::kShortEdge; return true; } } return false; } }; #endif template <> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, WindowOpenDisposition val) { std::string disposition = "other"; switch (val) { case WindowOpenDisposition::CURRENT_TAB: disposition = "default"; break; case WindowOpenDisposition::NEW_FOREGROUND_TAB: disposition = "foreground-tab"; break; case WindowOpenDisposition::NEW_BACKGROUND_TAB: disposition = "background-tab"; break; case WindowOpenDisposition::NEW_POPUP: case WindowOpenDisposition::NEW_WINDOW: disposition = "new-window"; break; case WindowOpenDisposition::SAVE_TO_DISK: disposition = "save-to-disk"; break; default: break; } return gin::ConvertToV8(isolate, disposition); } }; template <> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, content::SavePageType* out) { std::string save_type; if (!ConvertFromV8(isolate, val, &save_type)) return false; save_type = base::ToLowerASCII(save_type); if (save_type == "htmlonly") { *out = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; } else if (save_type == "htmlcomplete") { *out = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; } else if (save_type == "mhtml") { *out = content::SAVE_PAGE_TYPE_AS_MHTML; } else { return false; } return true; } }; template <> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, electron::api::WebContents::Type val) { using Type = electron::api::WebContents::Type; std::string type; switch (val) { case Type::kBackgroundPage: type = "backgroundPage"; break; case Type::kBrowserWindow: type = "window"; break; case Type::kBrowserView: type = "browserView"; break; case Type::kRemote: type = "remote"; break; case Type::kWebView: type = "webview"; break; case Type::kOffScreen: type = "offscreen"; break; default: break; } return gin::ConvertToV8(isolate, type); } static bool FromV8(v8::Isolate* isolate, v8::Local val, electron::api::WebContents::Type* out) { using Type = electron::api::WebContents::Type; std::string type; if (!ConvertFromV8(isolate, val, &type)) return false; if (type == "backgroundPage") { *out = Type::kBackgroundPage; } else if (type == "browserView") { *out = Type::kBrowserView; } else if (type == "webview") { *out = Type::kWebView; #if BUILDFLAG(ENABLE_OSR) } else if (type == "offscreen") { *out = Type::kOffScreen; #endif } else { return false; } return true; } }; template <> struct Converter> { static v8::Local ToV8( v8::Isolate* isolate, const scoped_refptr& val) { gin_helper::Dictionary dict(isolate, v8::Object::New(isolate)); dict.Set("id", val->GetId()); dict.Set("url", val->GetURL().spec()); return dict.GetHandle(); } }; } // namespace gin namespace electron { namespace api { namespace { base::IDMap& GetAllWebContents() { static base::NoDestructor> s_all_web_contents; return *s_all_web_contents; } // Called when CapturePage is done. void OnCapturePageDone(gin_helper::Promise promise, const SkBitmap& bitmap) { // Hack to enable transparency in captured image promise.Resolve(gfx::Image::CreateFrom1xBitmap(bitmap)); } absl::optional GetCursorBlinkInterval() { #if defined(OS_MAC) base::TimeDelta interval; if (ui::TextInsertionCaretBlinkPeriod(&interval)) return interval; #elif defined(OS_LINUX) if (auto* linux_ui = views::LinuxUI::instance()) return linux_ui->GetCursorBlinkInterval(); #elif defined(OS_WIN) const auto system_msec = ::GetCaretBlinkTime(); if (system_msec != 0) { return (system_msec == INFINITE) ? base::TimeDelta() : base::Milliseconds(system_msec); } #endif return absl::nullopt; } #if BUILDFLAG(ENABLE_PRINTING) // This will return false if no printer with the provided device_name can be // found on the network. We need to check this because Chromium does not do // sanity checking of device_name validity and so will crash on invalid names. bool IsDeviceNameValid(const std::u16string& device_name) { #if defined(OS_MAC) base::ScopedCFTypeRef new_printer_id( base::SysUTF16ToCFStringRef(device_name)); PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get()); bool printer_exists = new_printer != nullptr; PMRelease(new_printer); return printer_exists; #elif defined(OS_WIN) printing::ScopedPrinterHandle printer; return printer.OpenPrinterWithName(base::as_wcstr(device_name)); #else return true; #endif } std::pair GetDefaultPrinterAsync() { #if defined(OS_WIN) // Blocking is needed here because Windows printer drivers are oftentimes // not thread-safe and have to be accessed on the UI thread. base::ThreadRestrictions::ScopedAllowIO allow_io; #endif scoped_refptr print_backend = printing::PrintBackend::CreateInstance( g_browser_process->GetApplicationLocale()); std::string printer_name; printing::mojom::ResultCode code = print_backend->GetDefaultPrinterName(printer_name); // We don't want to return if this fails since some devices won't have a // default printer. if (code != printing::mojom::ResultCode::kSuccess) LOG(ERROR) << "Failed to get default printer name"; // Check for existing printers and pick the first one should it exist. if (printer_name.empty()) { printing::PrinterList printers; if (print_backend->EnumeratePrinters(&printers) != printing::mojom::ResultCode::kSuccess) return std::make_pair("Failed to enumerate printers", std::u16string()); if (!printers.empty()) printer_name = printers.front().printer_name; } return std::make_pair(std::string(), base::UTF8ToUTF16(printer_name)); } // Copied from // chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc:L36-L54 scoped_refptr CreatePrinterHandlerTaskRunner() { // USER_VISIBLE because the result is displayed in the print preview dialog. #if !defined(OS_WIN) static constexpr base::TaskTraits kTraits = { base::MayBlock(), base::TaskPriority::USER_VISIBLE}; #endif #if defined(USE_CUPS) // CUPS is thread safe. return base::ThreadPool::CreateTaskRunner(kTraits); #elif defined(OS_WIN) // Windows drivers are likely not thread-safe and need to be accessed on the // UI thread. return content::GetUIThreadTaskRunner( {base::MayBlock(), base::TaskPriority::USER_VISIBLE}); #else // Be conservative on unsupported platforms. return base::ThreadPool::CreateSingleThreadTaskRunner(kTraits); #endif } #endif struct UserDataLink : public base::SupportsUserData::Data { explicit UserDataLink(base::WeakPtr contents) : web_contents(contents) {} base::WeakPtr web_contents; }; const void* kElectronApiWebContentsKey = &kElectronApiWebContentsKey; const char kRootName[] = ""; struct FileSystem { FileSystem() = default; FileSystem(const std::string& type, const std::string& file_system_name, const std::string& root_url, const std::string& file_system_path) : type(type), file_system_name(file_system_name), root_url(root_url), file_system_path(file_system_path) {} std::string type; std::string file_system_name; std::string root_url; std::string file_system_path; }; std::string RegisterFileSystem(content::WebContents* web_contents, const base::FilePath& path) { auto* isolated_context = storage::IsolatedContext::GetInstance(); std::string root_name(kRootName); storage::IsolatedContext::ScopedFSHandle file_system = isolated_context->RegisterFileSystemForPath( storage::kFileSystemTypeLocal, std::string(), path, &root_name); content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance(); content::RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); int renderer_id = render_view_host->GetProcess()->GetID(); policy->GrantReadFileSystem(renderer_id, file_system.id()); policy->GrantWriteFileSystem(renderer_id, file_system.id()); policy->GrantCreateFileForFileSystem(renderer_id, file_system.id()); policy->GrantDeleteFromFileSystem(renderer_id, file_system.id()); if (!policy->CanReadFile(renderer_id, path)) policy->GrantReadFile(renderer_id, path); return file_system.id(); } FileSystem CreateFileSystemStruct(content::WebContents* web_contents, const std::string& file_system_id, const std::string& file_system_path, const std::string& type) { const GURL origin = web_contents->GetURL().DeprecatedGetOriginAsURL(); std::string file_system_name = storage::GetIsolatedFileSystemName(origin, file_system_id); std::string root_url = storage::GetIsolatedFileSystemRootURIString( origin, file_system_id, kRootName); return FileSystem(type, file_system_name, root_url, file_system_path); } std::unique_ptr CreateFileSystemValue( const FileSystem& file_system) { auto file_system_value = std::make_unique(); file_system_value->SetString("type", file_system.type); file_system_value->SetString("fileSystemName", file_system.file_system_name); file_system_value->SetString("rootURL", file_system.root_url); file_system_value->SetString("fileSystemPath", file_system.file_system_path); return file_system_value; } void WriteToFile(const base::FilePath& path, const std::string& content) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::WILL_BLOCK); DCHECK(!path.empty()); base::WriteFile(path, content.data(), content.size()); } void AppendToFile(const base::FilePath& path, const std::string& content) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::WILL_BLOCK); DCHECK(!path.empty()); base::AppendToFile(path, content); } PrefService* GetPrefService(content::WebContents* web_contents) { auto* context = web_contents->GetBrowserContext(); return static_cast(context)->prefs(); } std::map GetAddedFileSystemPaths( content::WebContents* web_contents) { auto* pref_service = GetPrefService(web_contents); const base::DictionaryValue* file_system_paths_value = pref_service->GetDictionary(prefs::kDevToolsFileSystemPaths); std::map result; if (file_system_paths_value) { base::DictionaryValue::Iterator it(*file_system_paths_value); for (; !it.IsAtEnd(); it.Advance()) { std::string type = it.value().is_string() ? it.value().GetString() : std::string(); result[it.key()] = type; } } return result; } bool IsDevToolsFileSystemAdded(content::WebContents* web_contents, const std::string& file_system_path) { auto file_system_paths = GetAddedFileSystemPaths(web_contents); return file_system_paths.find(file_system_path) != file_system_paths.end(); } } // namespace #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) WebContents::Type GetTypeFromViewType(extensions::mojom::ViewType view_type) { switch (view_type) { case extensions::mojom::ViewType::kExtensionBackgroundPage: return WebContents::Type::kBackgroundPage; case extensions::mojom::ViewType::kAppWindow: case extensions::mojom::ViewType::kComponent: case extensions::mojom::ViewType::kExtensionDialog: case extensions::mojom::ViewType::kExtensionPopup: case extensions::mojom::ViewType::kBackgroundContents: case extensions::mojom::ViewType::kExtensionGuest: case extensions::mojom::ViewType::kTabContents: case extensions::mojom::ViewType::kInvalid: return WebContents::Type::kRemote; } } #endif WebContents::WebContents(v8::Isolate* isolate, content::WebContents* web_contents) : content::WebContentsObserver(web_contents), type_(Type::kRemote), id_(GetAllWebContents().Add(this)), devtools_file_system_indexer_( base::MakeRefCounted()), exclusive_access_manager_(std::make_unique(this)), file_task_runner_( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) #if BUILDFLAG(ENABLE_PRINTING) , print_task_runner_(CreatePrinterHandlerTaskRunner()) #endif { #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) // WebContents created by extension host will have valid ViewType set. extensions::mojom::ViewType view_type = extensions::GetViewType(web_contents); if (view_type != extensions::mojom::ViewType::kInvalid) { InitWithExtensionView(isolate, web_contents, view_type); } extensions::ElectronExtensionWebContentsObserver::CreateForWebContents( web_contents); script_executor_ = std::make_unique(web_contents); #endif auto session = Session::CreateFrom(isolate, GetBrowserContext()); session_.Reset(isolate, session.ToV8()); web_contents->SetUserAgentOverride(blink::UserAgentOverride::UserAgentOnly( GetBrowserContext()->GetUserAgent()), false); web_contents->SetUserData(kElectronApiWebContentsKey, std::make_unique(GetWeakPtr())); InitZoomController(web_contents, gin::Dictionary::CreateEmpty(isolate)); } WebContents::WebContents(v8::Isolate* isolate, std::unique_ptr web_contents, Type type) : content::WebContentsObserver(web_contents.get()), type_(type), id_(GetAllWebContents().Add(this)), devtools_file_system_indexer_( base::MakeRefCounted()), exclusive_access_manager_(std::make_unique(this)), file_task_runner_( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) #if BUILDFLAG(ENABLE_PRINTING) , print_task_runner_(CreatePrinterHandlerTaskRunner()) #endif { DCHECK(type != Type::kRemote) << "Can't take ownership of a remote WebContents"; auto session = Session::CreateFrom(isolate, GetBrowserContext()); session_.Reset(isolate, session.ToV8()); InitWithSessionAndOptions(isolate, std::move(web_contents), session, gin::Dictionary::CreateEmpty(isolate)); } WebContents::WebContents(v8::Isolate* isolate, const gin_helper::Dictionary& options) : id_(GetAllWebContents().Add(this)), devtools_file_system_indexer_( base::MakeRefCounted()), exclusive_access_manager_(std::make_unique(this)), file_task_runner_( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) #if BUILDFLAG(ENABLE_PRINTING) , print_task_runner_(CreatePrinterHandlerTaskRunner()) #endif { // Read options. options.Get("backgroundThrottling", &background_throttling_); // Get type options.Get("type", &type_); #if BUILDFLAG(ENABLE_OSR) bool b = false; if (options.Get(options::kOffscreen, &b) && b) type_ = Type::kOffScreen; #endif // Init embedder earlier options.Get("embedder", &embedder_); // Whether to enable DevTools. options.Get("devTools", &enable_devtools_); // BrowserViews are not attached to a window initially so they should start // off as hidden. This is also important for compositor recycling. See: // https://github.com/electron/electron/pull/21372 bool initially_shown = type_ != Type::kBrowserView; options.Get(options::kShow, &initially_shown); // Obtain the session. std::string partition; gin::Handle session; if (options.Get("session", &session) && !session.IsEmpty()) { } else if (options.Get("partition", &partition)) { session = Session::FromPartition(isolate, partition); } else { // Use the default session if not specified. session = Session::FromPartition(isolate, ""); } session_.Reset(isolate, session.ToV8()); std::unique_ptr web_contents; if (IsGuest()) { scoped_refptr site_instance = content::SiteInstance::CreateForURL(session->browser_context(), GURL("chrome-guest://fake-host")); content::WebContents::CreateParams params(session->browser_context(), site_instance); guest_delegate_ = std::make_unique(embedder_->web_contents(), this); params.guest_delegate = guest_delegate_.get(); #if BUILDFLAG(ENABLE_OSR) if (embedder_ && embedder_->IsOffScreen()) { auto* view = new OffScreenWebContentsView( false, base::BindRepeating(&WebContents::OnPaint, base::Unretained(this))); params.view = view; params.delegate_view = view; web_contents = content::WebContents::Create(params); view->SetWebContents(web_contents.get()); } else { #endif web_contents = content::WebContents::Create(params); #if BUILDFLAG(ENABLE_OSR) } } else if (IsOffScreen()) { bool transparent = false; options.Get(options::kTransparent, &transparent); content::WebContents::CreateParams params(session->browser_context()); auto* view = new OffScreenWebContentsView( transparent, base::BindRepeating(&WebContents::OnPaint, base::Unretained(this))); params.view = view; params.delegate_view = view; web_contents = content::WebContents::Create(params); view->SetWebContents(web_contents.get()); #endif } else { content::WebContents::CreateParams params(session->browser_context()); params.initially_hidden = !initially_shown; web_contents = content::WebContents::Create(params); } InitWithSessionAndOptions(isolate, std::move(web_contents), session, options); } void WebContents::InitZoomController(content::WebContents* web_contents, const gin_helper::Dictionary& options) { WebContentsZoomController::CreateForWebContents(web_contents); zoom_controller_ = WebContentsZoomController::FromWebContents(web_contents); double zoom_factor; if (options.Get(options::kZoomFactor, &zoom_factor)) zoom_controller_->SetDefaultZoomFactor(zoom_factor); } void WebContents::InitWithSessionAndOptions( v8::Isolate* isolate, std::unique_ptr owned_web_contents, gin::Handle session, const gin_helper::Dictionary& options) { Observe(owned_web_contents.get()); InitWithWebContents(std::move(owned_web_contents), session->browser_context(), IsGuest()); inspectable_web_contents_->GetView()->SetDelegate(this); auto* prefs = web_contents()->GetMutableRendererPrefs(); // Collect preferred languages from OS and browser process. accept_languages // effects HTTP header, navigator.languages, and CJK fallback font selection. // // Note that an application locale set to the browser process might be // different with the one set to the preference list. // (e.g. overridden with --lang) std::string accept_languages = g_browser_process->GetApplicationLocale() + ","; for (auto const& language : electron::GetPreferredLanguages()) { if (language == g_browser_process->GetApplicationLocale()) continue; accept_languages += language + ","; } accept_languages.pop_back(); prefs->accept_languages = accept_languages; #if defined(OS_LINUX) || defined(OS_WIN) // Update font settings. static const gfx::FontRenderParams params( gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr)); prefs->should_antialias_text = params.antialiasing; prefs->use_subpixel_positioning = params.subpixel_positioning; prefs->hinting = params.hinting; prefs->use_autohinter = params.autohinter; prefs->use_bitmaps = params.use_bitmaps; prefs->subpixel_rendering = params.subpixel_rendering; #endif // Honor the system's cursor blink rate settings if (auto interval = GetCursorBlinkInterval()) prefs->caret_blink_interval = *interval; // Save the preferences in C++. // 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(); WebContentsPermissionHelper::CreateForWebContents(web_contents()); InitZoomController(web_contents(), options); #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) extensions::ElectronExtensionWebContentsObserver::CreateForWebContents( web_contents()); script_executor_ = std::make_unique(web_contents()); #endif AutofillDriverFactory::CreateForWebContents(web_contents()); web_contents()->SetUserAgentOverride(blink::UserAgentOverride::UserAgentOnly( GetBrowserContext()->GetUserAgent()), false); if (IsGuest()) { NativeWindow* owner_window = nullptr; if (embedder_) { // New WebContents's owner_window is the embedder's owner_window. auto* relay = NativeWindowRelay::FromWebContents(embedder_->web_contents()); if (relay) owner_window = relay->GetNativeWindow(); } if (owner_window) SetOwnerWindow(owner_window); } web_contents()->SetUserData(kElectronApiWebContentsKey, std::make_unique(GetWeakPtr())); } #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) void WebContents::InitWithExtensionView(v8::Isolate* isolate, content::WebContents* web_contents, extensions::mojom::ViewType view_type) { // Must reassign type prior to calling `Init`. type_ = GetTypeFromViewType(view_type); if (type_ == Type::kRemote) return; if (type_ == Type::kBackgroundPage) // non-background-page WebContents are retained by other classes. We need // to pin here to prevent background-page WebContents from being GC'd. // The background page api::WebContents will live until the underlying // content::WebContents is destroyed. Pin(isolate); // Allow toggling DevTools for background pages Observe(web_contents); InitWithWebContents(std::unique_ptr(web_contents), GetBrowserContext(), IsGuest()); inspectable_web_contents_->GetView()->SetDelegate(this); } #endif void WebContents::InitWithWebContents( std::unique_ptr web_contents, ElectronBrowserContext* browser_context, bool is_guest) { browser_context_ = browser_context; web_contents->SetDelegate(this); #if BUILDFLAG(ENABLE_PRINTING) PrintPreviewMessageHandler::CreateForWebContents(web_contents.get()); PrintViewManagerElectron::CreateForWebContents(web_contents.get()); printing::CreateCompositeClientIfNeeded(web_contents.get(), browser_context->GetUserAgent()); #endif #if BUILDFLAG(ENABLE_PDF_VIEWER) pdf::PDFWebContentsHelper::CreateForWebContentsWithClient( web_contents.get(), std::make_unique()); #endif // Determine whether the WebContents is offscreen. auto* web_preferences = WebContentsPreferences::From(web_contents.get()); offscreen_ = web_preferences && web_preferences->IsOffscreen(); // Create InspectableWebContents. inspectable_web_contents_ = std::make_unique( std::move(web_contents), browser_context->prefs(), is_guest); inspectable_web_contents_->SetDelegate(this); } WebContents::~WebContents() { // clear out objects that have been granted permissions so that when // WebContents::RenderFrameDeleted is called as a result of WebContents // destruction it doesn't try to clear out a granted_devices_ // on a destructed object. granted_devices_.clear(); if (!inspectable_web_contents_) { WebContentsDestroyed(); return; } inspectable_web_contents_->GetView()->SetDelegate(nullptr); // This event is only for internal use, which is emitted when WebContents is // being destroyed. Emit("will-destroy"); // For guest view based on OOPIF, the WebContents is released by the embedder // frame, and we need to clear the reference to the memory. bool not_owned_by_this = IsGuest() && attached_; #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) // And background pages are owned by extensions::ExtensionHost. if (type_ == Type::kBackgroundPage) not_owned_by_this = true; #endif if (not_owned_by_this) { inspectable_web_contents_->ReleaseWebContents(); WebContentsDestroyed(); } // InspectableWebContents will be automatically destroyed. } void WebContents::DeleteThisIfAlive() { // It is possible that the FirstWeakCallback has been called but the // SecondWeakCallback has not, in this case the garbage collection of // WebContents has already started and we should not |delete this|. // Calling |GetWrapper| can detect this corner case. auto* isolate = JavascriptEnvironment::GetIsolate(); v8::HandleScope scope(isolate); v8::Local wrapper; if (!GetWrapper(isolate).ToLocal(&wrapper)) return; delete this; } void WebContents::Destroy() { // The content::WebContents should be destroyed asyncronously when possible // as user may choose to destroy WebContents during an event of it. if (Browser::Get()->is_shutting_down() || IsGuest()) { DeleteThisIfAlive(); } else { base::PostTask(FROM_HERE, {content::BrowserThread::UI}, base::BindOnce( [](base::WeakPtr contents) { if (contents) contents->DeleteThisIfAlive(); }, GetWeakPtr())); } } bool WebContents::DidAddMessageToConsole( content::WebContents* source, blink::mojom::ConsoleMessageLevel level, const std::u16string& message, int32_t line_no, const std::u16string& source_id) { return Emit("console-message", static_cast(level), message, line_no, source_id); } void WebContents::OnCreateWindow( const GURL& target_url, const content::Referrer& referrer, const std::string& frame_name, WindowOpenDisposition disposition, const std::string& features, const scoped_refptr& body) { Emit("-new-window", target_url, frame_name, disposition, features, referrer, body); } void WebContents::WebContentsCreatedWithFullParams( content::WebContents* source_contents, int opener_render_process_id, int opener_render_frame_id, const content::mojom::CreateNewWindowParams& params, content::WebContents* new_contents) { ChildWebContentsTracker::CreateForWebContents(new_contents); auto* tracker = ChildWebContentsTracker::FromWebContents(new_contents); tracker->url = params.target_url; tracker->frame_name = params.frame_name; tracker->referrer = params.referrer.To(); 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 content::mojom::CreateNewWindowParams& params) { bool default_prevented = Emit( "-will-add-new-contents", params.target_url, params.frame_name, params.raw_features, params.disposition, *params.referrer, params.body); // 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( content::RenderFrameHost* opener, content::SiteInstance* source_site_instance, bool is_new_browsing_instance, const GURL& opener_url, const std::string& frame_name, const GURL& target_url, const content::StoragePartitionId& partition_id, content::SessionStorageNamespace* session_storage_namespace) { return nullptr; } void WebContents::AddNewContents( content::WebContents* source, std::unique_ptr new_contents, const GURL& target_url, WindowOpenDisposition disposition, const gfx::Rect& initial_rect, bool user_gesture, bool* was_blocked) { auto* tracker = ChildWebContentsTracker::FromWebContents(new_contents.get()); DCHECK(tracker); v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); auto api_web_contents = CreateAndTake(isolate, std::move(new_contents), Type::kBrowserWindow); // We call RenderFrameCreated here as at this point the empty "about:blank" // render frame has already been created. If the window never navigates again // RenderFrameCreated won't be called and certain prefs like // "kBackgroundColor" will not be applied. auto* frame = api_web_contents->MainFrame(); if (frame) { api_web_contents->HandleNewRenderFrame(frame); } if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture, initial_rect.x(), initial_rect.y(), initial_rect.width(), initial_rect.height(), tracker->url, tracker->frame_name, tracker->referrer, tracker->raw_features, tracker->body)) { api_web_contents->Destroy(); } } content::WebContents* WebContents::OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) { auto weak_this = GetWeakPtr(); if (params.disposition != WindowOpenDisposition::CURRENT_TAB) { Emit("-new-window", params.url, "", params.disposition, "", params.referrer, params.post_data); return nullptr; } if (!weak_this || !web_contents()) return nullptr; content::NavigationController::LoadURLParams load_url_params(params.url); load_url_params.referrer = params.referrer; load_url_params.transition_type = params.transition; load_url_params.extra_headers = params.extra_headers; load_url_params.should_replace_current_entry = params.should_replace_current_entry; load_url_params.is_renderer_initiated = params.is_renderer_initiated; load_url_params.started_from_context_menu = params.started_from_context_menu; load_url_params.initiator_origin = params.initiator_origin; load_url_params.source_site_instance = params.source_site_instance; load_url_params.frame_tree_node_id = params.frame_tree_node_id; load_url_params.redirect_chain = params.redirect_chain; load_url_params.has_user_gesture = params.user_gesture; load_url_params.blob_url_loader_factory = params.blob_url_loader_factory; load_url_params.href_translate = params.href_translate; load_url_params.reload_type = params.reload_type; if (params.post_data) { load_url_params.load_type = content::NavigationController::LOAD_TYPE_HTTP_POST; load_url_params.post_data = params.post_data; } source->GetController().LoadURLWithParams(load_url_params); return source; } void WebContents::BeforeUnloadFired(content::WebContents* tab, bool proceed, bool* proceed_to_fire_unload) { if (type_ == Type::kBrowserWindow || type_ == Type::kOffScreen || type_ == Type::kBrowserView) *proceed_to_fire_unload = proceed; else *proceed_to_fire_unload = true; // Note that Chromium does not emit this for navigations. Emit("before-unload-fired", proceed); } void WebContents::SetContentsBounds(content::WebContents* source, const gfx::Rect& rect) { for (ExtendedWebContentsObserver& observer : observers_) observer.OnSetContentBounds(rect); } void WebContents::CloseContents(content::WebContents* source) { Emit("close"); auto* autofill_driver_factory = AutofillDriverFactory::FromWebContents(web_contents()); if (autofill_driver_factory) { autofill_driver_factory->CloseAllPopups(); } for (ExtendedWebContentsObserver& observer : observers_) observer.OnCloseContents(); } void WebContents::ActivateContents(content::WebContents* source) { for (ExtendedWebContentsObserver& observer : observers_) observer.OnActivateContents(); } void WebContents::UpdateTargetURL(content::WebContents* source, const GURL& url) { Emit("update-target-url", url); } bool WebContents::HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { if (type_ == Type::kWebView && embedder_) { // Send the unhandled keyboard events back to the embedder. return embedder_->HandleKeyboardEvent(source, event); } else { return PlatformHandleKeyboardEvent(source, event); } } #if !defined(OS_MAC) // NOTE: The macOS version of this function is found in // electron_api_web_contents_mac.mm, as it requires calling into objective-C // code. bool WebContents::PlatformHandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { // Escape exits tabbed fullscreen mode. if (event.windows_key_code == ui::VKEY_ESCAPE && is_html_fullscreen()) { ExitFullscreenModeForTab(source); return true; } // Check if the webContents has preferences and to ignore shortcuts auto* web_preferences = WebContentsPreferences::From(source); if (web_preferences && web_preferences->ShouldIgnoreMenuShortcuts()) return false; // Let the NativeWindow handle other parts. if (owner_window()) { owner_window()->HandleKeyboardEvent(source, event); return true; } return false; } #endif content::KeyboardEventProcessingResult WebContents::PreHandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { if (event.GetType() == blink::WebInputEvent::Type::kRawKeyDown || event.GetType() == blink::WebInputEvent::Type::kKeyUp) { bool prevent_default = Emit("before-input-event", event); if (prevent_default) { return content::KeyboardEventProcessingResult::HANDLED; } } return content::KeyboardEventProcessingResult::NOT_HANDLED; } void WebContents::ContentsZoomChange(bool zoom_in) { Emit("zoom-changed", zoom_in ? "in" : "out"); } Profile* WebContents::GetProfile() { return nullptr; } bool WebContents::IsFullscreen() const { return owner_window_ && owner_window_->IsFullscreen(); } void WebContents::EnterFullscreen(const GURL& url, ExclusiveAccessBubbleType bubble_type, const int64_t display_id) {} void WebContents::ExitFullscreen() {} void WebContents::UpdateExclusiveAccessExitBubbleContent( const GURL& url, ExclusiveAccessBubbleType bubble_type, ExclusiveAccessBubbleHideCallback bubble_first_hide_callback, bool force_update) {} void WebContents::OnExclusiveAccessUserInput() {} content::WebContents* WebContents::GetActiveWebContents() { return web_contents(); } bool WebContents::CanUserExitFullscreen() const { return true; } bool WebContents::IsExclusiveAccessBubbleDisplayed() const { return false; } void WebContents::EnterFullscreenModeForTab( content::RenderFrameHost* requesting_frame, const blink::mojom::FullscreenOptions& options) { auto* source = content::WebContents::FromRenderFrameHost(requesting_frame); auto* permission_helper = WebContentsPermissionHelper::FromWebContents(source); auto callback = base::BindRepeating(&WebContents::OnEnterFullscreenModeForTab, base::Unretained(this), requesting_frame, options); permission_helper->RequestFullscreenPermission(callback); exclusive_access_manager_->fullscreen_controller()->EnterFullscreenModeForTab( requesting_frame, options.display_id); } void WebContents::OnEnterFullscreenModeForTab( content::RenderFrameHost* requesting_frame, const blink::mojom::FullscreenOptions& options, bool allowed) { if (!allowed || !owner_window_) return; auto* source = content::WebContents::FromRenderFrameHost(requesting_frame); if (IsFullscreenForTabOrPending(source)) { DCHECK_EQ(fullscreen_frame_, source->GetFocusedFrame()); return; } SetHtmlApiFullscreen(true); if (native_fullscreen_) { // Explicitly trigger a view resize, as the size is not actually changing if // the browser is fullscreened, too. source->GetRenderViewHost()->GetWidget()->SynchronizeVisualProperties(); } } void WebContents::ExitFullscreenModeForTab(content::WebContents* source) { if (!owner_window_) return; SetHtmlApiFullscreen(false); if (native_fullscreen_) { // Explicitly trigger a view resize, as the size is not actually changing if // the browser is fullscreened, too. Chrome does this indirectly from // `chrome/browser/ui/exclusive_access/fullscreen_controller.cc`. source->GetRenderViewHost()->GetWidget()->SynchronizeVisualProperties(); } exclusive_access_manager_->fullscreen_controller()->ExitFullscreenModeForTab( source); } void WebContents::RendererUnresponsive( content::WebContents* source, content::RenderWidgetHost* render_widget_host, base::RepeatingClosure hang_monitor_restarter) { Emit("unresponsive"); } void WebContents::RendererResponsive( content::WebContents* source, content::RenderWidgetHost* render_widget_host) { Emit("responsive"); } bool WebContents::HandleContextMenu(content::RenderFrameHost& render_frame_host, const content::ContextMenuParams& params) { Emit("context-menu", std::make_pair(params, &render_frame_host)); return true; } bool WebContents::OnGoToEntryOffset(int offset) { GoToOffset(offset); return false; } void WebContents::FindReply(content::WebContents* web_contents, int request_id, int number_of_matches, const gfx::Rect& selection_rect, int active_match_ordinal, bool final_update) { if (!final_update) return; v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); gin_helper::Dictionary result = gin::Dictionary::CreateEmpty(isolate); result.Set("requestId", request_id); result.Set("matches", number_of_matches); result.Set("selectionArea", selection_rect); result.Set("activeMatchOrdinal", active_match_ordinal); result.Set("finalUpdate", final_update); // Deprecate after 2.0 Emit("found-in-page", result.GetHandle()); } void WebContents::RequestKeyboardLock(content::WebContents* web_contents, bool esc_key_locked) { exclusive_access_manager_->keyboard_lock_controller()->RequestKeyboardLock( web_contents, esc_key_locked); } void WebContents::CancelKeyboardLockRequest( content::WebContents* web_contents) { exclusive_access_manager_->keyboard_lock_controller() ->CancelKeyboardLockRequest(web_contents); } bool WebContents::CheckMediaAccessPermission( content::RenderFrameHost* render_frame_host, const GURL& security_origin, blink::mojom::MediaStreamType type) { auto* web_contents = content::WebContents::FromRenderFrameHost(render_frame_host); auto* permission_helper = WebContentsPermissionHelper::FromWebContents(web_contents); return permission_helper->CheckMediaAccessPermission(security_origin, type); } void WebContents::RequestMediaAccessPermission( content::WebContents* web_contents, const content::MediaStreamRequest& request, content::MediaResponseCallback callback) { auto* permission_helper = WebContentsPermissionHelper::FromWebContents(web_contents); permission_helper->RequestMediaAccessPermission(request, std::move(callback)); } void WebContents::RequestToLockMouse(content::WebContents* web_contents, bool user_gesture, bool last_unlocked_by_target) { auto* permission_helper = WebContentsPermissionHelper::FromWebContents(web_contents); permission_helper->RequestPointerLockPermission(user_gesture); } content::JavaScriptDialogManager* WebContents::GetJavaScriptDialogManager( content::WebContents* source) { if (!dialog_manager_) dialog_manager_ = std::make_unique(); return dialog_manager_.get(); } void WebContents::OnAudioStateChanged(bool audible) { Emit("-audio-state-changed", audible); } void WebContents::BeforeUnloadFired(bool proceed, const base::TimeTicks& proceed_time) { // Do nothing, we override this method just to avoid compilation error since // there are two virtual functions named BeforeUnloadFired. } void WebContents::HandleNewRenderFrame( content::RenderFrameHost* render_frame_host) { auto* rwhv = render_frame_host->GetView(); if (!rwhv) return; // Set the background color of RenderWidgetHostView. auto* web_preferences = WebContentsPreferences::From(web_contents()); if (web_preferences) { bool guest = IsGuest() || type_ == Type::kBrowserView; absl::optional color = guest ? SK_ColorTRANSPARENT : web_preferences->GetBackgroundColor(); web_contents()->SetPageBaseBackgroundColor(color); rwhv->SetBackgroundColor(color.value_or(SK_ColorWHITE)); } if (!background_throttling_) render_frame_host->GetRenderViewHost()->SetSchedulerThrottling(false); auto* rwh_impl = static_cast(rwhv->GetRenderWidgetHost()); if (rwh_impl) rwh_impl->disable_hidden_ = !background_throttling_; auto* web_frame = WebFrameMain::FromRenderFrameHost(render_frame_host); if (web_frame) web_frame->Connect(); } void WebContents::RenderFrameCreated( content::RenderFrameHost* render_frame_host) { HandleNewRenderFrame(render_frame_host); // RenderFrameCreated is called for speculative frames which may not be // used in certain cross-origin navigations. Invoking // RenderFrameHost::GetLifecycleState currently crashes when called for // speculative frames so we need to filter it out for now. Check // https://crbug.com/1183639 for details on when this can be removed. auto* rfh_impl = static_cast(render_frame_host); if (rfh_impl->lifecycle_state() == content::RenderFrameHostImpl::LifecycleStateImpl::kSpeculative) { return; } content::RenderFrameHost::LifecycleState lifecycle_state = render_frame_host->GetLifecycleState(); if (lifecycle_state == content::RenderFrameHost::LifecycleState::kActive) { v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::HandleScope handle_scope(isolate); gin_helper::Dictionary details = gin_helper::Dictionary::CreateEmpty(isolate); details.SetGetter("frame", render_frame_host); Emit("frame-created", details); } } void WebContents::RenderFrameDeleted( content::RenderFrameHost* render_frame_host) { // A RenderFrameHost can be deleted when: // - A WebContents is removed and its containing frames are disposed. // - An