| // Copyright 2021 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "ui/shell_dialogs/select_file_dialog_linux_portal.h" | 
 |  | 
 | #include <string_view> | 
 |  | 
 | #include "base/check.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/logging.h" | 
 | #include "base/memory/ref_counted_memory.h" | 
 | #include "base/nix/xdg_util.h" | 
 | #include "base/no_destructor.h" | 
 | #include "base/notreached.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/task/sequenced_task_runner.h" | 
 | #include "base/time/time.h" | 
 | #include "components/dbus/thread_linux/dbus_thread_linux.h" | 
 | #include "components/dbus/utils/check_for_service_and_start.h" | 
 | #include "components/dbus/xdg/request.h" | 
 | #include "components/dbus/xdg/systemd.h" | 
 | #include "dbus/message.h" | 
 | #include "dbus/object_path.h" | 
 | #include "dbus/object_proxy.h" | 
 | #include "ui/aura/window_tree_host.h" | 
 | #include "ui/base/l10n/l10n_util.h" | 
 | #include "ui/gfx/native_widget_types.h" | 
 | #include "ui/linux/linux_ui_delegate.h" | 
 | #include "ui/shell_dialogs/selected_file_info.h" | 
 | #include "ui/strings/grit/ui_strings.h" | 
 | #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h" | 
 | #include "url/gurl.h" | 
 | #include "url/url_util.h" | 
 |  | 
 | namespace ui { | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr char kXdgPortalService[] = "org.freedesktop.portal.Desktop"; | 
 | constexpr char kXdgPortalObject[] = "/org/freedesktop/portal/desktop"; | 
 |  | 
 | constexpr int kXdgPortalRequiredVersion = 3; | 
 |  | 
 | constexpr char kFileChooserInterfaceName[] = | 
 |     "org.freedesktop.portal.FileChooser"; | 
 |  | 
 | constexpr char kFileChooserMethodOpenFile[] = "OpenFile"; | 
 | constexpr char kFileChooserMethodSaveFile[] = "SaveFile"; | 
 |  | 
 | constexpr char kFileChooserOptionAcceptLabel[] = "accept_label"; | 
 | constexpr char kFileChooserOptionMultiple[] = "multiple"; | 
 | constexpr char kFileChooserOptionDirectory[] = "directory"; | 
 | constexpr char kFileChooserOptionFilters[] = "filters"; | 
 | constexpr char kFileChooserOptionCurrentFilter[] = "current_filter"; | 
 | constexpr char kFileChooserOptionCurrentFolder[] = "current_folder"; | 
 | constexpr char kFileChooserOptionCurrentName[] = "current_name"; | 
 | constexpr char kFileChooserOptionModal[] = "modal"; | 
 |  | 
 | constexpr uint32_t kFileChooserFilterKindGlob = 0; | 
 |  | 
 | constexpr char kFileUriPrefix[] = "file://"; | 
 |  | 
 | enum class ServiceAvailability { | 
 |   kNotStarted, | 
 |   kInProgress, | 
 |   kNotAvailable, | 
 |   kAvailable, | 
 | }; | 
 |  | 
 | ServiceAvailability g_service_availability = ServiceAvailability::kNotStarted; | 
 |  | 
 | scoped_refptr<base::SequencedTaskRunner>& GetMainTaskRunner() { | 
 |   static base::NoDestructor<scoped_refptr<base::SequencedTaskRunner>> | 
 |       main_task_runner; | 
 |   return *main_task_runner; | 
 | } | 
 |  | 
 | void OnGetPropertyReply(dbus::Response* response) { | 
 |   if (!response) { | 
 |     g_service_availability = ServiceAvailability::kNotAvailable; | 
 |     return; | 
 |   } | 
 |  | 
 |   uint32_t version = 0; | 
 |   dbus::MessageReader reader(response); | 
 |   if (!reader.PopVariantOfUint32(&version)) { | 
 |     g_service_availability = ServiceAvailability::kNotAvailable; | 
 |     return; | 
 |   } | 
 |  | 
 |   g_service_availability = version >= kXdgPortalRequiredVersion | 
 |                                ? ServiceAvailability::kAvailable | 
 |                                : ServiceAvailability::kNotAvailable; | 
 | } | 
 |  | 
 | void OnServiceStarted(std::optional<bool> service_started) { | 
 |   if (!service_started.value_or(false)) { | 
 |     g_service_availability = ServiceAvailability::kNotAvailable; | 
 |     return; | 
 |   } | 
 |  | 
 |   dbus::ObjectProxy* portal = | 
 |       dbus_thread_linux::GetSharedSessionBus()->GetObjectProxy( | 
 |           kXdgPortalService, dbus::ObjectPath(kXdgPortalObject)); | 
 |  | 
 |   dbus::MethodCall get_property_call(DBUS_INTERFACE_PROPERTIES, "Get"); | 
 |   dbus::MessageWriter get_property_writer(&get_property_call); | 
 |   get_property_writer.AppendString(kFileChooserInterfaceName); | 
 |   get_property_writer.AppendString("version"); | 
 |   portal->CallMethod(&get_property_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, | 
 |                      base::BindOnce(&OnGetPropertyReply)); | 
 | } | 
 |  | 
 | void OnSystemdUnitStarted(dbus_xdg::SystemdUnitStatus) { | 
 |   dbus_utils::CheckForServiceAndStart(dbus_thread_linux::GetSharedSessionBus(), | 
 |                                       kXdgPortalService, | 
 |                                       base::BindOnce(&OnServiceStarted)); | 
 | } | 
 |  | 
 | DbusByteArray PathToByteArray(const base::FilePath& path) { | 
 |   return DbusByteArray(base::MakeRefCounted<base::RefCountedBytes>( | 
 |       base::as_bytes(base::span_with_nul_from_cstring_view( | 
 |           base::cstring_view(path.value()))))); | 
 | } | 
 |  | 
 | std::vector<base::FilePath> ConvertUrisToPaths( | 
 |     const std::vector<std::string>& uris) { | 
 |   std::vector<base::FilePath> paths; | 
 |   for (const std::string& uri : uris) { | 
 |     if (!base::StartsWith(uri, kFileUriPrefix, base::CompareCase::SENSITIVE)) { | 
 |       LOG(WARNING) << "Ignoring unknown file chooser URI: " << uri; | 
 |       continue; | 
 |     } | 
 |  | 
 |     std::string_view encoded_path(uri); | 
 |     encoded_path.remove_prefix(strlen(kFileUriPrefix)); | 
 |  | 
 |     url::RawCanonOutputT<char16_t> decoded_path; | 
 |     url::DecodeURLEscapeSequences( | 
 |         encoded_path, url::DecodeURLMode::kUTF8OrIsomorphic, &decoded_path); | 
 |     paths.emplace_back(base::UTF16ToUTF8(decoded_path.view())); | 
 |   } | 
 |   return paths; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | SelectFileDialogLinuxPortal::SelectFileDialogLinuxPortal( | 
 |     Listener* listener, | 
 |     std::unique_ptr<ui::SelectFilePolicy> policy) | 
 |     : SelectFileDialogLinux(listener, std::move(policy)) {} | 
 |  | 
 | SelectFileDialogLinuxPortal::~SelectFileDialogLinuxPortal() { | 
 |   if (reenable_window_event_handling_) { | 
 |     invoker_task_runner_->PostTask(FROM_HERE, | 
 |                                    std::move(reenable_window_event_handling_)); | 
 |   } | 
 | } | 
 |  | 
 | // static | 
 | void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() { | 
 |   if (g_service_availability != ServiceAvailability::kNotStarted) { | 
 |     return; | 
 |   } | 
 |   g_service_availability = ServiceAvailability::kInProgress; | 
 |  | 
 |   GetMainTaskRunner() = base::SequencedTaskRunner::GetCurrentDefault(); | 
 |  | 
 |   dbus_xdg::SetSystemdScopeUnitNameForXdgPortal( | 
 |       dbus_thread_linux::GetSharedSessionBus().get(), | 
 |       base::BindOnce(&OnSystemdUnitStarted)); | 
 | } | 
 |  | 
 | // static | 
 | bool SelectFileDialogLinuxPortal::IsPortalAvailable() { | 
 |   if (g_service_availability == ServiceAvailability::kInProgress) { | 
 |     LOG(WARNING) << "Portal availability checked before test was complete"; | 
 |   } | 
 |  | 
 |   return g_service_availability == ServiceAvailability::kAvailable; | 
 | } | 
 |  | 
 | bool SelectFileDialogLinuxPortal::IsRunning( | 
 |     gfx::NativeWindow parent_window) const { | 
 |   return parent_window && host_ && host_.get() == parent_window->GetHost(); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::SelectFileImpl( | 
 |     Type type, | 
 |     const std::u16string& title, | 
 |     const base::FilePath& default_path, | 
 |     const FileTypeInfo* file_types, | 
 |     int file_type_index, | 
 |     const base::FilePath::StringType& default_extension, | 
 |     gfx::NativeWindow owning_window, | 
 |     const GURL* caller) { | 
 |   type_ = type; | 
 |   invoker_task_runner_ = base::SequencedTaskRunner::GetCurrentDefault(); | 
 |  | 
 |   if (owning_window) { | 
 |     if (auto* root = owning_window->GetRootWindow()) { | 
 |       if (auto* host = root->GetNativeWindowProperty( | 
 |               views::DesktopWindowTreeHostLinux::kWindowKey)) { | 
 |         host_ = static_cast<aura::WindowTreeHost*>(host)->GetWeakPtr(); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   if (file_types) { | 
 |     set_file_types(*file_types); | 
 |   } | 
 |  | 
 |   set_file_type_index(file_type_index); | 
 |  | 
 |   PortalFilterSet filter_set = BuildFilterSet(); | 
 |  | 
 |   // Keep a copy of the filters so the index of the chosen one can be identified | 
 |   // and returned to listeners later. | 
 |   filters_ = filter_set.filters; | 
 |  | 
 |   if (host_) { | 
 |     auto* delegate = ui::LinuxUiDelegate::GetInstance(); | 
 |     if (delegate && | 
 |         delegate->ExportWindowHandle( | 
 |             host_->GetAcceleratedWidget(), | 
 |             base::BindOnce( | 
 |                 &SelectFileDialogLinuxPortal::SelectFileImplWithParentHandle, | 
 |                 this, title, default_path, filter_set, default_extension))) { | 
 |       // Return early to skip the fallback below. | 
 |       return; | 
 |     } else { | 
 |       LOG(WARNING) << "Failed to export window handle for portal select dialog"; | 
 |     } | 
 |   } | 
 |  | 
 |   // No parent, so just use a blank parent handle. | 
 |   SelectFileImplWithParentHandle(title, default_path, filter_set, | 
 |                                  default_extension, ""); | 
 | } | 
 |  | 
 | bool SelectFileDialogLinuxPortal::HasMultipleFileTypeChoicesImpl() { | 
 |   return file_types().extensions.size() > 1; | 
 | } | 
 |  | 
 | SelectFileDialogLinuxPortal::PortalFilterSet | 
 | SelectFileDialogLinuxPortal::BuildFilterSet() { | 
 |   PortalFilterSet filter_set; | 
 |  | 
 |   for (size_t i = 0; i < file_types().extensions.size(); ++i) { | 
 |     PortalFilter filter; | 
 |  | 
 |     std::vector<std::string> original_patterns; | 
 |  | 
 |     for (const std::string& extension : file_types().extensions[i]) { | 
 |       if (extension.empty()) { | 
 |         continue; | 
 |       } | 
 |  | 
 |       // We want to allow ASCII case-insensitive matches for the extension on | 
 |       // a per-character basis, since that's what | 
 |       // https://html.spec.whatwg.org/multipage/input.html#attr-input-accept | 
 |       // suggests.  For example, we should accept file.txt, file.TXT, or | 
 |       // file.tXt.  To do this, we expand characters with ASCII case | 
 |       // equivalents to be represented by [aA], as documented in | 
 |       // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html | 
 |       std::string pattern("*."); | 
 |       for (char c : extension) { | 
 |         char lower = base::ToLowerASCII(c); | 
 |         char upper = base::ToUpperASCII(c); | 
 |         if (upper != lower) { | 
 |           pattern.append({'[', lower, upper, ']'}); | 
 |         } else { | 
 |           pattern.append({c}); | 
 |         } | 
 |       } | 
 |       filter.patterns.push_back(pattern); | 
 |  | 
 |       // Save the original form for use as a fallback description. | 
 |       original_patterns.push_back("*." + extension); | 
 |     } | 
 |  | 
 |     if (filter.patterns.empty()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     // If there is no matching description, use a default description based on | 
 |     // the filter. | 
 |     if (i < file_types().extension_description_overrides.size()) { | 
 |       filter.name = | 
 |           base::UTF16ToUTF8(file_types().extension_description_overrides[i]); | 
 |     } | 
 |     if (filter.name.empty()) { | 
 |       filter.name = base::JoinString(original_patterns, ","); | 
 |     } | 
 |  | 
 |     // The -1 is required to match against the right filter because | 
 |     // |file_type_index_| is 1-indexed. | 
 |     if (i == file_type_index() - 1) { | 
 |       filter_set.default_filter = filter; | 
 |     } | 
 |  | 
 |     filter_set.filters.push_back(std::move(filter)); | 
 |   } | 
 |  | 
 |   if (file_types().include_all_files && !filter_set.filters.empty()) { | 
 |     // Add the *.* filter, but only if we have added other filters (otherwise it | 
 |     // is implied). | 
 |     PortalFilter filter; | 
 |     filter.name = l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES); | 
 |     filter.patterns.push_back("*.*"); | 
 |  | 
 |     filter_set.filters.push_back(std::move(filter)); | 
 |   } | 
 |  | 
 |   return filter_set; | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::SelectFileImplWithParentHandle( | 
 |     std::u16string title, | 
 |     base::FilePath default_path, | 
 |     PortalFilterSet filter_set, | 
 |     base::FilePath::StringType default_extension, | 
 |     std::string parent_handle) { | 
 |   bool default_path_exists = CallDirectoryExistsOnUIThread(default_path); | 
 |   GetMainTaskRunner()->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&SelectFileDialogLinuxPortal::SelectFileImplOnMainThread, | 
 |                      this, std::move(title), std::move(default_path), | 
 |                      default_path_exists, std::move(filter_set), | 
 |                      std::move(default_extension), std::move(parent_handle))); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::SelectFileImplOnMainThread( | 
 |     std::u16string title, | 
 |     base::FilePath default_path, | 
 |     const bool default_path_exists, | 
 |     PortalFilterSet filter_set, | 
 |     base::FilePath::StringType default_extension, | 
 |     std::string parent_handle) { | 
 |   CHECK(GetMainTaskRunner()->RunsTasksInCurrentSequence()); | 
 |  | 
 |   std::string method; | 
 |   switch (type_) { | 
 |     case SELECT_FOLDER: | 
 |     case SELECT_UPLOAD_FOLDER: | 
 |     case SELECT_EXISTING_FOLDER: | 
 |     case SELECT_OPEN_FILE: | 
 |     case SELECT_OPEN_MULTI_FILE: | 
 |       method = kFileChooserMethodOpenFile; | 
 |       break; | 
 |     case SELECT_SAVEAS_FILE: | 
 |       method = kFileChooserMethodSaveFile; | 
 |       break; | 
 |     case SELECT_NONE: | 
 |       NOTREACHED(); | 
 |   } | 
 |  | 
 |   std::string utf8_title; | 
 |   if (!title.empty()) { | 
 |     utf8_title = base::UTF16ToUTF8(title); | 
 |   } else { | 
 |     int message_id = 0; | 
 |     if (type_ == SELECT_SAVEAS_FILE) { | 
 |       message_id = IDS_SAVEAS_ALL_FILES; | 
 |     } else if (type_ == SELECT_OPEN_MULTI_FILE) { | 
 |       message_id = IDS_OPEN_FILES_DIALOG_TITLE; | 
 |     } else { | 
 |       message_id = IDS_OPEN_FILE_DIALOG_TITLE; | 
 |     } | 
 |     utf8_title = l10n_util::GetStringUTF8(message_id); | 
 |   } | 
 |  | 
 |   MakeFileChooserRequest( | 
 |       method, utf8_title, | 
 |       BuildOptionsDictionary(default_path, default_path_exists, filter_set), | 
 |       std::move(parent_handle)); | 
 | } | 
 |  | 
 | DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary( | 
 |     const base::FilePath& default_path, | 
 |     bool default_path_exists, | 
 |     const PortalFilterSet& filter_set) { | 
 |   DbusDictionary dict; | 
 |  | 
 |   switch (type_) { | 
 |     case SelectFileDialog::SELECT_UPLOAD_FOLDER: | 
 |       dict.PutAs(kFileChooserOptionAcceptLabel, | 
 |                  DbusString(l10n_util::GetStringUTF8( | 
 |                      IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON))); | 
 |       [[fallthrough]]; | 
 |     case SelectFileDialog::SELECT_FOLDER: | 
 |     case SelectFileDialog::Type::SELECT_EXISTING_FOLDER: | 
 |       dict.PutAs(kFileChooserOptionDirectory, DbusBoolean(true)); | 
 |       break; | 
 |     case SelectFileDialog::SELECT_OPEN_MULTI_FILE: | 
 |       dict.PutAs(kFileChooserOptionMultiple, DbusBoolean(true)); | 
 |       break; | 
 |     default: | 
 |       break; | 
 |   } | 
 |  | 
 |   if (!default_path.empty() && base::IsStringUTF8(default_path.value())) { | 
 |     if (default_path_exists) { | 
 |       // If this is an existing directory, navigate to that directory, with no | 
 |       // filename. | 
 |       dict.PutAs(kFileChooserOptionCurrentFolder, | 
 |                  PathToByteArray(default_path)); | 
 |     } else { | 
 |       // The default path does not exist, or is an existing file. We use | 
 |       // current_folder followed by current_name, as per the recommendation of | 
 |       // the GTK docs and the pattern followed by SelectFileDialogLinuxGtk. | 
 |       dict.PutAs(kFileChooserOptionCurrentFolder, | 
 |                  PathToByteArray(default_path.DirName())); | 
 |  | 
 |       // current_folder is supported by xdg-desktop-portal but current_name | 
 |       // is not - only try to set this when invoking a save file dialog. | 
 |       if (type_ == SELECT_SAVEAS_FILE) { | 
 |         dict.PutAs(kFileChooserOptionCurrentName, | 
 |                    DbusString(default_path.BaseName().value())); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   if (!filter_set.filters.empty()) { | 
 |     DbusFilters filters_array; | 
 |     for (const auto& filter : filter_set.filters) { | 
 |       filters_array.value().push_back(MakeFilterStruct(filter)); | 
 |     } | 
 |     dict.PutAs(kFileChooserOptionFilters, std::move(filters_array)); | 
 |  | 
 |     if (filter_set.default_filter) { | 
 |       dict.PutAs(kFileChooserOptionCurrentFilter, | 
 |                  MakeFilterStruct(*filter_set.default_filter)); | 
 |     } | 
 |   } | 
 |  | 
 |   dict.PutAs(kFileChooserOptionModal, DbusBoolean(true)); | 
 |  | 
 |   return dict; | 
 | } | 
 |  | 
 | SelectFileDialogLinuxPortal::DbusFilter | 
 | SelectFileDialogLinuxPortal::MakeFilterStruct(const PortalFilter& filter) { | 
 |   DbusFilterPatterns patterns; | 
 |   for (const std::string& pattern_str : filter.patterns) { | 
 |     patterns.value().push_back(MakeDbusStruct( | 
 |         DbusUint32(kFileChooserFilterKindGlob), DbusString(pattern_str))); | 
 |   } | 
 |   return MakeDbusStruct(DbusString(filter.name), std::move(patterns)); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::MakeFileChooserRequest( | 
 |     const std::string& method, | 
 |     const std::string& title, | 
 |     DbusDictionary options, | 
 |     std::string parent_handle) { | 
 |   CHECK(GetMainTaskRunner()->RunsTasksInCurrentSequence()); | 
 |   scoped_refptr<dbus::Bus> bus = dbus_thread_linux::GetSharedSessionBus(); | 
 |   dbus::ObjectProxy* portal = bus->GetObjectProxy( | 
 |       kXdgPortalService, dbus::ObjectPath(kXdgPortalObject)); | 
 |  | 
 |   // Unretained is safe since we own the Request object. | 
 |   auto callback = | 
 |       base::BindOnce(&SelectFileDialogLinuxPortal::OnFileChooserResponse, | 
 |                      base::Unretained(this)); | 
 |   file_chooser_request_ = std::make_unique<dbus_xdg::Request>( | 
 |       bus, portal, kFileChooserInterfaceName, method, | 
 |       MakeDbusParameters(DbusString(std::move(parent_handle)), | 
 |                          DbusString(title)), | 
 |       std::move(options), std::move(callback)); | 
 |   invoker_task_runner_->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&SelectFileDialogLinuxPortal::DialogCreatedOnInvoker, | 
 |                      this)); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::OnFileChooserResponse( | 
 |     base::expected<DbusDictionary, dbus_xdg::ResponseError> results) { | 
 |   CHECK(GetMainTaskRunner()->RunsTasksInCurrentSequence()); | 
 |  | 
 |   file_chooser_request_.reset(); | 
 |  | 
 |   if (!results.has_value()) { | 
 |     CancelOpen(); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::vector<std::string> uris; | 
 |   auto* wrapped_uris = results->GetAs<DbusArray<DbusString>>("uris"); | 
 |   if (wrapped_uris) { | 
 |     for (auto& element : wrapped_uris->value()) { | 
 |       uris.push_back(element.value()); | 
 |     } | 
 |   } | 
 |  | 
 |   if (uris.empty()) { | 
 |     CancelOpen(); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::vector<base::FilePath> paths = ConvertUrisToPaths(uris); | 
 |   if (paths.empty()) { | 
 |     CancelOpen(); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::string current_filter_name; | 
 |   auto* current_filter = results->GetAs<DbusFilter>("current_filter"); | 
 |   if (current_filter) { | 
 |     current_filter_name = std::get<0>(current_filter->value()).value(); | 
 |   } | 
 |  | 
 |   CompleteOpen(std::move(paths), std::move(current_filter_name)); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::CompleteOpen( | 
 |     std::vector<base::FilePath> paths, | 
 |     std::string current_filter) { | 
 |   dbus_thread_linux::GetSharedSessionBus()->AssertOnOriginThread(); | 
 |   invoker_task_runner_->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&SelectFileDialogLinuxPortal::CompleteOpenOnInvoker, this, | 
 |                      std::move(paths), std::move(current_filter))); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::CancelOpen() { | 
 |   dbus_thread_linux::GetSharedSessionBus()->AssertOnOriginThread(); | 
 |   invoker_task_runner_->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&SelectFileDialogLinuxPortal::CancelOpenOnInvoker, this)); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::DialogCreatedOnInvoker() { | 
 |   CHECK(invoker_task_runner_->RunsTasksInCurrentSequence()); | 
 |   if (!host_) { | 
 |     return; | 
 |   } | 
 |   host_->ReleaseCapture(); | 
 |   reenable_window_event_handling_ = | 
 |       static_cast<views::DesktopWindowTreeHostLinux*>(host_.get()) | 
 |           ->DisableEventListening(); | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::CompleteOpenOnInvoker( | 
 |     std::vector<base::FilePath> paths, | 
 |     std::string current_filter) { | 
 |   CHECK(invoker_task_runner_->RunsTasksInCurrentSequence()); | 
 |   UnparentOnInvoker(); | 
 |  | 
 |   if (listener_) { | 
 |     if (type_ == SELECT_OPEN_MULTI_FILE) { | 
 |       listener_->MultiFilesSelected(FilePathListToSelectedFileInfoList(paths)); | 
 |     } else if (paths.size() > 1) { | 
 |       LOG(ERROR) << "Got >1 file URI from a single-file chooser"; | 
 |     } else { | 
 |       int index = 1; | 
 |       for (size_t i = 0; i < filters_.size(); ++i) { | 
 |         if (filters_[i].name == current_filter) { | 
 |           index = 1 + i; | 
 |           break; | 
 |         } | 
 |       } | 
 |       listener_->FileSelected(SelectedFileInfo(paths[0]), index); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::CancelOpenOnInvoker() { | 
 |   CHECK(invoker_task_runner_->RunsTasksInCurrentSequence()); | 
 |   UnparentOnInvoker(); | 
 |  | 
 |   if (listener_) { | 
 |     listener_->FileSelectionCanceled(); | 
 |   } | 
 | } | 
 |  | 
 | void SelectFileDialogLinuxPortal::UnparentOnInvoker() { | 
 |   if (reenable_window_event_handling_) { | 
 |     std::move(reenable_window_event_handling_).Run(); | 
 |   } | 
 |   host_ = nullptr; | 
 | } | 
 |  | 
 | SelectFileDialogLinuxPortal::PortalFilter::PortalFilter() = default; | 
 | SelectFileDialogLinuxPortal::PortalFilter::PortalFilter( | 
 |     const PortalFilter& other) = default; | 
 | SelectFileDialogLinuxPortal::PortalFilter::PortalFilter(PortalFilter&& other) = | 
 |     default; | 
 | SelectFileDialogLinuxPortal::PortalFilter::~PortalFilter() = default; | 
 |  | 
 | SelectFileDialogLinuxPortal::PortalFilterSet::PortalFilterSet() = default; | 
 | SelectFileDialogLinuxPortal::PortalFilterSet::PortalFilterSet( | 
 |     const PortalFilterSet& other) = default; | 
 | SelectFileDialogLinuxPortal::PortalFilterSet::PortalFilterSet( | 
 |     PortalFilterSet&& other) = default; | 
 | SelectFileDialogLinuxPortal::PortalFilterSet::~PortalFilterSet() = default; | 
 |  | 
 | }  // namespace ui |