// 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. cr.define('extensions', function() { 'use strict'; /** * @implements {extensions.ActivityLogDelegate} * @implements {extensions.ActivityLogEventDelegate} * @implements {extensions.ErrorPageDelegate} * @implements {extensions.ItemDelegate} * @implements {extensions.KeyboardShortcutDelegate} * @implements {extensions.LoadErrorDelegate} * @implements {extensions.PackDialogDelegate} * @implements {extensions.ToolbarDelegate} */ class Service { constructor() { /** @private {boolean} */ this.isDeleting_ = false; /** @private {!Set} */ 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} 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 { extensions.navigation.navigateTo({ page: extensions.Page.DETAILS, subpage: extensions.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(); } } cr.addSingletonGetter(Service); return {Service: Service}; });