Commit 59dd9ace authored by Kelvin Jiang's avatar Kelvin Jiang Committed by Commit Bot

[Extensions] Allow navigation to activity log page for all extension IDs

This CL allows users to navigate to the activity log page via URL even
if the extension ID specified in the URL is invalid/uninstalled. This
allows the user to see install-time activities in real-time via the
following steps:

1) Navigate to the activity log via URL
2) Click on the real-time tab
3) Install the extension with the ID in the activity log's URL. Note
   that a listener for extension activities is already present for the
   extension ID.
4) In the real-time tab, the extension's install-time activities should
   appear.

Screenshots: https://imgur.com/a/Niyc3S8

Bug: 832354
Change-Id: Ifaa0c3ea57d295f8657f067c610a093170e909a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1620346
Commit-Queue: Kelvin Jiang <kelvinjiang@chromium.org>
Reviewed-by: default avatarEsmael El-Moslimany <aee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664073}
parent 07170fe6
......@@ -295,6 +295,9 @@
<message name="IDS_EXTENSIONS_LOADING_ACTIVITIES" desc="The message shown to the user when the activity log for an extension is currently being fetched from the API.">
Fetching activities...
</message>
<message name="IDS_MISSING_OR_UNINSTALLED_EXTENSION" desc="The placeholder for the activity log page header if the extension does not exist or is not installed.">
Missing or uninstalled extension
</message>
<message name="IDS_EXTENSIONS_NO_ACTIVITIES" desc="The message shown to the user when an extension has no recent activities.">
No recent activities
</message>
......
......@@ -261,6 +261,7 @@ js_library("manager") {
"//ui/webui/resources/cr_elements/cr_view_manager:cr_view_manager",
"//ui/webui/resources/js:assert",
"//ui/webui/resources/js:cr",
"//ui/webui/resources/js:load_time_data",
]
externs_list = [ "$externs_path/developer_private.js" ]
}
......
......@@ -27,10 +27,13 @@
margin-inline-start: 8px;
}
#closeButton {
margin-inline-end: 16px;
}
#icon {
height: 24px;
margin-inline-end: 12px;
margin-inline-start: 16px;
width: 24px;
}
......@@ -65,13 +68,15 @@
<cr-icon-button class="icon-arrow-back no-overlap" id="closeButton"
aria-label="$i18n{back}" on-click="onCloseButtonTap_">
</cr-icon-button>
<img id="icon" src="[[extensionInfo.iconUrl]]"
alt$="[[appOrExtension(
extensionInfo.type,
'$i18nPolymer{appIcon}',
'$i18nPolymer{extensionIcon}')]]">
<template is="dom-if" if="[[!extensionInfo.isPlaceholder]]">
<img id="icon" src="[[extensionInfo.iconUrl]]"
alt$="[[appOrExtension(
extensionInfo.type,
'$i18nPolymer{appIcon}',
'$i18nPolymer{extensionIcon}')]]">
</template>
<div id="activity-log-heading">
[[i18n('activityLogPageHeading', extensionInfo.name)]]
[[getActivityLogHeading_(extensionInfo)]]
</div>
</div>
<cr-tabs selected="{{selectedSubpage_}}" tab-names="[[tabNames_]]">
......
......@@ -18,6 +18,17 @@ const ActivityLogSubpage = {
cr.define('extensions', function() {
'use strict';
/**
* A struct used as a placeholder for chrome.developerPrivate.ExtensionInfo
* for this component if the extensionId from the URL does not correspond to
* installed extension.
* @typedef {{
* id: string,
* isPlaceholder: boolean,
* }}
*/
let ActivityLogExtensionPlaceholder;
const ActivityLog = Polymer({
is: 'extensions-activity-log',
......@@ -30,7 +41,8 @@ cr.define('extensions', function() {
properties: {
/**
* The underlying ExtensionInfo for the details being displayed.
* @type {!chrome.developerPrivate.ExtensionInfo}
* @type {!chrome.developerPrivate.ExtensionInfo|
* !extensions.ActivityLogExtensionPlaceholder}
*/
extensionInfo: Object,
......@@ -83,6 +95,17 @@ cr.define('extensions', function() {
}
},
/**
* @private
* @return {string}
*/
getActivityLogHeading_: function() {
const headingName = this.extensionInfo.isPlaceholder ?
this.i18n('missingOrUninstalledExtension') :
this.extensionInfo.name;
return this.i18n('activityLogPageHeading', headingName);
},
/**
* @private
* @return {boolean}
......@@ -123,12 +146,17 @@ cr.define('extensions', function() {
/** @private */
onCloseButtonTap_: function() {
extensions.navigation.navigateTo(
{page: Page.DETAILS, extensionId: this.extensionInfo.id});
if (this.extensionInfo.isPlaceholder) {
extensions.navigation.navigateTo({page: Page.LIST});
} else {
extensions.navigation.navigateTo(
{page: Page.DETAILS, extensionId: this.extensionInfo.id});
}
},
});
return {
ActivityLog: ActivityLog,
ActivityLogExtensionPlaceholder: ActivityLogExtensionPlaceholder,
};
});
......@@ -100,7 +100,8 @@ cr.define('extensions', function() {
/**
* The item that provides some information about the current extension
* for the activity log view subpage. See also errorPageItem_.
* @private {!chrome.developerPrivate.ExtensionInfo|undefined}
* @private {!chrome.developerPrivate.ExtensionInfo|undefined|
* !extensions.ActivityLogExtensionPlaceholder}
*/
activityLogItem_: Object,
......@@ -407,6 +408,10 @@ cr.define('extensions', function() {
this.errorPageItem_ && this.errorPageItem_.id == item.id &&
this.currentPage_.page == Page.ERRORS) {
this.errorPageItem_ = item;
} else if (
this.activityLogItem_ && this.activityLogItem_.id == item.id &&
this.currentPage_.page == Page.ACTIVITY_LOG) {
this.activityLogItem_ = item;
}
},
......@@ -465,12 +470,24 @@ cr.define('extensions', function() {
const fromPage = this.currentPage_ ? this.currentPage_.page : null;
const toPage = newPage.page;
let data;
let activityLogPlaceholder;
if (newPage.extensionId) {
data = this.getData_(newPage.extensionId);
if (!data) {
// Attempting to view an invalid (removed?) app or extension ID.
extensions.navigation.replaceWith({page: Page.LIST});
return;
// Allow the user to navigate to the activity log page even if the
// extension ID is not valid. This enables the use case of seeing an
// extension's install-time activities by navigating to an extension's
// activity log page, then installing the extension.
if (this.showActivityLog && toPage == Page.ACTIVITY_LOG) {
activityLogPlaceholder = {
id: newPage.extensionId,
isPlaceholder: true,
};
} else {
// Attempting to view an invalid (removed?) app or extension ID.
extensions.navigation.replaceWith({page: Page.LIST});
return;
}
}
}
......@@ -487,7 +504,7 @@ cr.define('extensions', function() {
return;
}
this.activityLogItem_ = assert(data);
this.activityLogItem_ = data ? assert(data) : activityLogPlaceholder;
}
if (fromPage != toPage) {
......
......@@ -245,6 +245,7 @@ content::WebUIDataSource* CreateMdExtensionsSource(Profile* profile,
{"loadErrorErrorLabel", IDS_EXTENSIONS_LOAD_ERROR_ERROR_LABEL},
{"loadErrorRetry", IDS_EXTENSIONS_LOAD_ERROR_RETRY},
{"loadingActivities", IDS_EXTENSIONS_LOADING_ACTIVITIES},
{"missingOrUninstalledExtension", IDS_MISSING_OR_UNINSTALLED_EXTENSION},
{"noActivities", IDS_EXTENSIONS_NO_ACTIVITIES},
{"noErrorsToShow", IDS_EXTENSIONS_ERROR_NO_ERRORS_CODE_MESSAGE},
{"runtimeHostsDialogInputError",
......
......@@ -19,7 +19,8 @@ suite('ExtensionsActivityLogTest', function() {
/**
* Backing extension info for the activity log.
* @type {chrome.developerPrivate.ExtensionInfo}
* @type {chrome.developerPrivate.ExtensionInfo|
* extensions.ActivityLogExtensionPlaceholder}
*/
let extensionInfo;
......@@ -84,6 +85,22 @@ suite('ExtensionsActivityLogTest', function() {
currentPage, {page: Page.DETAILS, extensionId: EXTENSION_ID});
});
test(
'clicking on back button for a placeholder page navigates to list view',
function() {
activityLog.extensionInfo = {id: EXTENSION_ID, isPlaceholder: true};
Polymer.dom.flush();
let currentPage = null;
extensions.navigation.addListener(newPage => {
currentPage = newPage;
});
activityLog.$$('#closeButton').click();
expectDeepEquals(currentPage, {page: Page.LIST});
});
test('tab transitions', async () => {
Polymer.dom.flush();
// Default view should be the history view.
......
......@@ -171,8 +171,9 @@ cr.define('extension_manager_tests', function() {
extensions.navigation.navigateTo(
{page: Page.ACTIVITY_LOG, extensionId: 'z'.repeat(32)});
Polymer.dom.flush();
// Should be re-routed to the main page.
assertViewActive('extensions-item-list');
// Should also be on activity log page. See |changePage_| in manager.js
// for the use case.
assertViewActive('extensions-activity-log');
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment