diff --git a/BUILD.gn b/BUILD.gn index 661df53db..c325679b9 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -327,6 +327,8 @@ static_library("libcef_static") { "libcef/browser/context_menu_params_impl.h", "libcef/browser/cookie_manager_impl.cc", "libcef/browser/cookie_manager_impl.h", + "libcef/browser/devtools/devtools_file_manager.cc", + "libcef/browser/devtools/devtools_file_manager.h", "libcef/browser/devtools/devtools_frontend.cc", "libcef/browser/devtools/devtools_frontend.h", "libcef/browser/devtools/devtools_manager_delegate.cc", diff --git a/libcef/browser/devtools/devtools_file_manager.cc b/libcef/browser/devtools/devtools_file_manager.cc new file mode 100644 index 000000000..61f0fb775 --- /dev/null +++ b/libcef/browser/devtools/devtools_file_manager.cc @@ -0,0 +1,203 @@ +// Copyright 2019 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#include "libcef/browser/devtools/devtools_file_manager.h" + +#include "libcef/browser/browser_host_impl.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/json/json_writer.h" +#include "base/lazy_instance.h" +#include "base/path_service.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/post_task.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/value_conversions.h" +#include "base/values.h" +#include "chrome/common/pref_names.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "content/public/browser/web_contents.h" + +namespace { + +base::LazyInstance::Leaky g_last_save_path = + LAZY_INSTANCE_INITIALIZER; + +void WriteToFile(const base::FilePath& path, const std::string& content) { + DCHECK(!path.empty()); + base::WriteFile(path, content.c_str(), content.length()); +} + +void AppendToFile(const base::FilePath& path, const std::string& content) { + DCHECK(!path.empty()); + base::AppendToFile(path, content.c_str(), content.size()); +} + +} // namespace + +CefDevToolsFileManager::CefDevToolsFileManager(CefBrowserHostImpl* browser_impl, + PrefService* prefs) + : browser_impl_(browser_impl), + prefs_(prefs), + file_task_runner_( + base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})), + weak_factory_(this) {} + +void CefDevToolsFileManager::SaveToFile(const std::string& url, + const std::string& content, + bool save_as) { + Save(url, content, save_as, + base::Bind(&CefDevToolsFileManager::FileSavedAs, + weak_factory_.GetWeakPtr(), url), + base::Bind(&CefDevToolsFileManager::CanceledFileSaveAs, + weak_factory_.GetWeakPtr(), url)); +} + +void CefDevToolsFileManager::AppendToFile(const std::string& url, + const std::string& content) { + Append(url, content, + base::Bind(&CefDevToolsFileManager::AppendedTo, + weak_factory_.GetWeakPtr(), url)); +} + +void CefDevToolsFileManager::Save(const std::string& url, + const std::string& content, + bool save_as, + const SaveCallback& saveCallback, + const CancelCallback& cancelCallback) { + auto it = saved_files_.find(url); + if (it != saved_files_.end() && !save_as) { + SaveAsFileSelected(url, content, saveCallback, it->second); + return; + } + + const base::DictionaryValue* file_map = + prefs_->GetDictionary(prefs::kDevToolsEditedFiles); + base::FilePath initial_path; + + const base::Value* path_value; + if (file_map->Get(base::MD5String(url), &path_value)) { + // Ignore base::GetValueAsFilePath() failure since we handle empty + // |initial_path| below. + ignore_result(base::GetValueAsFilePath(*path_value, &initial_path)); + } + + if (initial_path.empty()) { + GURL gurl(url); + std::string suggested_file_name = + gurl.is_valid() ? gurl.ExtractFileName() : url; + + if (suggested_file_name.length() > 64) + suggested_file_name = suggested_file_name.substr(0, 64); + + if (!g_last_save_path.Pointer()->empty()) { + initial_path = g_last_save_path.Pointer()->DirName().AppendASCII( + suggested_file_name); + } else { + // Use the temp directory. It may be an empty value. + base::PathService::Get(base::DIR_TEMP, &initial_path); + initial_path = initial_path.AppendASCII(suggested_file_name); + } + } + + CefFileDialogRunner::FileChooserParams params; + params.mode = blink::mojom::FileChooserParams::Mode::kSave; + if (!initial_path.empty()) { + params.default_file_name = initial_path; + if (!initial_path.Extension().empty()) { + params.accept_types.push_back(CefString(initial_path.Extension())); + } + } + + browser_impl_->RunFileChooser( + params, base::Bind(&CefDevToolsFileManager::SaveAsDialogDismissed, + weak_factory_.GetWeakPtr(), url, content, saveCallback, + cancelCallback)); +} + +void CefDevToolsFileManager::SaveAsDialogDismissed( + const std::string& url, + const std::string& content, + const SaveCallback& saveCallback, + const CancelCallback& cancelCallback, + int selected_accept_filter, + const std::vector& file_paths) { + if (file_paths.size() == 1) { + SaveAsFileSelected(url, content, saveCallback, file_paths[0]); + } else { + cancelCallback.Run(); + } +} + +void CefDevToolsFileManager::SaveAsFileSelected(const std::string& url, + const std::string& content, + const SaveCallback& callback, + const base::FilePath& path) { + *g_last_save_path.Pointer() = path; + saved_files_[url] = path; + + DictionaryPrefUpdate update(prefs_, prefs::kDevToolsEditedFiles); + base::DictionaryValue* files_map = update.Get(); + files_map->SetKey(base::MD5String(url), base::CreateFilePathValue(path)); + std::string file_system_path = path.AsUTF8Unsafe(); + callback.Run(file_system_path); + file_task_runner_->PostTask(FROM_HERE, + base::BindOnce(&::WriteToFile, path, content)); +} + +void CefDevToolsFileManager::FileSavedAs(const std::string& url, + const std::string& file_system_path) { + base::Value url_value(url); + base::Value file_system_path_value(file_system_path); + CallClientFunction("DevToolsAPI.savedURL", &url_value, + &file_system_path_value, NULL); +} + +void CefDevToolsFileManager::CanceledFileSaveAs(const std::string& url) { + base::Value url_value(url); + CallClientFunction("DevToolsAPI.canceledSaveURL", &url_value, NULL, NULL); +} + +void CefDevToolsFileManager::Append(const std::string& url, + const std::string& content, + const AppendCallback& callback) { + auto it = saved_files_.find(url); + if (it == saved_files_.end()) + return; + callback.Run(); + file_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&::AppendToFile, it->second, content)); +} + +void CefDevToolsFileManager::AppendedTo(const std::string& url) { + base::Value url_value(url); + CallClientFunction("DevToolsAPI.appendedToURL", &url_value, NULL, NULL); +} + +void CefDevToolsFileManager::CallClientFunction( + const std::string& function_name, + const base::Value* arg1, + const base::Value* arg2, + const base::Value* arg3) { + std::string javascript = function_name + "("; + if (arg1) { + std::string json; + base::JSONWriter::Write(*arg1, &json); + javascript.append(json); + if (arg2) { + base::JSONWriter::Write(*arg2, &json); + javascript.append(", ").append(json); + if (arg3) { + base::JSONWriter::Write(*arg3, &json); + javascript.append(", ").append(json); + } + } + } + javascript.append(");"); + browser_impl_->web_contents()->GetMainFrame()->ExecuteJavaScript( + base::UTF8ToUTF16(javascript)); +} diff --git a/libcef/browser/devtools/devtools_file_manager.h b/libcef/browser/devtools/devtools_file_manager.h new file mode 100644 index 000000000..275ad51b1 --- /dev/null +++ b/libcef/browser/devtools/devtools_file_manager.h @@ -0,0 +1,81 @@ +// Copyright 2019 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#ifndef CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_FILE_MANAGER_H_ +#define CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_FILE_MANAGER_H_ + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" + +#include + +namespace base { +class FilePath; +class SequencedTaskRunner; +class Value; +} // namespace base + +class CefBrowserHostImpl; +class PrefService; + +// File management helper for DevTools. +// Based on chrome/browser/devtools/devtools_ui_bindings.cc and +// chrome/browser/devtools/devtools_file_helper.cc. +class CefDevToolsFileManager { + public: + CefDevToolsFileManager(CefBrowserHostImpl* browser_impl, PrefService* prefs); + + void SaveToFile(const std::string& url, + const std::string& content, + bool save_as); + void AppendToFile(const std::string& url, const std::string& content); + + private: + // SaveToFile implementation: + typedef base::Callback SaveCallback; + typedef base::Callback CancelCallback; + void Save(const std::string& url, + const std::string& content, + bool save_as, + const SaveCallback& saveCallback, + const CancelCallback& cancelCallback); + void SaveAsDialogDismissed(const std::string& url, + const std::string& content, + const SaveCallback& saveCallback, + const CancelCallback& cancelCallback, + int selected_accept_filter, + const std::vector& file_paths); + void SaveAsFileSelected(const std::string& url, + const std::string& content, + const SaveCallback& callback, + const base::FilePath& path); + void FileSavedAs(const std::string& url, const std::string& file_system_path); + void CanceledFileSaveAs(const std::string& url); + + // AppendToFile implementation: + typedef base::Callback AppendCallback; + void Append(const std::string& url, + const std::string& content, + const AppendCallback& callback); + void AppendedTo(const std::string& url); + + void CallClientFunction(const std::string& function_name, + const base::Value* arg1, + const base::Value* arg2, + const base::Value* arg3); + + // Guaranteed to outlive this object. + CefBrowserHostImpl* browser_impl_; + PrefService* prefs_; + + typedef std::map PathsMap; + PathsMap saved_files_; + scoped_refptr file_task_runner_; + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CefDevToolsFileManager); +}; + +#endif // CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_FILE_MANAGER_H_ diff --git a/libcef/browser/devtools/devtools_frontend.cc b/libcef/browser/devtools/devtools_frontend.cc index a075a55d2..785b8d8a7 100644 --- a/libcef/browser/devtools/devtools_frontend.cc +++ b/libcef/browser/devtools/devtools_frontend.cc @@ -190,6 +190,7 @@ CefDevToolsFrontend::CefDevToolsFrontend( frontend_browser_(frontend_browser), inspected_contents_(inspected_contents), inspect_element_at_(inspect_element_at), + file_manager_(frontend_browser.get(), GetPrefs()), weak_factory_(this) {} CefDevToolsFrontend::~CefDevToolsFrontend() { @@ -355,6 +356,22 @@ void CefDevToolsFrontend::HandleMessageFromDevToolsFrontend( if (!params->GetString(0, &origin) || !params->GetString(1, &script)) return; extensions_api_[origin + "/"] = script; + } else if (method == "save" && params->GetSize() == 3) { + std::string url; + std::string content; + bool save_as; + if (!params->GetString(0, &url) || !params->GetString(1, &content) || + !params->GetBoolean(2, &save_as)) { + return; + } + file_manager_.SaveToFile(url, content, save_as); + } else if (method == "append" && params->GetSize() == 2) { + std::string url; + std::string content; + if (!params->GetString(0, &url) || !params->GetString(1, &content)) { + return; + } + file_manager_.AppendToFile(url, content); } else { return; } diff --git a/libcef/browser/devtools/devtools_frontend.h b/libcef/browser/devtools/devtools_frontend.h index 5d31ae792..1f7f3678e 100644 --- a/libcef/browser/devtools/devtools_frontend.h +++ b/libcef/browser/devtools/devtools_frontend.h @@ -8,6 +8,7 @@ #include #include "libcef/browser/browser_host_impl.h" +#include "libcef/browser/devtools/devtools_file_manager.h" #include "base/compiler_specific.h" #include "base/macros.h" @@ -93,6 +94,7 @@ class CefDevToolsFrontend : public content::WebContentsObserver, PendingRequestsMap pending_requests_; using ExtensionsAPIs = std::map; ExtensionsAPIs extensions_api_; + CefDevToolsFileManager file_manager_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(CefDevToolsFrontend); diff --git a/libcef/browser/prefs/browser_prefs.cc b/libcef/browser/prefs/browser_prefs.cc index 644052cf3..08608799a 100644 --- a/libcef/browser/prefs/browser_prefs.cc +++ b/libcef/browser/prefs/browser_prefs.cc @@ -192,6 +192,7 @@ std::unique_ptr CreatePrefService(Profile* profile, // DevTools preferences. // Based on DevToolsWindow::RegisterProfilePrefs. registry->RegisterDictionaryPref(prefs::kDevToolsPreferences); + registry->RegisterDictionaryPref(prefs::kDevToolsEditedFiles); if (command_line->HasSwitch(switches::kEnablePreferenceTesting)) { // Preferences used with unit tests.