feat: add app.getApplicationInfoForProtocol API (#24112)

* pre merge

* windows changes

* added tests

* clean up

* more cleanup

* lint error

* windows 7 support

* added windows 7 implementation

* code review

* lint and code review

* code review

* app.md merge conflict

* merge conflict app.md

accidently deleted code block

* 'lint'

* mis-moved getapplicationinfoforprotocol() into anonymous namespace

* fix test

* lint

* code review
This commit is contained in:
George Xu 2020-06-30 12:22:30 -07:00 committed by GitHub
parent 2cbd091e89
commit ee61eb9aa4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 261 additions and 20 deletions

View file

@ -21,11 +21,17 @@
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "chrome/browser/icon_manager.h"
#include "electron/electron_version.h"
#include "shell/browser/api/electron_api_app.h"
#include "shell/browser/electron_browser_main_parts.h"
#include "shell/browser/ui/message_box.h"
#include "shell/browser/ui/win/jump_list.h"
#include "shell/common/application_info.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_helper/arguments.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/skia_util.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
@ -49,7 +55,6 @@ BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
bool GetProcessExecPath(base::string16* exe) {
base::FilePath path;
if (!base::PathService::Get(base::FILE_EXE, &path)) {
LOG(ERROR) << "Error getting app exe path";
return false;
}
*exe = path.value();
@ -81,23 +86,25 @@ bool IsValidCustomProtocol(const base::string16& scheme) {
return cmd_key.Valid() && cmd_key.HasValue(L"URL Protocol");
}
// Helper for GetApplicationInfoForProtocol().
// takes in an assoc_str
// (https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/ne-shlwapi-assocstr)
// and returns the application name, icon and path that handles the protocol.
//
// Windows 8 introduced a new protocol->executable binding system which cannot
// be retrieved in the HKCR registry subkey method implemented below. We call
// AssocQueryString with the new Win8-only flag ASSOCF_IS_PROTOCOL instead.
base::string16 GetAppForProtocolUsingAssocQuery(const GURL& url) {
base::string16 GetAppInfoHelperForProtocol(ASSOCSTR assoc_str,
const GURL& url) {
const base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
if (!IsValidCustomProtocol(url_scheme))
return base::string16();
// Query AssocQueryString for a human-readable description of the program
// that will be invoked given the provided URL spec. This is used only to
// populate the external protocol dialog box the user sees when invoking
// an unknown external protocol.
wchar_t out_buffer[1024];
DWORD buffer_size = base::size(out_buffer);
HRESULT hr =
AssocQueryString(ASSOCF_IS_PROTOCOL, ASSOCSTR_FRIENDLYAPPNAME,
url_scheme.c_str(), NULL, out_buffer, &buffer_size);
AssocQueryString(ASSOCF_IS_PROTOCOL, assoc_str, url_scheme.c_str(), NULL,
out_buffer, &buffer_size);
if (FAILED(hr)) {
DLOG(WARNING) << "AssocQueryString failed!";
return base::string16();
@ -105,6 +112,32 @@ base::string16 GetAppForProtocolUsingAssocQuery(const GURL& url) {
return base::string16(out_buffer);
}
void OnIconDataAvailable(const base::FilePath& app_path,
const base::string16& app_display_name,
gin_helper::Promise<gin_helper::Dictionary> promise,
gfx::Image icon) {
if (!icon.IsEmpty()) {
v8::HandleScope scope(promise.isolate());
gin_helper::Dictionary dict =
gin::Dictionary::CreateEmpty(promise.isolate());
dict.Set("path", app_path);
dict.Set("name", app_display_name);
dict.Set("icon", icon);
promise.Resolve(dict);
} else {
promise.RejectWithErrorMessage("Failed to get file icon.");
}
}
base::string16 GetAppDisplayNameForProtocol(const GURL& url) {
return GetAppInfoHelperForProtocol(ASSOCSTR_FRIENDLYAPPNAME, url);
}
base::string16 GetAppPathForProtocol(const GURL& url) {
return GetAppInfoHelperForProtocol(ASSOCSTR_EXECUTABLE, url);
}
base::string16 GetAppForProtocolUsingRegistry(const GURL& url) {
const base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
if (!IsValidCustomProtocol(url_scheme))
@ -169,6 +202,96 @@ void Browser::Focus(gin_helper::Arguments* args) {
EnumWindows(&WindowsEnumerationHandler, reinterpret_cast<LPARAM>(&pid));
}
void GetFileIcon(const base::FilePath& path,
v8::Isolate* isolate,
base::CancelableTaskTracker* cancelable_task_tracker_,
const base::string16 app_display_name,
gin_helper::Promise<gin_helper::Dictionary> promise) {
base::FilePath normalized_path = path.NormalizePathSeparators();
IconLoader::IconSize icon_size = IconLoader::IconSize::LARGE;
auto* icon_manager = ElectronBrowserMainParts::Get()->GetIconManager();
gfx::Image* icon =
icon_manager->LookupIconFromFilepath(normalized_path, icon_size);
if (icon) {
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
dict.Set("icon", *icon);
dict.Set("name", app_display_name);
dict.Set("path", normalized_path);
promise.Resolve(dict);
} else {
icon_manager->LoadIcon(normalized_path, icon_size,
base::BindOnce(&OnIconDataAvailable, normalized_path,
app_display_name, std::move(promise)),
cancelable_task_tracker_);
}
}
void GetApplicationInfoForProtocolUsingRegistry(
v8::Isolate* isolate,
const GURL& url,
gin_helper::Promise<gin_helper::Dictionary> promise,
base::CancelableTaskTracker* cancelable_task_tracker_) {
base::FilePath app_path;
const base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
if (!IsValidCustomProtocol(url_scheme)) {
promise.RejectWithErrorMessage("invalid url_scheme");
return;
}
base::string16 command_to_launch;
const base::string16 cmd_key_path = url_scheme + L"\\shell\\open\\command";
base::win::RegKey cmd_key_exe(HKEY_CLASSES_ROOT, cmd_key_path.c_str(),
KEY_READ);
if (cmd_key_exe.ReadValue(NULL, &command_to_launch) == ERROR_SUCCESS) {
base::CommandLine command_line(
base::CommandLine::FromString(command_to_launch));
app_path = command_line.GetProgram();
} else {
promise.RejectWithErrorMessage(
"Unable to retrieve installation path to app");
return;
}
const base::string16 app_display_name = GetAppForProtocolUsingRegistry(url);
if (app_display_name.length() == 0) {
promise.RejectWithErrorMessage(
"Unable to retrieve application display name");
return;
}
GetFileIcon(app_path, isolate, cancelable_task_tracker_, app_display_name,
std::move(promise));
}
// resolves `Promise<Object>` - Resolve with an object containing the following:
// * `icon` NativeImage - the display icon of the app handling the protocol.
// * `path` String - installation path of the app handling the protocol.
// * `name` String - display name of the app handling the protocol.
void GetApplicationInfoForProtocolUsingAssocQuery(
v8::Isolate* isolate,
const GURL& url,
gin_helper::Promise<gin_helper::Dictionary> promise,
base::CancelableTaskTracker* cancelable_task_tracker_) {
base::string16 app_path = GetAppPathForProtocol(url);
if (app_path.empty()) {
promise.RejectWithErrorMessage(
"Unable to retrieve installation path to app");
return;
}
base::string16 app_display_name = GetAppDisplayNameForProtocol(url);
if (app_display_name.empty()) {
promise.RejectWithErrorMessage("Unable to retrieve display name of app");
return;
}
base::FilePath app_path_file_path = base::FilePath(app_path);
GetFileIcon(app_path_file_path, isolate, cancelable_task_tracker_,
app_display_name, std::move(promise));
}
void Browser::AddRecentDocument(const base::FilePath& path) {
CComPtr<IShellItem> item;
HRESULT hr = SHCreateItemFromParsingName(path.value().c_str(), NULL,
@ -358,7 +481,7 @@ bool Browser::IsDefaultProtocolClient(const std::string& protocol,
base::string16 Browser::GetApplicationNameForProtocol(const GURL& url) {
// Windows 8 or above has a new protocol association query.
if (base::win::GetVersion() >= base::win::Version::WIN8) {
base::string16 application_name = GetAppForProtocolUsingAssocQuery(url);
base::string16 application_name = GetAppDisplayNameForProtocol(url);
if (!application_name.empty())
return application_name;
}
@ -366,6 +489,24 @@ base::string16 Browser::GetApplicationNameForProtocol(const GURL& url) {
return GetAppForProtocolUsingRegistry(url);
}
v8::Local<v8::Promise> Browser::GetApplicationInfoForProtocol(
v8::Isolate* isolate,
const GURL& url) {
gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
// Windows 8 or above has a new protocol association query.
if (base::win::GetVersion() >= base::win::Version::WIN8) {
GetApplicationInfoForProtocolUsingAssocQuery(
isolate, url, std::move(promise), &cancelable_task_tracker_);
return handle;
}
GetApplicationInfoForProtocolUsingRegistry(isolate, url, std::move(promise),
&cancelable_task_tracker_);
return handle;
}
bool Browser::SetBadgeCount(int count) {
return false;
}