blob: 0e1e52cb28d02b1616d378ebc9b8db994bf246be [file] [log] [blame]
// Copyright 2015 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.
import {assert} from 'chrome://resources/js/assert.m.js';
import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
import {ActivityLogDelegate} from './activity_log/activity_log_history.js';
import {ActivityLogEventDelegate} from './activity_log/activity_log_stream.js';
import {ErrorPageDelegate} from './error_page.js';
import {ItemDelegate} from './item.js';
import {KeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js';
import {LoadErrorDelegate} from './load_error.js';
import {Dialog, navigation, Page} from './navigation_helper.js';
import {PackDialogDelegate} from './pack_dialog.js';
import {ToolbarDelegate} from './toolbar.js';
/**
* @implements {ActivityLogDelegate}
* @implements {ActivityLogEventDelegate}
* @implements {ErrorPageDelegate}
* @implements {ItemDelegate}
* @implements {KeyboardShortcutDelegate}
* @implements {LoadErrorDelegate}
* @implements {PackDialogDelegate}
* @implements {ToolbarDelegate}
*/
export class Service {
constructor() {
/** @private {boolean} */
this.isDeleting_ = false;
/** @private {!Set<string>} */
this.eventsToIgnoreOnce_ = new Set();
}
getProfileConfiguration() {
return new Promise(function(resolve, reject) {
chrome.developerPrivate.getProfileConfiguration(resolve);
});
}
getItemStateChangedTarget() {
return chrome.developerPrivate.onItemStateChanged;
}
/**
* @param {string} extensionId
* @param {!chrome.developerPrivate.EventType} eventType
* @return {boolean}
*/
shouldIgnoreUpdate(extensionId, eventType) {
return this.eventsToIgnoreOnce_.delete(`${extensionId}_${eventType}`);
}
/**
* @param {string} extensionId
* @param {!chrome.developerPrivate.EventType} eventType
*/
ignoreNextEvent(extensionId, eventType) {
this.eventsToIgnoreOnce_.add(`${extensionId}_${eventType}`);
}
getProfileStateChangedTarget() {
return chrome.developerPrivate.onProfileStateChanged;
}
getExtensionsInfo() {
return new Promise(function(resolve, reject) {
chrome.developerPrivate.getExtensionsInfo(
{includeDisabled: true, includeTerminated: true}, resolve);
});
}
/** @override */
getExtensionSize(id) {
return new Promise(function(resolve, reject) {
chrome.developerPrivate.getExtensionSize(id, resolve);
});
}
/** @override */
addRuntimeHostPermission(id, host) {
return new Promise((resolve, reject) => {
chrome.developerPrivate.addHostPermission(id, host, () => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message);
return;
}
resolve();
});
});
}
/** @override */
removeRuntimeHostPermission(id, host) {
return new Promise((resolve, reject) => {
chrome.developerPrivate.removeHostPermission(id, host, () => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message);
return;
}
resolve();
});
});
}
/**
* Opens a file browser dialog for the user to select a file (or directory).
* @param {chrome.developerPrivate.SelectType} selectType
* @param {chrome.developerPrivate.FileType} fileType
* @return {Promise<string>} The promise to be resolved with the selected
* path.
*/
chooseFilePath_(selectType, fileType) {
return new Promise(function(resolve, reject) {
chrome.developerPrivate.choosePath(selectType, fileType, function(path) {
if (chrome.runtime.lastError &&
chrome.runtime.lastError != 'File selection was canceled.') {
reject(chrome.runtime.lastError);
} else {
resolve(path || '');
}
});
});
}
/** @override */
updateExtensionCommandKeybinding(extensionId, commandName, keybinding) {
chrome.developerPrivate.updateExtensionCommand({
extensionId: extensionId,
commandName: commandName,
keybinding: keybinding,
});
}
/** @override */
updateExtensionCommandScope(extensionId, commandName, scope) {
// The COMMAND_REMOVED event needs to be ignored since it is sent before
// the command is added back with the updated scope but can be handled
// after the COMMAND_ADDED event.
this.ignoreNextEvent(
extensionId, chrome.developerPrivate.EventType.COMMAND_REMOVED);
chrome.developerPrivate.updateExtensionCommand({
extensionId: extensionId,
commandName: commandName,
scope: scope,
});
}
/** @override */
setShortcutHandlingSuspended(isCapturing) {
chrome.developerPrivate.setShortcutHandlingSuspended(isCapturing);
}
/**
* @param {chrome.developerPrivate.LoadUnpackedOptions=} opt_options
* @return {!Promise} A signal that loading finished, rejected if any error
* occurred.
* @private
*/
loadUnpackedHelper_(opt_options) {
return new Promise(function(resolve, reject) {
const options = Object.assign(
{
failQuietly: true,
populateError: true,
},
opt_options);
chrome.developerPrivate.loadUnpacked(options, (loadError) => {
if (chrome.runtime.lastError &&
chrome.runtime.lastError.message !=
'File selection was canceled.') {
throw new Error(chrome.runtime.lastError.message);
}
if (loadError) {
return reject(loadError);
}
resolve();
});
});
}
/** @override */
deleteItem(id) {
if (this.isDeleting_) {
return;
}
this.isDeleting_ = true;
chrome.management.uninstall(id, {showConfirmDialog: true}, () => {
// The "last error" was almost certainly the user canceling the dialog.
// Do nothing. We only check it so we don't get noisy logs.
/** @suppress {suspiciousCode} */
chrome.runtime.lastError;
this.isDeleting_ = false;
});
}
/** @override */
setItemEnabled(id, isEnabled) {
chrome.management.setEnabled(id, isEnabled);
}
/** @override */
setItemAllowedIncognito(id, isAllowedIncognito) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: id,
incognitoAccess: isAllowedIncognito,
});
}
/** @override */
setItemAllowedOnFileUrls(id, isAllowedOnFileUrls) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: id,
fileAccess: isAllowedOnFileUrls,
});
}
/** @override */
setItemHostAccess(id, hostAccess) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: id,
hostAccess: hostAccess,
});
}
/** @override */
setItemCollectsErrors(id, collectsErrors) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: id,
errorCollection: collectsErrors,
});
}
/** @override */
inspectItemView(id, view) {
chrome.developerPrivate.openDevTools({
extensionId: id,
renderProcessId: view.renderProcessId,
renderViewId: view.renderViewId,
incognito: view.incognito,
});
}
/**
* @param {string} url
* @override
*/
openUrl(url) {
window.open(url);
}
/** @override */
reloadItem(id) {
return new Promise(function(resolve, reject) {
chrome.developerPrivate.reload(
id, {failQuietly: true, populateErrorForUnpacked: true},
(loadError) => {
if (loadError) {
reject(loadError);
return;
}
resolve();
});
});
}
/** @override */
repairItem(id) {
chrome.developerPrivate.repairExtension(id);
}
/** @override */
showItemOptionsPage(extension) {
assert(extension && extension.optionsPage);
if (extension.optionsPage.openInTab) {
chrome.developerPrivate.showOptions(extension.id);
} else {
navigation.navigateTo({
page: Page.DETAILS,
subpage: Dialog.OPTIONS,
extensionId: extension.id,
});
}
}
/** @override */
setProfileInDevMode(inDevMode) {
chrome.developerPrivate.updateProfileConfiguration(
{inDeveloperMode: inDevMode});
}
/** @override */
loadUnpacked() {
return this.loadUnpackedHelper_();
}
/** @override */
retryLoadUnpacked(retryGuid) {
// Attempt to load an unpacked extension, optionally as another attempt at
// a previously-specified load.
return this.loadUnpackedHelper_({retryGuid: retryGuid});
}
/** @override */
choosePackRootDirectory() {
return this.chooseFilePath_(
chrome.developerPrivate.SelectType.FOLDER,
chrome.developerPrivate.FileType.LOAD);
}
/** @override */
choosePrivateKeyPath() {
return this.chooseFilePath_(
chrome.developerPrivate.SelectType.FILE,
chrome.developerPrivate.FileType.PEM);
}
/** @override */
packExtension(rootPath, keyPath, flag, callback) {
chrome.developerPrivate.packDirectory(rootPath, keyPath, flag, callback);
}
/** @override */
updateAllExtensions() {
return new Promise((resolve) => {
chrome.developerPrivate.autoUpdate(resolve);
chrome.metricsPrivate.recordUserAction('Options_UpdateExtensions');
});
}
/** @override */
deleteErrors(extensionId, errorIds, type) {
chrome.developerPrivate.deleteExtensionErrors({
extensionId: extensionId,
errorIds: errorIds,
type: type,
});
}
/** @override */
requestFileSource(args) {
return new Promise(function(resolve, reject) {
chrome.developerPrivate.requestFileSource(args, resolve);
});
}
/** @override */
showInFolder(id) {
chrome.developerPrivate.showPath(id);
}
/** @override */
getExtensionActivityLog(extensionId) {
return new Promise(function(resolve, reject) {
chrome.activityLogPrivate.getExtensionActivities(
{
activityType: chrome.activityLogPrivate.ExtensionActivityFilter.ANY,
extensionId: extensionId
},
resolve);
});
}
/** @override */
getFilteredExtensionActivityLog(extensionId, searchTerm) {
const anyType = chrome.activityLogPrivate.ExtensionActivityFilter.ANY;
// Construct one filter for each API call we will make: one for substring
// search by api call, one for substring search by page URL, and one for
// substring search by argument URL. % acts as a wildcard.
const activityLogFilters = [
{
activityType: anyType,
extensionId: extensionId,
apiCall: `%${searchTerm}%`,
},
{
activityType: anyType,
extensionId: extensionId,
pageUrl: `%${searchTerm}%`,
},
{
activityType: anyType,
extensionId: extensionId,
argUrl: `%${searchTerm}%`
}
];
const promises = activityLogFilters.map(
filter => new Promise(function(resolve, reject) {
chrome.activityLogPrivate.getExtensionActivities(filter, resolve);
}));
return Promise.all(promises).then(results => {
// We may have results that are present in one or more searches, so
// we merge them here. We also assume that every distinct activity
// id corresponds to exactly one activity.
const activitiesById = new Map();
for (const result of results) {
for (const activity of result.activities) {
activitiesById.set(activity.activityId, activity);
}
}
return {activities: Array.from(activitiesById.values())};
});
}
/** @override */
deleteActivitiesById(activityIds) {
return new Promise(function(resolve, reject) {
chrome.activityLogPrivate.deleteActivities(activityIds, resolve);
});
}
/** @override */
deleteActivitiesFromExtension(extensionId) {
return new Promise(function(resolve, reject) {
chrome.activityLogPrivate.deleteActivitiesByExtension(
extensionId, resolve);
});
}
/** @override */
getOnExtensionActivity() {
return chrome.activityLogPrivate.onExtensionActivity;
}
/** @override */
downloadActivities(rawActivityData, fileName) {
const blob = new Blob([rawActivityData], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
}
/**
* Attempts to load an unpacked extension via a drag-n-drop gesture.
* @return {!Promise}
*/
loadUnpackedFromDrag() {
return this.loadUnpackedHelper_({useDraggedPath: true});
}
installDroppedFile() {
chrome.developerPrivate.installDroppedFile();
}
notifyDragInstallInProgress() {
chrome.developerPrivate.notifyDragInstallInProgress();
}
}
addSingletonGetter(Service);