Skip to content

Commit

Permalink
[FileManager] Annotate policy-assigned default apps with a managed icon.
Browse files Browse the repository at this point in the history
Bug: 1366815
Change-Id: I463df2c7ce2f37e6e37c8333c32ea50aa7acf0c1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4043923
Reviewed-by: Luciano Pacheco <lucmult@chromium.org>
Commit-Queue: Andrew Rayskiy <greengrape@google.com>
Cr-Commit-Position: refs/heads/main@{#1075651}
  • Loading branch information
Andrew Rayskiy authored and Chromium LUCI CQ committed Nov 25, 2022
1 parent 95e03df commit 7f0b07f
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 22 deletions.
3 changes: 2 additions & 1 deletion chrome/browser/ash/file_manager/file_manager_browsertest.cc
Expand Up @@ -978,7 +978,8 @@ WRAPPED_INSTANTIATE_TEST_SUITE_P(
TestCase("checkDeleteDisabledInRecents"),
TestCase("checkGoToFileLocationEnabledInRecents"),
TestCase("checkGoToFileLocationDisabledInMultipleSelection"),
TestCase("checkDefaultTask")));
TestCase("checkDefaultTask"),
TestCase("checkPolicyAssignedDefaultHasManagedIcon")));

WRAPPED_INSTANTIATE_TEST_SUITE_P(
Toolbar, /* toolbar.js */
Expand Down
11 changes: 9 additions & 2 deletions ui/file_manager/file_manager/background/js/test_util.js
Expand Up @@ -383,13 +383,20 @@ test.util.sync.execCommand = (contentWindow, command) => {
* @param {Window} contentWindow Window to be tested.
* @param {Array<Object>} taskList List of tasks to be returned in
* fileManagerPrivate.getFileTasks().
* @param {boolean}
* isPolicyDefault Whether the default is set by policy.
* @return {boolean} Always return true.
*/
test.util.sync.overrideTasks = (contentWindow, taskList) => {
test.util.sync
.overrideTasks = (contentWindow, taskList, isPolicyDefault = false) => {
const getFileTasks = (entries, onTasks) => {
// Call onTask asynchronously (same with original getFileTasks).
setTimeout(() => {
onTasks({tasks: taskList});
const policyDefaultHandlerStatus = isPolicyDefault ?
chrome.fileManagerPrivate.PolicyDefaultHandlerStatus
.DEFAULT_HANDLER_ASSIGNED_BY_POLICY :
undefined;
onTasks({tasks: taskList, policyDefaultHandlerStatus});
}, 0);
};

Expand Down
20 changes: 11 additions & 9 deletions ui/file_manager/file_manager/foreground/css/menu.css
Expand Up @@ -73,7 +73,9 @@ cr-menu[hidden].files-menu.animating {
}

cr-menu.files-menu > cr-menu-item {
position: relative;
display: flex;
align-items: center;
flex-direction: row;
}

/* Icon on the left of the item label for cr.ui.FilesMenuItem.
Expand All @@ -91,30 +93,30 @@ cr-menu.files-menu cr-menu-item .icon {
}

cr-menu.files-menu cr-menu-item .icon.start {
display: inline-block;
align-self: flex-start;
margin-inline-end: 8px;
}

cr-menu.files-menu:not(.has-icon-start) cr-menu-item .icon.start {
display: none;
}

cr-menu.files-menu cr-menu-item .icon.end {
float: right;
cr-menu.files-menu cr-menu-item .icon.managed {
background-image: url(chrome://resources/images/business.svg);
margin-inline-start: 8px;
}

html[dir='rtl'] cr-menu.files-menu cr-menu-item .icon.end {
float: left;
cr-menu.files-menu cr-menu-item .icon.end {
align-self: flex-end;
margin-inline-start: 8px;
}

cr-menu.files-menu > cr-menu-item > .icon {
position: relative;
z-index: 1;
}

cr-menu.files-menu > cr-menu-item > span {
position: relative;
vertical-align: middle;
flex-grow: 1;
z-index: 1;
}

Expand Down
15 changes: 13 additions & 2 deletions ui/file_manager/file_manager/foreground/js/task_controller.ts
Expand Up @@ -238,9 +238,12 @@ export class TaskController {
// an item to change default task.
if (defaultTask) {
combobutton.addSeparator();
// TODO(greengrape): Ensure that the passed object is a `DropdownItem`.
const changeDefaultMenuItem = combobutton.addDropDownItem({
type: TaskMenuItemType.CHANGE_DEFAULT_TASK,
label: str('CHANGE_DEFAULT_MENU_ITEM'),
isDefault: false,
isPolicyDefault: false,
});
changeDefaultMenuItem.classList.add('change-default');

Expand Down Expand Up @@ -272,7 +275,9 @@ export class TaskController {
for (const task of tasks) {
if (task === fileTasks.defaultTask) {
const title = task.title + ' ' + str('DEFAULT_TASK_LABEL');
items.push(createDropdownItem(task, title, true, true));
items.push(createDropdownItem(
task, title, /*bold=*/ true, /*isDefault=*/ true,
/*isPolicyDefault=*/ !!fileTasks.getPolicyDefaultHandlerStatus()));
} else {
items.push(createDropdownItem(task));
}
Expand Down Expand Up @@ -480,6 +485,7 @@ export class TaskController {
if (taskCount > 0) {
if (defaultTask) {
const menuItem = this.ui_.defaultTaskMenuItem;
menuItem.setIsDefaultAttribute();
/**
* Menu icon can be controlled by either `iconEndImage` or
* `iconEndFileType`, since the default task menu item DOM is shared,
Expand All @@ -500,6 +506,9 @@ export class TaskController {
menuItem.setIconEndHidden(true);
}

menuItem.toggleManagedIcon(
/*visible=*/ !!openTasks.policyDefaultHandlerStatus);

menuItem.label = defaultTask.title;
menuItem.descriptor = defaultTask.descriptor;
}
Expand Down Expand Up @@ -656,6 +665,7 @@ export interface DropdownItem {
task: chrome.fileManagerPrivate.FileTask;
bold: boolean;
isDefault: boolean;
isPolicyDefault: boolean;
isGenericFileHandler?: boolean;
}

Expand All @@ -666,7 +676,7 @@ export interface DropdownItem {
*/
function createDropdownItem(
task: chrome.fileManagerPrivate.FileTask, title?: string, bold?: boolean,
isDefault?: boolean): DropdownItem {
isDefault?: boolean, isPolicyDefault?: boolean): DropdownItem {
return {
type: TaskMenuItemType.RUN_TASK,
label: title || task.title,
Expand All @@ -675,6 +685,7 @@ function createDropdownItem(
task: task,
bold: bold || false,
isDefault: isDefault || false,
isPolicyDefault: isPolicyDefault || false,
isGenericFileHandler: task.isGenericFileHandler,
};
}
7 changes: 6 additions & 1 deletion ui/file_manager/file_manager/foreground/js/ui/combobutton.js
Expand Up @@ -47,11 +47,16 @@ export class ComboButton extends MultiMenuButton {

addDropDownItem(item) {
this.multiple = true;
const menuitem = this.menu.addMenuItem(item);
const menuitem = /** @type {!MenuItem} */ (this.menu.addMenuItem(item));

// If menu is files-menu, decorate menu item as FilesMenuItem.
if (this.menu.classList.contains('files-menu')) {
decorate(menuitem, FilesMenuItem);
/** @type {!FilesMenuItem} */ (menuitem).toggleManagedIcon(
/*visible=*/ item.isPolicyDefault);
if (item.isDefault) {
/** @type {!FilesMenuItem} */ (menuitem).setIsDefaultAttribute();
}
}

menuitem.data = item;
Expand Down
39 changes: 36 additions & 3 deletions ui/file_manager/file_manager/foreground/js/ui/files_menu.js
Expand Up @@ -25,6 +25,9 @@ export class FilesMenuItem extends MenuItem {
/** @private {?HTMLElement} */
this.iconStart_ = null;

/** @private {?HTMLElement} */
this.iconManaged_ = null;

/** @private {?HTMLElement} */
this.iconEnd_ = null;

Expand Down Expand Up @@ -64,20 +67,26 @@ export class FilesMenuItem extends MenuItem {
assertInstanceof(document.createElement('div'), HTMLElement);
this.iconStart_.classList.add('icon', 'start');

this.iconManaged_ =
assertInstanceof(document.createElement('div'), HTMLElement);
this.iconManaged_.classList.add('icon', 'managed');

this.iconEnd_ =
assertInstanceof(document.createElement('div'), HTMLElement);
this.iconEnd_.classList.add('icon', 'end');
/**
* This is hidden by default because most of the menu items don't require
* an end icon, the component which uses end icon should explicitly make
* it visible.
* This is hidden by default because most of the menu items require
* neither the end icon nor the managed icon, so the component that
* plans to use either end icon should explicitly make it visible.
*/
this.setIconEndHidden(true);
this.toggleManagedIcon(/*visible=*/ false);

// Override with standard menu item elements.
this.textContent = '';
this.appendChild(this.iconStart_);
this.appendChild(this.label_);
this.appendChild(this.iconManaged_);
this.appendChild(this.iconEnd_);
}

Expand Down Expand Up @@ -237,6 +246,30 @@ export class FilesMenuItem extends MenuItem {
this.iconStart_.setAttribute('file-type-icon', value);
}

/**
* Sets or removes the `is-managed` attribute.
* @param {boolean} isManaged
*/
toggleIsManagedAttribute(isManaged) {
this.toggleAttribute('is-managed', isManaged);
}

/**
* Sets the `is-default` attribute.
*/
setIsDefaultAttribute() {
this.toggleAttribute('is-default', true);
}

/**
* Toggles visibility of the `Managed by Policy` icon.
* @param {boolean} visible
*/
toggleManagedIcon(visible) {
this.iconManaged_.toggleAttribute('hidden', !visible);
this.toggleIsManagedAttribute(visible);
}

/**
* @return {string}
*/
Expand Down
7 changes: 7 additions & 0 deletions ui/file_manager/file_manager/foreground/js/ui/menu_item.d.ts
@@ -0,0 +1,7 @@
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export class MenuItem extends HTMLElement {
disabled: boolean;
}
1 change: 1 addition & 0 deletions ui/file_manager/file_names.gni
Expand Up @@ -346,6 +346,7 @@ checked_in_dts_files = [
"file_manager/foreground/js/ui/command.d.ts",
"file_manager/foreground/js/ui/list.d.ts",
"file_manager/foreground/js/ui/list_item.d.ts",
"file_manager/foreground/js/ui/menu_item.d.ts",
"file_manager/foreground/js/ui/multi_menu_button.d.ts",
]

Expand Down
64 changes: 63 additions & 1 deletion ui/file_manager/integration_tests/file_manager/context_menu.js
Expand Up @@ -882,7 +882,6 @@ testcase.checkContextMenuFocus = async () => {
chrome.test.assertEq('menuitem', focusedElement.attributes['role']);
};


testcase.checkDefaultTask = async () => {
// Open FilesApp on Downloads.
const appId = await setupAndWaitUntilReady(
Expand Down Expand Up @@ -925,3 +924,66 @@ testcase.checkDefaultTask = async () => {
chrome.test.assertTrue(!!folderDefaultTaskItem);
chrome.test.assertTrue(folderDefaultTaskItem.hidden);
};

testcase.checkPolicyAssignedDefaultHasManagedIcon = async () => {
// Open FilesApp on Downloads.
const appId =
await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);

// Force a task for the `hello` file.
const fakeDefaultTask = new FakeTask(
/* isDefault */ true,
{appId: 'dummyId1', taskType: 'app', actionId: 'open-with'},
'DummyDefaultTask');
const fakeSecondaryTask = new FakeTask(
/* isDefault */ false,
{appId: 'dummyId2', taskType: 'app', actionId: 'open-with'},
'DummySecondaryTask');

await remoteCall.callRemoteTestUtil(
'overrideTasks', appId,
[[fakeDefaultTask, fakeSecondaryTask], /*isPolicyDefault=*/ true]);

// Display the context menu.
await remoteCall.showContextMenuFor(appId, ENTRIES.hello.nameText);

// Get the context menu.
const contextMenu = await remoteCall.getMenu(appId, 'context-menu');

// Check the default task item is visible and has is-default/is-managed
// properties set.
const contextMenuDefaultTaskItem = contextMenu['items'].find(
el => el.attributes.id === 'default-task-menu-item');
chrome.test.assertTrue(!!contextMenuDefaultTaskItem);
chrome.test.assertFalse(contextMenuDefaultTaskItem.hidden);
chrome.test.assertEq(contextMenuDefaultTaskItem.text, 'DummyDefaultTask');
chrome.test.assertTrue('is-default' in contextMenuDefaultTaskItem.attributes);
chrome.test.assertTrue('is-managed' in contextMenuDefaultTaskItem.attributes);

// Dismiss the context menu.
await remoteCall.dismissMenu(appId);

// Display the tasks menu.
await remoteCall.expandOpenDropdown(appId);

// Get the tasks menu.
const tasksMenu = await remoteCall.getMenu(appId, 'tasks');

// Check the default task item is visible and has is-default/is-managed
// properties set.
const tasksMenuDefaultTaskItem = tasksMenu['items'][0];
chrome.test.assertTrue(!!tasksMenuDefaultTaskItem);
chrome.test.assertFalse(tasksMenuDefaultTaskItem.hidden);
chrome.test.assertTrue(
tasksMenuDefaultTaskItem.text.includes('DummyDefaultTask'));
chrome.test.assertTrue('is-default' in tasksMenuDefaultTaskItem.attributes);
chrome.test.assertTrue('is-managed' in tasksMenuDefaultTaskItem.attributes);

// Check that the remaining items do not have is-default/is-managed
// properties.
const tasksMenuNonDefaultTaskItems = tasksMenu['items'].slice(1);
for (const nonDefaultTaskItem of tasksMenuNonDefaultTaskItems) {
chrome.test.assertFalse('is-default' in nonDefaultTaskItem.attributes);
chrome.test.assertFalse('is-managed' in nonDefaultTaskItem.attributes);
}
};
22 changes: 19 additions & 3 deletions ui/file_manager/integration_tests/remote_call.js
Expand Up @@ -932,19 +932,22 @@ export class RemoteCallFilesApp extends RemoteCall {
/**
* @param {string} appId App window Id.
* @param {string|!Array<string>} query Query to find the elements.
* @return {!Promise<!ElementObject>} Promise to be fulfilled with the
* @return {!Promise<!Array<!ElementObject>>} Promise to be fulfilled with the
* elements.
* @private
*/
async queryElements_(appId, query) {
return this.callRemoteTestUtil('deepQueryAllElements', appId, [query]);
if (typeof query === 'string') {
query = [query];
}
return this.callRemoteTestUtil('deepQueryAllElements', appId, query);
}

/**
* Returns the menu as ElementObject and its menu-items (including separators)
* in the `items` property.
* @param {string} appId App window Id.
* @param {string|!Array<string>} menu Query to find the elements.
* @param {string|!Array<string>} menu The name of the menu.
* @return {!Promise<undefined|!ElementObject>} Promise to be fulfilled with
* the menu.
*/
Expand All @@ -953,7 +956,10 @@ export class RemoteCallFilesApp extends RemoteCall {
// TODO: Implement for other menus.
if (menu === 'context-menu') {
menuId = '#file-context-menu';
} else if (menu == 'tasks') {
menuId = '#tasks-menu';
}

if (!menuId) {
console.error(`Invalid menu '${menu}'`);
return;
Expand All @@ -965,4 +971,14 @@ export class RemoteCallFilesApp extends RemoteCall {
menuElement.items = await this.queryElements_(appId, `${menuId} > *`);
return menuElement;
}

/**
* Displays the "tasks" menu from the "OPEN" button dropdown.
* The caller code has to prepare the selection to have multiple tasks.
* @param {string} appId App window Id.
*/
async expandOpenDropdown(appId) {
// Wait the OPEN button to have multiple tasks.
await this.waitAndClickElement(appId, '#tasks[multiple]');
}
}

0 comments on commit 7f0b07f

Please sign in to comment.