Commit 2ee44ad3 authored by Kelvin Jiang's avatar Kelvin Jiang Committed by Commit Bot

[Extensions] Add an activity log page (placeholder page)

Add a placeholder page to show an activity log for an extension, hidden behind the
"enable-extension-activity-logging" command line flag, and accessible via
chrome://extensions/?id=<extension_id>. If the flag is disabled and a user
navigates to the page, redirect to the extensions details page.

Bug: 898309
Change-Id: Ib0f50487c323e8c07d6fddcd57df3b8e5477cfc6
Reviewed-on: https://chromium-review.googlesource.com/c/1295296Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Commit-Queue: Kelvin Jiang <kelvinjiang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#603176}
parent 1d776440
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
<message name="IDS_EXTENSIONS_ALLOW_ON_ALL_URLS" desc="The checkbox for allowing an extension to run scripts on all websites without explicit permission."> <message name="IDS_EXTENSIONS_ALLOW_ON_ALL_URLS" desc="The checkbox for allowing an extension to run scripts on all websites without explicit permission.">
Allow on all websites Allow on all websites
</message> </message>
<message name="IDS_EXTENSIONS_ACTIVITY_LOG" desc="The label of the button to click to view recent extension activity.">
View Activity Log
</message>
<message name="IDS_EXTENSIONS_BACKGROUND_PAGE" desc="Display name for an autogenerated background page."> <message name="IDS_EXTENSIONS_BACKGROUND_PAGE" desc="Display name for an autogenerated background page.">
background page background page
</message> </message>
......
...@@ -50,6 +50,7 @@ grit("flattened_resources") { ...@@ -50,6 +50,7 @@ grit("flattened_resources") {
} }
js_type_check("closure_compile") { js_type_check("closure_compile") {
deps = [ deps = [
":activity_log",
":code_section", ":code_section",
":detail_view", ":detail_view",
":drag_and_drop_handler", ":drag_and_drop_handler",
...@@ -88,6 +89,12 @@ js_library("code_section") { ...@@ -88,6 +89,12 @@ js_library("code_section") {
externs_list = [ "$externs_path/developer_private.js" ] externs_list = [ "$externs_path/developer_private.js" ]
} }
js_library("activity_log") {
deps = [
"//ui/webui/resources/js:cr",
]
}
js_library("detail_view") { js_library("detail_view") {
deps = [ deps = [
":item", ":item",
...@@ -228,6 +235,7 @@ js_library("load_error") { ...@@ -228,6 +235,7 @@ js_library("load_error") {
js_library("manager") { js_library("manager") {
deps = [ deps = [
":activity_log",
":detail_view", ":detail_view",
":item", ":item",
":item_list", ":item_list",
......
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/html/cr.html">
<dom-module id="extensions-activity-log">
<template>
<style include="iron-flex cr-shared-style">
#container {
height: 100%;
overflow: overlay;
}
#main {
background-color: white;
margin: auto;
min-height: 100%;
padding-bottom: 64px;
width: var(--cr-toolbar-field-width);
}
</style>
<div id="container">
<div id="main">
Welcome to the new activity log
</div>
</div>
</template>
<script src="activity_log.js"></script>
</dom-module>
// Copyright 2018 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.
cr.define('extensions', function() {
'use strict';
const ActivityLog = Polymer({
is: 'extensions-activity-log',
properties: {},
});
return {ActivityLog: ActivityLog};
});
...@@ -386,6 +386,10 @@ ...@@ -386,6 +386,10 @@
icon-class="icon-external" label="$i18n{itemOptions}" icon-class="icon-external" label="$i18n{itemOptions}"
on-click="onExtensionOptionsTap_"> on-click="onExtensionOptionsTap_">
</cr-link-row> </cr-link-row>
<cr-link-row class="hr" is="" id="extensions-activity-log-link"
hidden$="[[!showActivityLog]]" label="$i18n{viewActivityLog}"
on-click="onActivityLogTap_">
</cr-link-row>
<cr-link-row class="hr" hidden="[[!data.manifestHomePageUrl.length]]" <cr-link-row class="hr" hidden="[[!data.manifestHomePageUrl.length]]"
icon-class="icon-external" id="extensionWebsite" icon-class="icon-external" id="extensionWebsite"
label="$i18n{extensionWebsite}" on-click="onExtensionWebSiteTap_"> label="$i18n{extensionWebsite}" on-click="onExtensionWebSiteTap_">
......
...@@ -31,6 +31,9 @@ cr.define('extensions', function() { ...@@ -31,6 +31,9 @@ cr.define('extensions', function() {
/** Whether "allow in incognito" option should be shown. */ /** Whether "allow in incognito" option should be shown. */
incognitoAvailable: Boolean, incognitoAvailable: Boolean,
/** Whether "View Activity Log" link should be shown. */
showActivityLog: Boolean,
}, },
observers: [ observers: [
...@@ -60,6 +63,12 @@ cr.define('extensions', function() { ...@@ -60,6 +63,12 @@ cr.define('extensions', function() {
}); });
}, },
/** @private */
onActivityLogTap_: function() {
extensions.navigation.navigateTo(
{page: Page.ACTIVITY_LOG, extensionId: this.data.id});
},
/** /**
* @param {string} description * @param {string} description
* @param {string} fallback * @param {string} fallback
......
...@@ -29,6 +29,12 @@ ...@@ -29,6 +29,12 @@
<structure name="IDR_MD_EXTENSIONS_CODE_SECTION_JS" <structure name="IDR_MD_EXTENSIONS_CODE_SECTION_JS"
file="code_section.js" file="code_section.js"
type="chrome_html" /> type="chrome_html" />
<structure name="IDR_MD_EXTENSIONS_ACTIVITY_LOG_HTML"
file="activity_log.html"
type="chrome_html" />
<structure name="IDR_MD_EXTENSIONS_ACTIVITY_LOG_JS"
file="activity_log.js"
type="chrome_html" />
<structure name="IDR_MD_EXTENSIONS_DETAIL_VIEW_HTML" <structure name="IDR_MD_EXTENSIONS_DETAIL_VIEW_HTML"
file="detail_view.html" file="detail_view.html"
type="chrome_html" /> type="chrome_html" />
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<link rel="import" href="chrome://resources/cr_elements/cr_view_manager/cr_view_manager.html"> <link rel="import" href="chrome://resources/cr_elements/cr_view_manager/cr_view_manager.html">
<link rel="import" href="chrome://resources/html/assert.html"> <link rel="import" href="chrome://resources/html/assert.html">
<link rel="import" href="chrome://resources/html/cr.html"> <link rel="import" href="chrome://resources/html/cr.html">
<link rel="import" href="activity_log.html">
<link rel="import" href="detail_view.html"> <link rel="import" href="detail_view.html">
<link rel="import" href="drop_overlay.html"> <link rel="import" href="drop_overlay.html">
<link rel="import" href="error_page.html"> <link rel="import" href="error_page.html">
...@@ -89,11 +90,18 @@ ...@@ -89,11 +90,18 @@
<template> <template>
<extensions-detail-view delegate="[[delegate]]" slot="view" <extensions-detail-view delegate="[[delegate]]" slot="view"
in-dev-mode="[[inDevMode]]" in-dev-mode="[[inDevMode]]"
show-activity-log="[[showActivityLog]]"
incognito-available="[[incognitoAvailable_]]" incognito-available="[[incognitoAvailable_]]"
data="[[detailViewItem_]]"> data="[[detailViewItem_]]">
</extensions-detail-view> </extensions-detail-view>
</template> </template>
</cr-lazy-render> </cr-lazy-render>
<cr-lazy-render id="activity-log">
<template>
<extensions-activity-log slot="view">
</extensions-activity-log>
</template>
</cr-lazy-render>
<cr-lazy-render id="keyboard-shortcuts"> <cr-lazy-render id="keyboard-shortcuts">
<template> <template>
<extensions-keyboard-shortcuts delegate="[[delegate]]" slot="view" <extensions-keyboard-shortcuts delegate="[[delegate]]" slot="view"
......
...@@ -51,6 +51,11 @@ cr.define('extensions', function() { ...@@ -51,6 +51,11 @@ cr.define('extensions', function() {
value: () => loadTimeData.getBoolean('inDevMode'), value: () => loadTimeData.getBoolean('inDevMode'),
}, },
showActivityLog: {
type: Boolean,
value: () => loadTimeData.getBoolean('showActivityLog'),
},
devModeControlledByPolicy: { devModeControlledByPolicy: {
type: Boolean, type: Boolean,
value: false, value: false,
...@@ -400,7 +405,8 @@ cr.define('extensions', function() { ...@@ -400,7 +405,8 @@ cr.define('extensions', function() {
// We should never try and remove a non-existent item. // We should never try and remove a non-existent item.
assert(index >= 0); assert(index >= 0);
this.splice(listId, index, 1); this.splice(listId, index, 1);
if ((this.currentPage_.page == Page.DETAILS || if ((this.currentPage_.page == Page.ACTIVITY_LOG ||
this.currentPage_.page == Page.DETAILS ||
this.currentPage_.page == Page.ERRORS) && this.currentPage_.page == Page.ERRORS) &&
this.currentPage_.extensionId == itemId) { this.currentPage_.extensionId == itemId) {
// Leave the details page (the 'list' page is a fine choice). // Leave the details page (the 'list' page is a fine choice).
...@@ -453,6 +459,15 @@ cr.define('extensions', function() { ...@@ -453,6 +459,15 @@ cr.define('extensions', function() {
this.detailViewItem_ = assert(data); this.detailViewItem_ = assert(data);
else if (toPage == Page.ERRORS) else if (toPage == Page.ERRORS)
this.errorPageItem_ = assert(data); this.errorPageItem_ = assert(data);
else if (toPage == Page.ACTIVITY_LOG) {
if (!this.showActivityLog) {
// Redirect back to the details page if we try to view the
// activity log of an extension but the flag is not set.
extensions.navigation.replaceWith(
{page: Page.DETAILS, extensionId: newPage.extensionId});
return;
}
}
if (fromPage != toPage) { if (fromPage != toPage) {
/** @type {CrViewManagerElement} */ (this.$.viewManager) /** @type {CrViewManagerElement} */ (this.$.viewManager)
...@@ -505,7 +520,8 @@ cr.define('extensions', function() { ...@@ -505,7 +520,8 @@ cr.define('extensions', function() {
onViewExitFinish_: function(e) { onViewExitFinish_: function(e) {
const viewType = e.path[0].tagName; const viewType = e.path[0].tagName;
if (viewType == 'EXTENSIONS-ITEM-LIST' || if (viewType == 'EXTENSIONS-ITEM-LIST' ||
viewType == 'EXTENSIONS-KEYBOARD-SHORTCUTS') { viewType == 'EXTENSIONS-KEYBOARD-SHORTCUTS' ||
viewType == 'EXTENSIONS-ACTIVITY-LOG') {
return; return;
} }
......
...@@ -12,6 +12,7 @@ cr.exportPath('extensions'); ...@@ -12,6 +12,7 @@ cr.exportPath('extensions');
const Page = { const Page = {
LIST: 'items-list', LIST: 'items-list',
DETAILS: 'details-view', DETAILS: 'details-view',
ACTIVITY_LOG: 'activity-log',
SHORTCUTS: 'keyboard-shortcuts', SHORTCUTS: 'keyboard-shortcuts',
ERRORS: 'error-page', ERRORS: 'error-page',
}; };
...@@ -99,6 +100,9 @@ cr.define('extensions', function() { ...@@ -99,6 +100,9 @@ cr.define('extensions', function() {
let id = search.get('id'); let id = search.get('id');
if (id) if (id)
return {page: Page.DETAILS, extensionId: id}; return {page: Page.DETAILS, extensionId: id};
id = search.get('activity');
if (id)
return {page: Page.ACTIVITY_LOG, extensionId: id};
id = search.get('options'); id = search.get('options');
if (id) if (id)
return {page: Page.DETAILS, extensionId: id, subpage: Dialog.OPTIONS}; return {page: Page.DETAILS, extensionId: id, subpage: Dialog.OPTIONS};
...@@ -180,6 +184,9 @@ cr.define('extensions', function() { ...@@ -180,6 +184,9 @@ cr.define('extensions', function() {
case Page.LIST: case Page.LIST:
path = '/'; path = '/';
break; break;
case Page.ACTIVITY_LOG:
path = '/?activity=' + entry.extensionId;
break;
case Page.DETAILS: case Page.DETAILS:
if (entry.subpage) { if (entry.subpage) {
assert(entry.subpage == Dialog.OPTIONS); assert(entry.subpage == Dialog.OPTIONS);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "base/command_line.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/timer/elapsed_timer.h" #include "base/timer/elapsed_timer.h"
...@@ -16,6 +17,7 @@ ...@@ -16,6 +17,7 @@
#include "chrome/browser/extensions/chrome_extension_browser_constants.h" #include "chrome/browser/extensions/chrome_extension_browser_constants.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/metrics_handler.h" #include "chrome/browser/ui/webui/metrics_handler.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h" #include "chrome/common/url_constants.h"
#include "chrome/grit/browser_resources.h" #include "chrome/grit/browser_resources.h"
...@@ -48,6 +50,7 @@ namespace extensions { ...@@ -48,6 +50,7 @@ namespace extensions {
namespace { namespace {
constexpr char kInDevModeKey[] = "inDevMode"; constexpr char kInDevModeKey[] = "inDevMode";
constexpr char kShowActivityLogKey[] = "showActivityLog";
constexpr char kLoadTimeClassesKey[] = "loadTimeClasses"; constexpr char kLoadTimeClassesKey[] = "loadTimeClasses";
struct LocalizedString { struct LocalizedString {
...@@ -255,6 +258,7 @@ content::WebUIDataSource* CreateMdExtensionsSource(bool in_dev_mode) { ...@@ -255,6 +258,7 @@ content::WebUIDataSource* CreateMdExtensionsSource(bool in_dev_mode) {
{"toolbarUpdatingToast", IDS_MD_EXTENSIONS_TOOLBAR_UPDATING_TOAST}, {"toolbarUpdatingToast", IDS_MD_EXTENSIONS_TOOLBAR_UPDATING_TOAST},
{"updateRequiredByPolicy", {"updateRequiredByPolicy",
IDS_MD_EXTENSIONS_DISABLED_UPDATE_REQUIRED_BY_POLICY}, IDS_MD_EXTENSIONS_DISABLED_UPDATE_REQUIRED_BY_POLICY},
{"viewActivityLog", IDS_EXTENSIONS_ACTIVITY_LOG},
{"viewBackgroundPage", IDS_EXTENSIONS_BACKGROUND_PAGE}, {"viewBackgroundPage", IDS_EXTENSIONS_BACKGROUND_PAGE},
{"viewIncognito", IDS_EXTENSIONS_VIEW_INCOGNITO}, {"viewIncognito", IDS_EXTENSIONS_VIEW_INCOGNITO},
{"viewInactive", IDS_EXTENSIONS_VIEW_INACTIVE}, {"viewInactive", IDS_EXTENSIONS_VIEW_INACTIVE},
...@@ -314,8 +318,10 @@ content::WebUIDataSource* CreateMdExtensionsSource(bool in_dev_mode) { ...@@ -314,8 +318,10 @@ content::WebUIDataSource* CreateMdExtensionsSource(bool in_dev_mode) {
IDS_MD_EXTENSIONS_HOST_PERMISSIONS_LEARN_MORE, IDS_MD_EXTENSIONS_HOST_PERMISSIONS_LEARN_MORE,
base::ASCIIToUTF16( base::ASCIIToUTF16(
chrome_extension_constants::kRuntimeHostPermissionsHelpURL))); chrome_extension_constants::kRuntimeHostPermissionsHelpURL)));
source->AddBoolean(kInDevModeKey, in_dev_mode); source->AddBoolean(kInDevModeKey, in_dev_mode);
source->AddBoolean(kShowActivityLogKey,
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExtensionActivityLogging));
source->AddString(kLoadTimeClassesKey, GetLoadTimeClasses(in_dev_mode)); source->AddString(kLoadTimeClassesKey, GetLoadTimeClasses(in_dev_mode));
#if BUILDFLAG(OPTIMIZE_WEBUI) #if BUILDFLAG(OPTIMIZE_WEBUI)
......
...@@ -439,6 +439,29 @@ TEST_F( ...@@ -439,6 +439,29 @@ TEST_F(
extension_manager_tests.TestNames.UrlNavigationToDetails); extension_manager_tests.TestNames.UrlNavigationToDetails);
}); });
TEST_F(
'CrExtensionsManagerTestWithIdQueryParam', 'UrlNavigationToActivityLogFail',
function() {
this.runMochaTest(
extension_manager_tests.TestNames.UrlNavigationToActivityLogFail);
});
CrExtensionsManagerTestWithActivityLogFlag =
class extends CrExtensionsManagerTestWithIdQueryParam {
/** @override */
get commandLineSwitches() {
return [{
switchName: 'enable-extension-activity-logging',
}];
}
};
TEST_F(
'CrExtensionsManagerTestWithActivityLogFlag',
'UrlNavigationToActivityLogSuccess', function() {
this.runMochaTest(
extension_manager_tests.TestNames.UrlNavigationToActivityLogSuccess);
});
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Extension Keyboard Shortcuts Tests // Extension Keyboard Shortcuts Tests
......
...@@ -45,6 +45,7 @@ cr.define('extension_detail_view_tests', function() { ...@@ -45,6 +45,7 @@ cr.define('extension_detail_view_tests', function() {
item.set('delegate', mockDelegate); item.set('delegate', mockDelegate);
item.set('inDevMode', false); item.set('inDevMode', false);
item.set('incognitoAvailable', true); item.set('incognitoAvailable', true);
item.set('showActivityLog', false);
document.body.appendChild(item); document.body.appendChild(item);
}); });
...@@ -115,6 +116,11 @@ cr.define('extension_detail_view_tests', function() { ...@@ -115,6 +116,11 @@ cr.define('extension_detail_view_tests', function() {
item.set('data.optionsPage', {openInTab: true, url: optionsUrl}); item.set('data.optionsPage', {openInTab: true, url: optionsUrl});
expectTrue(testIsVisible('#extensions-options')); expectTrue(testIsVisible('#extensions-options'));
expectFalse(testIsVisible('#extensions-activity-log-link'));
item.set('showActivityLog', true);
Polymer.dom.flush();
expectTrue(testIsVisible('#extensions-activity-log-link'));
item.set('data.manifestHomePageUrl', 'http://example.com'); item.set('data.manifestHomePageUrl', 'http://example.com');
Polymer.dom.flush(); Polymer.dom.flush();
expectTrue(testIsVisible('#extensionWebsite')); expectTrue(testIsVisible('#extensionWebsite'));
...@@ -200,8 +206,29 @@ cr.define('extension_detail_view_tests', function() { ...@@ -200,8 +206,29 @@ cr.define('extension_detail_view_tests', function() {
'chrome-extension://' + extensionData.id + '/options.html'; 'chrome-extension://' + extensionData.id + '/options.html';
item.set('data.optionsPage', {openInTab: true, url: optionsUrl}); item.set('data.optionsPage', {openInTab: true, url: optionsUrl});
item.set('data.prettifiedPath', 'foo/bar/baz/'); item.set('data.prettifiedPath', 'foo/bar/baz/');
item.set('showActivityLog', true);
Polymer.dom.flush(); Polymer.dom.flush();
let currentPage = null;
extensions.navigation.addListener(newPage => {
currentPage = newPage;
});
// Even though the command line flag is not set for activity log, we
// still expect to navigate to it after clicking the link as the logic to
// redirect the page back to the details view is in manager.js.
// Since this behavior does not happen in the testing environment,
// we test the behavior in manager_test.js.
MockInteractions.tap(item.$$('#extensions-activity-log-link'));
expectDeepEquals(
currentPage,
{page: Page.ACTIVITY_LOG, extensionId: extensionData.id});
// Reset current page and test delegate calls.
extensions.navigation.navigateTo(
{page: Page.DETAILS, extensionId: extensionData.id});
currentPage = null;
mockDelegate.testClickingCalls( mockDelegate.testClickingCalls(
item.$$('#allow-incognito').getLabel(), 'setItemAllowedIncognito', item.$$('#allow-incognito').getLabel(), 'setItemAllowedIncognito',
[extensionData.id, true]); [extensionData.id, true]);
......
...@@ -10,6 +10,10 @@ cr.define('extension_manager_tests', function() { ...@@ -10,6 +10,10 @@ cr.define('extension_manager_tests', function() {
ItemListVisibility: 'item list visibility', ItemListVisibility: 'item list visibility',
SplitItems: 'split items', SplitItems: 'split items',
UrlNavigationToDetails: 'url navigation to details', UrlNavigationToDetails: 'url navigation to details',
UrlNavigationToActivityLogFail:
'url navigation to activity log without flag set',
UrlNavigationToActivityLogSuccess:
'url navigation to activity log with flag set',
}; };
function getDataByName(list, name) { function getDataByName(list, name) {
...@@ -120,6 +124,50 @@ cr.define('extension_manager_tests', function() { ...@@ -120,6 +124,50 @@ cr.define('extension_manager_tests', function() {
Polymer.dom.flush(); Polymer.dom.flush();
assertViewActive('extensions-detail-view'); assertViewActive('extensions-detail-view');
}); });
test(assert(TestNames.UrlNavigationToActivityLogFail), function() {
expectFalse(manager.showActivityLog);
// Try to open activity log with a valid ID.
extensions.navigation.navigateTo({
page: Page.ACTIVITY_LOG,
extensionId: 'ldnnhddmnhbkjipkidpdiheffobcpfmf'
});
Polymer.dom.flush();
// Should be re-routed to details page with showActivityLog set to false.
assertViewActive('extensions-detail-view');
const detailsView = manager.$$('extensions-detail-view');
expectFalse(detailsView.showActivityLog);
// Try to open activity log with an invalid ID.
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');
});
test(assert(TestNames.UrlNavigationToActivityLogSuccess), function() {
expectTrue(manager.showActivityLog);
// Try to open activity log with a valid ID.
extensions.navigation.navigateTo({
page: Page.ACTIVITY_LOG,
extensionId: 'ldnnhddmnhbkjipkidpdiheffobcpfmf'
});
Polymer.dom.flush();
// Should be on activity log page.
assertViewActive('extensions-activity-log');
// Try to open activity log with an invalid ID.
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');
});
}); });
return { return {
......
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