/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { createElement, createFactory, } = require("resource://devtools/client/shared/vendor/react.js"); const EventEmitter = require("resource://devtools/shared/event-emitter.js"); const { Provider, } = require("resource://devtools/client/shared/vendor/react-redux.js"); const extensionsSidebarReducer = require("resource://devtools/client/inspector/extensions/reducers/sidebar.js"); const { default: objectInspectorReducer, } = require("resource://devtools/client/shared/components/object-inspector/reducer.js"); const ExtensionSidebarComponent = createFactory( require("resource://devtools/client/inspector/extensions/components/ExtensionSidebar.js") ); const { updateExtensionPage, updateObjectTreeView, updateExpressionResultView, removeExtensionSidebar, } = require("resource://devtools/client/inspector/extensions/actions/sidebar.js"); /** * ExtensionSidebar instances represents Inspector sidebars installed by add-ons * using the devtools.panels.elements.createSidebarPane WebExtensions API. * * The WebExtensions API registers the extensions' sidebars on the toolbox instance * (using the registerInspectorExtensionSidebar method) and, once the Inspector has been * created, the toolbox uses the Inpector createExtensionSidebar method to create the * ExtensionSidebar instances and then it registers them to the Inspector. * * @param {Inspector} inspector * The inspector where the sidebar should be hooked to. * @param {Object} options * @param {String} options.id * The unique id of the sidebar. * @param {String} options.title * The title of the sidebar. */ class ExtensionSidebar { constructor(inspector, { id, title }) { EventEmitter.decorate(this); this.inspector = inspector; this.store = inspector.store; this.id = id; this.title = title; this.destroyed = false; this.store.injectReducer("extensionsSidebar", extensionsSidebarReducer); this.store.injectReducer("objectInspector", objectInspectorReducer); } /** * Lazily create a React ExtensionSidebarComponent wrapped into a Redux Provider. */ get provider() { if (!this._provider) { this._provider = createElement( Provider, { store: this.store, key: this.id, title: this.title, }, ExtensionSidebarComponent({ id: this.id, onExtensionPageMount: containerEl => { this.emit("extension-page-mount", containerEl); }, onExtensionPageUnmount: containerEl => { this.emit("extension-page-unmount", containerEl); }, serviceContainer: { highlightDomElement: async (grip, options = {}) => { const nodeFront = await this.inspector.inspectorFront.getNodeFrontFromNodeGrip( grip ); return this.inspector.highlighters.showHighlighterTypeForNode( this.inspector.highlighters.TYPES.BOXMODEL, nodeFront, options ); }, unHighlightDomElement: async () => { return this.inspector.highlighters.hideHighlighterType( this.inspector.highlighters.TYPES.BOXMODEL ); }, openNodeInInspector: async grip => { const nodeFront = await this.inspector.inspectorFront.getNodeFrontFromNodeGrip( grip ); const onInspectorUpdated = this.inspector.once("inspector-updated"); const onNodeFrontSet = this.inspector.toolbox.selection.setNodeFront(nodeFront, { reason: "inspector-extension-sidebar", }); return Promise.all([onNodeFrontSet, onInspectorUpdated]); }, }, }) ); } return this._provider; } /** * Destroy the ExtensionSidebar instance, dispatch a removeExtensionSidebar Redux action * (which removes the related state from the Inspector store) and clear any reference * to the inspector, the Redux store and the lazily created Redux Provider component. * * This method is called by the inspector when the ExtensionSidebar is being removed * (or when the inspector is being destroyed). */ destroy() { if (this.destroyed) { throw new Error( `ExtensionSidebar instances cannot be destroyed more than once` ); } // Remove the data related to this extension from the inspector store. this.store.dispatch(removeExtensionSidebar(this.id)); this.inspector = null; this.store = null; this._provider = null; this.destroyed = true; } /** * Dispatch an objectTreeView action to change the SidebarComponent into an * ObjectTreeView React Component, which shows the passed javascript object * in the sidebar. */ setObject(object) { if (this.removed) { throw new Error( "Unable to set an object preview on a removed ExtensionSidebar" ); } this.store.dispatch(updateObjectTreeView(this.id, object)); } /** * Dispatch an objectPreview action to change the SidebarComponent into an * ObjectPreview React Component, which shows the passed value grip * in the sidebar. */ setExpressionResult(expressionResult, rootTitle) { if (this.removed) { throw new Error( "Unable to set an object preview on a removed ExtensionSidebar" ); } this.store.dispatch( updateExpressionResultView(this.id, expressionResult, rootTitle) ); } setExtensionPage(iframeURL) { if (this.removed) { throw new Error( "Unable to set an object preview on a removed ExtensionSidebar" ); } this.store.dispatch(updateExtensionPage(this.id, iframeURL)); } } module.exports = ExtensionSidebar;