extension-sidebar.js (6397B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 createElement, 9 createFactory, 10 } = require("resource://devtools/client/shared/vendor/react.mjs"); 11 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 12 const { 13 Provider, 14 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 15 16 const extensionsSidebarReducer = require("resource://devtools/client/inspector/extensions/reducers/sidebar.js"); 17 const { 18 default: objectInspectorReducer, 19 } = require("resource://devtools/client/shared/components/object-inspector/reducer.js"); 20 21 const { 22 updateExtensionPage, 23 updateObjectTreeView, 24 updateExpressionResultView, 25 removeExtensionSidebar, 26 } = require("resource://devtools/client/inspector/extensions/actions/sidebar.js"); 27 28 /** 29 * ExtensionSidebar instances represents Inspector sidebars installed by add-ons 30 * using the devtools.panels.elements.createSidebarPane WebExtensions API. 31 * 32 * The WebExtensions API registers the extensions' sidebars on the toolbox instance 33 * (using the registerInspectorExtensionSidebar method) and, once the Inspector has been 34 * created, the toolbox uses the Inpector createExtensionSidebar method to create the 35 * ExtensionSidebar instances and then it registers them to the Inspector. 36 * 37 * @param {Inspector} inspector 38 * The inspector where the sidebar should be hooked to. 39 * @param {object} options 40 * @param {string} options.id 41 * The unique id of the sidebar. 42 * @param {string} options.title 43 * The title of the sidebar. 44 */ 45 class ExtensionSidebar { 46 constructor(inspector, { id, title }) { 47 EventEmitter.decorate(this); 48 this.inspector = inspector; 49 this.store = inspector.store; 50 this.id = id; 51 this.title = title; 52 this.destroyed = false; 53 54 this.store.injectReducer("extensionsSidebar", extensionsSidebarReducer); 55 this.store.injectReducer("objectInspector", objectInspectorReducer); 56 } 57 58 /** 59 * Lazily create a React ExtensionSidebarComponent wrapped into a Redux Provider. 60 */ 61 get provider() { 62 if (!this._provider) { 63 // Load the ExtensionSidebar component via the Browser Loader as it ultimately loads Reps and Object Inspector, 64 // which are expected to be loaded in a document scope. 65 const ExtensionSidebarComponent = createFactory( 66 this.inspector.browserRequire( 67 "resource://devtools/client/inspector/extensions/components/ExtensionSidebar.js" 68 ) 69 ); 70 this._provider = createElement( 71 Provider, 72 { 73 store: this.store, 74 key: this.id, 75 title: this.title, 76 }, 77 ExtensionSidebarComponent({ 78 id: this.id, 79 onExtensionPageMount: containerEl => { 80 this.emit("extension-page-mount", containerEl); 81 }, 82 onExtensionPageUnmount: containerEl => { 83 this.emit("extension-page-unmount", containerEl); 84 }, 85 serviceContainer: { 86 highlightDomElement: async (grip, options = {}) => { 87 const nodeFront = 88 await this.inspector.inspectorFront.getNodeFrontFromNodeGrip( 89 grip 90 ); 91 return this.inspector.highlighters.showHighlighterTypeForNode( 92 this.inspector.highlighters.TYPES.BOXMODEL, 93 nodeFront, 94 options 95 ); 96 }, 97 unHighlightDomElement: async () => { 98 return this.inspector.highlighters.hideHighlighterType( 99 this.inspector.highlighters.TYPES.BOXMODEL 100 ); 101 }, 102 openNodeInInspector: async grip => { 103 const nodeFront = 104 await this.inspector.inspectorFront.getNodeFrontFromNodeGrip( 105 grip 106 ); 107 const onInspectorUpdated = 108 this.inspector.once("inspector-updated"); 109 const onNodeFrontSet = 110 this.inspector.toolbox.selection.setNodeFront(nodeFront, { 111 reason: "inspector-extension-sidebar", 112 }); 113 114 return Promise.all([onNodeFrontSet, onInspectorUpdated]); 115 }, 116 }, 117 }) 118 ); 119 } 120 121 return this._provider; 122 } 123 124 /** 125 * Destroy the ExtensionSidebar instance, dispatch a removeExtensionSidebar Redux action 126 * (which removes the related state from the Inspector store) and clear any reference 127 * to the inspector, the Redux store and the lazily created Redux Provider component. 128 * 129 * This method is called by the inspector when the ExtensionSidebar is being removed 130 * (or when the inspector is being destroyed). 131 */ 132 destroy() { 133 if (this.destroyed) { 134 throw new Error( 135 `ExtensionSidebar instances cannot be destroyed more than once` 136 ); 137 } 138 139 // Remove the data related to this extension from the inspector store. 140 this.store.dispatch(removeExtensionSidebar(this.id)); 141 142 this.inspector = null; 143 this.store = null; 144 this._provider = null; 145 146 this.destroyed = true; 147 } 148 149 /** 150 * Dispatch an objectTreeView action to change the SidebarComponent into an 151 * ObjectTreeView React Component, which shows the passed javascript object 152 * in the sidebar. 153 */ 154 setObject(object) { 155 if (this.removed) { 156 throw new Error( 157 "Unable to set an object preview on a removed ExtensionSidebar" 158 ); 159 } 160 161 this.store.dispatch(updateObjectTreeView(this.id, object)); 162 } 163 164 /** 165 * Dispatch an objectPreview action to change the SidebarComponent into an 166 * ObjectPreview React Component, which shows the passed value grip 167 * in the sidebar. 168 */ 169 setExpressionResult(expressionResult, rootTitle) { 170 if (this.removed) { 171 throw new Error( 172 "Unable to set an object preview on a removed ExtensionSidebar" 173 ); 174 } 175 176 this.store.dispatch( 177 updateExpressionResultView(this.id, expressionResult, rootTitle) 178 ); 179 } 180 181 setExtensionPage(iframeURL) { 182 if (this.removed) { 183 throw new Error( 184 "Unable to set an object preview on a removed ExtensionSidebar" 185 ); 186 } 187 188 this.store.dispatch(updateExtensionPage(this.id, iframeURL)); 189 } 190 } 191 192 module.exports = ExtensionSidebar;