| // 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<base::FilePath>::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::CreateSequencedTaskRunner( |
| {base::ThreadPool(), 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<base::FilePath>& 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, nullptr); |
| } |
| |
| void CefDevToolsFileManager::CanceledFileSaveAs(const std::string& url) { |
| base::Value url_value(url); |
| CallClientFunction("DevToolsAPI.canceledSaveURL", &url_value, nullptr, |
| nullptr); |
| } |
| |
| 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, nullptr, nullptr); |
| } |
| |
| 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), base::NullCallback()); |
| } |