michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: Cu.import("resource://gre/modules/osfile.jsm"); michael@0: const {VariablesView} = michael@0: Cu.import("resource:///modules/devtools/VariablesView.jsm", {}); michael@0: michael@0: const VARIABLES_VIEW_URL = michael@0: "chrome://browser/content/devtools/widgets/VariablesView.xul"; michael@0: michael@0: function ManifestEditor(project) { michael@0: this.project = project; michael@0: this._onContainerReady = this._onContainerReady.bind(this); michael@0: this._onEval = this._onEval.bind(this); michael@0: this._onSwitch = this._onSwitch.bind(this); michael@0: this._onDelete = this._onDelete.bind(this); michael@0: this._onNew = this._onNew.bind(this); michael@0: } michael@0: michael@0: ManifestEditor.prototype = { michael@0: get manifest() { return this.project.manifest; }, michael@0: michael@0: get editable() { return this.project.type == "packaged"; }, michael@0: michael@0: show: function(containerElement) { michael@0: let deferred = promise.defer(); michael@0: let iframe = this._iframe = document.createElement("iframe"); michael@0: michael@0: iframe.addEventListener("load", function onIframeLoad() { michael@0: iframe.removeEventListener("load", onIframeLoad, true); michael@0: deferred.resolve(iframe.contentWindow); michael@0: }, true); michael@0: michael@0: iframe.setAttribute("src", VARIABLES_VIEW_URL); michael@0: iframe.classList.add("variables-view"); michael@0: containerElement.appendChild(iframe); michael@0: michael@0: return deferred.promise.then(this._onContainerReady); michael@0: }, michael@0: michael@0: _onContainerReady: function(varWindow) { michael@0: let variablesContainer = varWindow.document.querySelector("#variables"); michael@0: michael@0: variablesContainer.classList.add("manifest-editor"); michael@0: michael@0: let editor = this.editor = new VariablesView(variablesContainer); michael@0: michael@0: editor.onlyEnumVisible = true; michael@0: editor.alignedValues = true; michael@0: editor.actionsFirst = true; michael@0: michael@0: if (this.editable) { michael@0: editor.eval = this._onEval; michael@0: editor.switch = this._onSwitch; michael@0: editor.delete = this._onDelete; michael@0: editor.new = this._onNew; michael@0: } michael@0: michael@0: return this.update(); michael@0: }, michael@0: michael@0: _onEval: function(variable, value) { michael@0: let parent = this._descend(variable.ownerView.symbolicPath); michael@0: try { michael@0: parent[variable.name] = JSON.parse(value); michael@0: } catch(e) { michael@0: Cu.reportError(e); michael@0: } michael@0: michael@0: this.update(); michael@0: }, michael@0: michael@0: _onSwitch: function(variable, newName) { michael@0: if (variable.name == newName) { michael@0: return; michael@0: } michael@0: michael@0: let parent = this._descend(variable.ownerView.symbolicPath); michael@0: parent[newName] = parent[variable.name]; michael@0: delete parent[variable.name]; michael@0: michael@0: this.update(); michael@0: }, michael@0: michael@0: _onDelete: function(variable) { michael@0: let parent = this._descend(variable.ownerView.symbolicPath); michael@0: delete parent[variable.name]; michael@0: }, michael@0: michael@0: _onNew: function(variable, newName, newValue) { michael@0: let parent = this._descend(variable.symbolicPath); michael@0: try { michael@0: parent[newName] = JSON.parse(newValue); michael@0: } catch(e) { michael@0: Cu.reportError(e); michael@0: } michael@0: michael@0: this.update(); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the value located at a given path in the manifest. michael@0: * @param path array michael@0: * A string for each path component: ["developer", "name"] michael@0: */ michael@0: _descend: function(path) { michael@0: let parent = this.manifest; michael@0: while (path.length) { michael@0: parent = parent[path.shift()]; michael@0: } michael@0: return parent; michael@0: }, michael@0: michael@0: update: function() { michael@0: this.editor.rawObject = this.manifest; michael@0: this.editor.commitHierarchy(); michael@0: michael@0: // Wait until the animation from commitHierarchy has completed michael@0: let deferred = promise.defer(); michael@0: setTimeout(deferred.resolve, this.editor.lazyEmptyDelay + 1); michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: save: function() { michael@0: if (this.editable) { michael@0: let validator = new AppValidator(this.project); michael@0: let manifestFile = validator._getPackagedManifestFile(); michael@0: let path = manifestFile.path; michael@0: michael@0: let encoder = new TextEncoder(); michael@0: let data = encoder.encode(JSON.stringify(this.manifest, null, 2)); michael@0: michael@0: return OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp" }); michael@0: } michael@0: michael@0: return promise.resolve(); michael@0: }, michael@0: michael@0: destroy: function() { michael@0: if (this._iframe) { michael@0: this._iframe.remove(); michael@0: } michael@0: } michael@0: };