From f4f6e6dae79fc0032e185fd63c58ac71e91e727a Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Thu, 26 Jun 2014 07:17:54 -0500 Subject: [PATCH] Bug 1021827 - Show menu items for project editor inside of App Manager;r=paul --- browser/devtools/jar.mn | 2 +- .../chrome/content/projecteditor-test.html | 13 -- .../chrome/content/projecteditor-test.xul | 18 ++ .../chrome/content/projecteditor.xul | 8 +- browser/devtools/projecteditor/lib/editors.js | 35 ++-- .../plugins/app-manager/app-project-editor.js | 2 +- .../lib/plugins/delete/delete.js | 4 +- .../lib/plugins/image-view/image-editor.js | 2 +- .../projecteditor/lib/plugins/new/new.js | 27 ++- .../projecteditor/lib/plugins/save/save.js | 40 +++-- .../projecteditor/lib/projecteditor.js | 168 +++++++++++++++--- browser/devtools/projecteditor/lib/shells.js | 2 +- browser/devtools/projecteditor/lib/tree.js | 11 +- .../devtools/projecteditor/test/browser.ini | 2 + .../test/browser_projecteditor_delete_file.js | 2 +- ...browser_projecteditor_immediate_destroy.js | 32 +++- .../test/browser_projecteditor_menubar_01.js | 28 +++ .../test/browser_projecteditor_menubar_02.js | 126 +++++++++++++ browser/devtools/projecteditor/test/head.js | 18 +- browser/devtools/webide/content/webide.js | 31 +++- browser/devtools/webide/content/webide.xul | 3 + .../browser/devtools/projecteditor.properties | 11 +- .../devtools/projecteditor/projecteditor.css | 2 - 23 files changed, 474 insertions(+), 113 deletions(-) delete mode 100644 browser/devtools/projecteditor/chrome/content/projecteditor-test.html create mode 100644 browser/devtools/projecteditor/chrome/content/projecteditor-test.xul create mode 100644 browser/devtools/projecteditor/test/browser_projecteditor_menubar_01.js create mode 100644 browser/devtools/projecteditor/test/browser_projecteditor_menubar_02.js diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn index c80c08e1161..9ac6fa64abb 100644 --- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -10,7 +10,7 @@ browser.jar: content/browser/devtools/projecteditor.xul (projecteditor/chrome/content/projecteditor.xul) content/browser/devtools/readdir.js (projecteditor/lib/helpers/readdir.js) content/browser/devtools/projecteditor-loader.xul (projecteditor/chrome/content/projecteditor-loader.xul) - content/browser/devtools/projecteditor-test.html (projecteditor/chrome/content/projecteditor-test.html) + content/browser/devtools/projecteditor-test.xul (projecteditor/chrome/content/projecteditor-test.xul) content/browser/devtools/projecteditor-loader.js (projecteditor/chrome/content/projecteditor-loader.js) content/browser/devtools/netmonitor.xul (netmonitor/netmonitor.xul) content/browser/devtools/netmonitor.css (netmonitor/netmonitor.css) diff --git a/browser/devtools/projecteditor/chrome/content/projecteditor-test.html b/browser/devtools/projecteditor/chrome/content/projecteditor-test.html deleted file mode 100644 index 1db6acf5623..00000000000 --- a/browser/devtools/projecteditor/chrome/content/projecteditor-test.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/browser/devtools/projecteditor/chrome/content/projecteditor-test.xul b/browser/devtools/projecteditor/chrome/content/projecteditor-test.xul new file mode 100644 index 00000000000..ee2be12f0d3 --- /dev/null +++ b/browser/devtools/projecteditor/chrome/content/projecteditor-test.xul @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/browser/devtools/projecteditor/chrome/content/projecteditor.xul b/browser/devtools/projecteditor/chrome/content/projecteditor.xul index 6e79bb61c64..ba5efeaf7f3 100644 --- a/browser/devtools/projecteditor/chrome/content/projecteditor.xul +++ b/browser/devtools/projecteditor/chrome/content/projecteditor.xul @@ -46,18 +46,12 @@ - - - - - - - + diff --git a/browser/devtools/projecteditor/lib/editors.js b/browser/devtools/projecteditor/lib/editors.js index 9688fd02123..2cfaa695a9c 100644 --- a/browser/devtools/projecteditor/lib/editors.js +++ b/browser/devtools/projecteditor/lib/editors.js @@ -26,6 +26,12 @@ var ItchEditor = Class({ */ hidesToolbar: false, + /** + * A boolean specifying whether the editor can be edited / saved. + * For instance, a 'save' doesn't make sense on an image. + */ + isEditable: false, + toString: function() { return this.label || ""; }, @@ -35,17 +41,19 @@ var ItchEditor = Class({ }, /** - * Initialize the editor with a single document. This should be called + * Initialize the editor with a single host. This should be called * by objects extending this object with: * ItchEditor.prototype.initialize.apply(this, arguments) */ - initialize: function(document) { - this.doc = document; + initialize: function(host) { + this.doc = host.document; this.label = ""; this.elt = this.doc.createElement("vbox"); this.elt.setAttribute("flex", "1"); this.elt.editor = this; this.toolbar = this.doc.querySelector("#projecteditor-toolbar"); + this.projectEditorKeyset = host.projectEditorKeyset; + this.projectEditorCommandset = host.projectEditorCommandset; }, /** @@ -103,6 +111,8 @@ exports.ItchEditor = ItchEditor; var TextEditor = Class({ extends: ItchEditor, + isEditable: true, + /** * Extra keyboard shortcuts to use with the editor. Shortcuts defined * within projecteditor should be triggered when they happen in the editor, and @@ -114,7 +124,7 @@ var TextEditor = Class({ // Copy all of the registered keys into extraKeys object, to notify CodeMirror // that it should be ignoring these keys - [...this.doc.querySelectorAll("#projecteditor-keyset key")].forEach((key) => { + [...this.projectEditorKeyset.querySelectorAll("key")].forEach((key) => { let keyUpper = key.getAttribute("key").toUpperCase(); let toolModifiers = key.getAttribute("modifiers"); let modifiers = { @@ -124,9 +134,10 @@ var TextEditor = Class({ // On the key press, we will dispatch the event within projecteditor. extraKeys[Editor.accel(keyUpper, modifiers)] = () => { - let event = this.doc.createEvent('Event'); + let doc = this.projectEditorCommandset.ownerDocument; + let event = doc.createEvent('Event'); event.initEvent('command', true, true); - let command = this.doc.querySelector("#" + key.getAttribute("command")); + let command = this.projectEditorCommandset.querySelector("#" + key.getAttribute("command")); command.dispatchEvent(event); }; }); @@ -227,22 +238,22 @@ var TextEditor = Class({ /** * Wrapper for TextEditor using JavaScript syntax highlighting. */ -function JSEditor(document) { - return TextEditor(document, Editor.modes.js); +function JSEditor(host) { + return TextEditor(host, Editor.modes.js); } /** * Wrapper for TextEditor using CSS syntax highlighting. */ -function CSSEditor(document) { - return TextEditor(document, Editor.modes.css); +function CSSEditor(host) { + return TextEditor(host, Editor.modes.css); } /** * Wrapper for TextEditor using HTML syntax highlighting. */ -function HTMLEditor(document) { - return TextEditor(document, Editor.modes.html); +function HTMLEditor(host) { + return TextEditor(host, Editor.modes.html); } /** diff --git a/browser/devtools/projecteditor/lib/plugins/app-manager/app-project-editor.js b/browser/devtools/projecteditor/lib/plugins/app-manager/app-project-editor.js index d1d3eda9fa5..5dc20b2f167 100644 --- a/browser/devtools/projecteditor/lib/plugins/app-manager/app-project-editor.js +++ b/browser/devtools/projecteditor/lib/plugins/app-manager/app-project-editor.js @@ -14,7 +14,7 @@ var AppProjectEditor = Class({ hidesToolbar: true, - initialize: function(document, host) { + initialize: function(host) { ItchEditor.prototype.initialize.apply(this, arguments); this.appended = promise.resolve(); this.host = host; diff --git a/browser/devtools/projecteditor/lib/plugins/delete/delete.js b/browser/devtools/projecteditor/lib/plugins/delete/delete.js index 4f66b2420b2..adbf1122151 100644 --- a/browser/devtools/projecteditor/lib/plugins/delete/delete.js +++ b/browser/devtools/projecteditor/lib/plugins/delete/delete.js @@ -14,11 +14,11 @@ var DeletePlugin = Class({ shouldConfirm: true, init: function(host) { - this.host.addCommand({ + this.host.addCommand(this, { id: "cmd-delete" }); this.host.createMenuItem({ - parent: "#directory-menu-popup", + parent: this.host.contextMenuPopup, label: getLocalizedString("projecteditor.deleteLabel"), command: "cmd-delete" }); diff --git a/browser/devtools/projecteditor/lib/plugins/image-view/image-editor.js b/browser/devtools/projecteditor/lib/plugins/image-view/image-editor.js index ec8ec6d26ce..b21efbfe992 100644 --- a/browser/devtools/projecteditor/lib/plugins/image-view/image-editor.js +++ b/browser/devtools/projecteditor/lib/plugins/image-view/image-editor.js @@ -12,7 +12,7 @@ const { ItchEditor } = require("projecteditor/editors"); var ImageEditor = Class({ extends: ItchEditor, - initialize: function(document) { + initialize: function() { ItchEditor.prototype.initialize.apply(this, arguments); this.label = "image"; this.appended = promise.resolve(); diff --git a/browser/devtools/projecteditor/lib/plugins/new/new.js b/browser/devtools/projecteditor/lib/plugins/new/new.js index 807e60c11b2..f9dcb697fb0 100644 --- a/browser/devtools/projecteditor/lib/plugins/new/new.js +++ b/browser/devtools/projecteditor/lib/plugins/new/new.js @@ -12,24 +12,23 @@ const { getLocalizedString } = require("projecteditor/helpers/l10n"); var NewFile = Class({ extends: Plugin, - init: function(host) { - this.host.createMenuItem({ - parent: "#file-menu-popup", - label: getLocalizedString("projecteditor.newLabel"), - command: "cmd-new", - key: "key-new" - }); - this.host.createMenuItem({ - parent: "#directory-menu-popup", - label: getLocalizedString("projecteditor.newLabel"), - command: "cmd-new" - }); - - this.command = this.host.addCommand({ + init: function() { + this.command = this.host.addCommand(this, { id: "cmd-new", key: getLocalizedString("projecteditor.new.commandkey"), modifiers: "accel" }); + this.host.createMenuItem({ + parent: this.host.fileMenuPopup, + label: getLocalizedString("projecteditor.newLabel"), + command: "cmd-new", + key: "key_cmd-new" + }); + this.host.createMenuItem({ + parent: this.host.contextMenuPopup, + label: getLocalizedString("projecteditor.newLabel"), + command: "cmd-new" + }); }, onCommand: function(cmd) { diff --git a/browser/devtools/projecteditor/lib/plugins/save/save.js b/browser/devtools/projecteditor/lib/plugins/save/save.js index 58c1cad4120..a6efa016606 100644 --- a/browser/devtools/projecteditor/lib/plugins/save/save.js +++ b/browser/devtools/projecteditor/lib/plugins/save/save.js @@ -15,29 +15,33 @@ var SavePlugin = Class({ init: function(host) { - this.host.addCommand({ - id: "cmd-saveas", - key: getLocalizedString("projecteditor.save.commandkey"), - modifiers: "accel shift" - }); - this.host.addCommand({ + this.host.addCommand(this, { id: "cmd-save", key: getLocalizedString("projecteditor.save.commandkey"), modifiers: "accel" }); + this.host.addCommand(this, { + id: "cmd-saveas", + key: getLocalizedString("projecteditor.save.commandkey"), + modifiers: "accel shift" + }); + this.host.createMenuItem({ + parent: this.host.fileMenuPopup, + label: getLocalizedString("projecteditor.saveLabel"), + command: "cmd-save", + key: "key_cmd-save" + }); + this.host.createMenuItem({ + parent: this.host.fileMenuPopup, + label: getLocalizedString("projecteditor.saveAsLabel"), + command: "cmd-saveas", + key: "key_cmd-saveas" + }); + }, - // Wait until we can add things into the app manager menu - // this.host.createMenuItem({ - // parent: "#file-menu-popup", - // label: "Save", - // command: "cmd-save", - // key: "key-save" - // }); - // this.host.createMenuItem({ - // parent: "#file-menu-popup", - // label: "Save As", - // command: "cmd-saveas", - // }); + isCommandEnabled: function(cmd) { + let currentEditor = this.host.currentEditor; + return currentEditor.isEditable; }, onCommand: function(cmd) { diff --git a/browser/devtools/projecteditor/lib/projecteditor.js b/browser/devtools/projecteditor/lib/projecteditor.js index d05f5d6fd95..5d0c1486346 100644 --- a/browser/devtools/projecteditor/lib/projecteditor.js +++ b/browser/devtools/projecteditor/lib/projecteditor.js @@ -76,15 +76,24 @@ var ProjectEditor = Class({ * The iframe to inject the DOM into. If this is not * specified, then this.load(frame) will need to be called * before accessing ProjectEditor. + * @param Object options + * - menubar: a element to inject menus into + * - menuindex: Integer child index to insert menus */ - initialize: function(iframe) { + initialize: function(iframe, options = {}) { this._onTreeSelected = this._onTreeSelected.bind(this); this._onTreeResourceRemoved = this._onTreeResourceRemoved.bind(this); this._onEditorCreated = this._onEditorCreated.bind(this); this._onEditorActivated = this._onEditorActivated.bind(this); this._onEditorDeactivated = this._onEditorDeactivated.bind(this); - this._updateEditorMenuItems = this._updateEditorMenuItems.bind(this); - + this._updateMenuItems = this._updateMenuItems.bind(this); + this.destroy = this.destroy.bind(this); + this.menubar = options.menubar || null; + this.menuindex = options.menuindex || null; + this._menuEnabled = true; + this._destroyed = false; + this._loaded = false; + this._pluginCommands = new Map(); if (iframe) { this.load(iframe); } @@ -111,7 +120,12 @@ var ProjectEditor = Class({ this.iframe = iframe; let domReady = () => { + if (this._destroyed) { + deferred.reject("Error: ProjectEditor has been destroyed before loading"); + return; + } this._onLoad(); + this._loaded = true; deferred.resolve(this); }; @@ -130,9 +144,11 @@ var ProjectEditor = Class({ this.document = this.iframe.contentDocument; this.window = this.iframe.contentWindow; + this._initCommands(); + this._buildMenubar(); this._buildSidebar(); - this.window.addEventListener("unload", this.destroy.bind(this)); + this.window.addEventListener("unload", this.destroy, false); // Editor management this.shells = new ShellDeck(this, this.document); @@ -143,9 +159,6 @@ var ProjectEditor = Class({ let shellContainer = this.document.querySelector("#shells-deck-container"); shellContainer.appendChild(this.shells.elt); - let popup = this.document.querySelector("#edit-menu-popup"); - popup.addEventListener("popupshowing", this.updateEditorMenuItems); - // We are not allowing preset projects for now - rebuild a fresh one // each time. this.setProject(new Project({ @@ -155,10 +168,40 @@ var ProjectEditor = Class({ openFiles: [] })); - this._initCommands(); this._initPlugins(); }, + _buildMenubar: function() { + + this.editMenu = this.document.getElementById("edit-menu"); + this.fileMenu = this.document.getElementById("file-menu"); + + this.editMenuPopup = this.document.getElementById("edit-menu-popup"); + this.fileMenuPopup = this.document.getElementById("file-menu-popup"); + this.editMenu.addEventListener("popupshowing", this._updateMenuItems); + this.fileMenu.addEventListener("popupshowing", this._updateMenuItems); + + if (this.menubar) { + let body = this.menubar.ownerDocument.body || + this.menubar.ownerDocument.querySelector("window"); + body.appendChild(this.projectEditorCommandset); + body.appendChild(this.projectEditorKeyset); + body.appendChild(this.editorCommandset); + body.appendChild(this.editorKeyset); + body.appendChild(this.contextMenuPopup); + + let index = this.menuindex || 0; + this.menubar.insertBefore(this.editMenu, this.menubar.children[index]); + this.menubar.insertBefore(this.fileMenu, this.menubar.children[index]); + } else { + this.document.getElementById("projecteditor-menubar").style.display = "block"; + } + + // Insert a controller to allow enabling and disabling of menu items. + this._commandWindow = this.editorCommandset.ownerDocument.defaultView; + this._commandController = getCommandController(this); + this._commandWindow.controllers.insertControllerAt(0, this._commandController); + }, /** * Create the project tree sidebar that lists files. @@ -166,7 +209,8 @@ var ProjectEditor = Class({ _buildSidebar: function() { this.projectTree = new ProjectTreeView(this.document, { resourceVisible: this.resourceVisible.bind(this), - resourceFormatter: this.resourceFormatter.bind(this) + resourceFormatter: this.resourceFormatter.bind(this), + contextMenuPopup: this.contextMenuPopup }); on(this, this.projectTree, "selection", this._onTreeSelected); on(this, this.projectTree, "resource-removed", this._onTreeResourceRemoved); @@ -179,8 +223,16 @@ var ProjectEditor = Class({ * Set up listeners for commands to dispatch to all of the plugins */ _initCommands: function() { - this.commands = this.document.querySelector("#projecteditor-commandset"); - this.commands.addEventListener("command", (evt) => { + + this.projectEditorCommandset = this.document.getElementById("projecteditor-commandset"); + this.projectEditorKeyset = this.document.getElementById("projecteditor-keyset"); + + this.editorCommandset = this.document.getElementById("editMenuCommands"); + this.editorKeyset = this.document.getElementById("editMenuKeys"); + + this.contextMenuPopup = this.document.getElementById("context-menu-popup"); + + this.projectEditorCommandset.addEventListener("command", (evt) => { evt.stopPropagation(); evt.preventDefault(); this.pluginDispatch("onCommand", evt.target.id, evt.target); @@ -207,17 +259,35 @@ var ProjectEditor = Class({ /** * Enable / disable necessary menu items using globalOverlay.js. */ - _updateEditorMenuItems: function() { - this.window.goUpdateGlobalEditMenuItems(); - this.window.goUpdateGlobalEditMenuItems(); - let commands = ['cmd_undo', 'cmd_redo', 'cmd_delete', 'cmd_findAgain']; - commands.forEach(this.window.goUpdateCommand); + _updateMenuItems: function() { + let window = this.editMenu.ownerDocument.defaultView; + let commands = ['cmd_undo', 'cmd_redo', 'cmd_delete', 'cmd_cut', 'cmd_copy', 'cmd_paste']; + commands.forEach(window.goUpdateCommand); + + for (let c of this._pluginCommands.keys()) { + window.goUpdateCommand(c); + } }, /** * Destroy all objects on the iframe unload event. */ destroy: function() { + this._destroyed = true; + + + // If been destroyed before the iframe finished loading, then + // the properties below will not exist. + if (!this._loaded) { + this.iframe.setAttribute("src", "about:blank"); + return; + } + + // Reset the src for the iframe so if it reused for a new ProjectEditor + // instance, the load will fire properly. + this.window.removeEventListener("unload", this.destroy, false); + this.iframe.setAttribute("src", "about:blank"); + this._plugins.forEach(plugin => { plugin.destroy(); }); forget(this, this.projectTree); @@ -226,6 +296,17 @@ var ProjectEditor = Class({ this.shells.destroy(); + this.projectEditorCommandset.remove(); + this.projectEditorKeyset.remove(); + this.editorCommandset.remove(); + this.editorKeyset.remove(); + this.contextMenuPopup.remove(); + this.editMenu.remove(); + this.fileMenu.remove(); + + this._commandWindow.controllers.removeController(this._commandController); + this._commandController = null; + forget(this, this.project); this.project.destroy(); this.project = null; @@ -384,11 +465,13 @@ var ProjectEditor = Class({ * @returns DOMElement * The command element that has been created. */ - addCommand: function(definition) { - let command = this.document.createElement("command"); + addCommand: function(plugin, definition) { + this._pluginCommands.set(definition.id, plugin); + let document = this.projectEditorKeyset.ownerDocument; + let command = document.createElement("command"); command.setAttribute("id", definition.id); if (definition.key) { - let key = this.document.createElement("key"); + let key = document.createElement("key"); key.id = "key_" + definition.id; let keyName = definition.key; @@ -399,10 +482,10 @@ var ProjectEditor = Class({ } key.setAttribute("modifiers", definition.modifiers); key.setAttribute("command", definition.id); - this.document.getElementById("projecteditor-keyset").appendChild(key); + this.projectEditorKeyset.appendChild(key); } command.setAttribute("oncommand", "void(0);"); // needed. See bug 371900 - this.document.getElementById("projecteditor-commandset").appendChild(command); + this.projectEditorCommandset.appendChild(command); return command; }, @@ -610,6 +693,49 @@ var ProjectEditor = Class({ get currentEditor() { return this.shells.currentEditor; }, + + /** + * Whether or not menu items should be able to be enabled. + * Note that even if this is true, certain menu items will not be + * enabled until the correct state is achieved (for instance, the + * 'copy' menu item is only enabled when there is a selection). + * But if this is false, then nothing will be enabled. + */ + set menuEnabled(val) { + this._menuEnabled = val; + this._updateMenuItems(); + }, + + get menuEnabled() { + return this._menuEnabled; + } }); + +/** + * Returns a controller object that can be used for + * editor-specific commands such as find, jump to line, + * copy/paste, etc. + */ +function getCommandController(host) { + return { + supportsCommand: function (cmd) { + return host._pluginCommands.get(cmd); + }, + + isCommandEnabled: function (cmd) { + if (!host.menuEnabled) { + return false; + } + let plugin = host._pluginCommands.get(cmd); + if (plugin && plugin.isCommandEnabled) { + return plugin.isCommandEnabled(cmd); + } + return true; + }, + doCommand: function(cmd) { + } + }; +} + exports.ProjectEditor = ProjectEditor; diff --git a/browser/devtools/projecteditor/lib/shells.js b/browser/devtools/projecteditor/lib/shells.js index 584769054e6..d4c75e8fa72 100644 --- a/browser/devtools/projecteditor/lib/shells.js +++ b/browser/devtools/projecteditor/lib/shells.js @@ -36,7 +36,7 @@ var Shell = Class({ let constructor = this._editorTypeForResource(); - this.editor = constructor(this.doc, this.host); + this.editor = constructor(this.host); this.editor.shell = this; this.editorAppended = this.editor.appended; diff --git a/browser/devtools/projecteditor/lib/tree.js b/browser/devtools/projecteditor/lib/tree.js index 744e6324c8f..c02b1e3705b 100644 --- a/browser/devtools/projecteditor/lib/tree.js +++ b/browser/devtools/projecteditor/lib/tree.js @@ -106,7 +106,7 @@ var ResourceContainer = Class({ */ openContextMenu: function(ev) { ev.preventDefault(); - let popup = this.tree.doc.getElementById("directory-menu-popup"); + let popup = this.tree.options.contextMenuPopup; popup.openPopupAtScreen(ev.screenX, ev.screenY, true); }, @@ -208,13 +208,14 @@ var TreeView = Class({ /** * @param Document document * @param Object options + * - contextMenuPopup: a element * - resourceFormatter: a function(Resource, DOMNode) * that renders the resource into the view * - resourceVisible: a function(Resource) -> Boolean * that determines if the resource should show up. */ - initialize: function(document, options) { - this.doc = document; + initialize: function(doc, options) { + this.doc = doc; this.options = merge({ resourceFormatter: function(resource, elt) { elt.textContent = resource.toString(); @@ -223,14 +224,14 @@ var TreeView = Class({ this.models = new Set(); this.roots = new Set(); this._containers = new Map(); - this.elt = document.createElementNS(HTML_NS, "div"); + this.elt = this.doc.createElementNS(HTML_NS, "div"); this.elt.tree = this; this.elt.className = "sources-tree"; this.elt.setAttribute("with-arrows", "true"); this.elt.setAttribute("theme", "dark"); this.elt.setAttribute("flex", "1"); - this.children = document.createElementNS(HTML_NS, "ul"); + this.children = this.doc.createElementNS(HTML_NS, "ul"); this.elt.appendChild(this.children); this.resourceChildrenChanged = this.resourceChildrenChanged.bind(this); diff --git a/browser/devtools/projecteditor/test/browser.ini b/browser/devtools/projecteditor/test/browser.ini index 2f08fed3e8d..2ad99841262 100644 --- a/browser/devtools/projecteditor/test/browser.ini +++ b/browser/devtools/projecteditor/test/browser.ini @@ -12,6 +12,8 @@ support-files = [browser_projecteditor_external_change.js] [browser_projecteditor_immediate_destroy.js] [browser_projecteditor_init.js] +[browser_projecteditor_menubar_01.js] +[browser_projecteditor_menubar_02.js] [browser_projecteditor_new_file.js] [browser_projecteditor_stores.js] [browser_projecteditor_tree_selection.js] diff --git a/browser/devtools/projecteditor/test/browser_projecteditor_delete_file.js b/browser/devtools/projecteditor/test/browser_projecteditor_delete_file.js index 14f63ec2b53..9a6c917dc98 100644 --- a/browser/devtools/projecteditor/test/browser_projecteditor_delete_file.js +++ b/browser/devtools/projecteditor/test/browser_projecteditor_delete_file.js @@ -37,7 +37,7 @@ let test = asyncTest(function*() { let defer = promise.defer(); let resource = container.resource; - let popup = projecteditor.document.getElementById("directory-menu-popup"); + let popup = projecteditor.contextMenuPopup; info ("Going to attempt deletion for: " + resource.path) onPopupShow(popup).then(function () { diff --git a/browser/devtools/projecteditor/test/browser_projecteditor_immediate_destroy.js b/browser/devtools/projecteditor/test/browser_projecteditor_immediate_destroy.js index 570b037d1a6..2cfaf3228bd 100644 --- a/browser/devtools/projecteditor/test/browser_projecteditor_immediate_destroy.js +++ b/browser/devtools/projecteditor/test/browser_projecteditor_immediate_destroy.js @@ -10,8 +10,9 @@ let test = asyncTest(function* () { info ("Testing tab closure when projecteditor is in various states"); + let loaderUrl = "chrome://browser/content/devtools/projecteditor-test.xul"; - yield addTab("chrome://browser/content/devtools/projecteditor-test.html").then(() => { + yield addTab(loaderUrl).then(() => { let iframe = content.document.getElementById("projecteditor-iframe"); ok (iframe, "Tab has placeholder iframe for projecteditor"); @@ -19,7 +20,7 @@ let test = asyncTest(function* () { gBrowser.removeCurrentTab(); }); - yield addTab("chrome://browser/content/devtools/projecteditor-test.html").then(() => { + yield addTab(loaderUrl).then(() => { let iframe = content.document.getElementById("projecteditor-iframe"); ok (iframe, "Tab has placeholder iframe for projecteditor"); @@ -30,7 +31,7 @@ let test = asyncTest(function* () { gBrowser.removeCurrentTab(); }); - yield addTab("chrome://browser/content/devtools/projecteditor-test.html").then(() => { + yield addTab(loaderUrl).then(() => { let iframe = content.document.getElementById("projecteditor-iframe"); ok (iframe, "Tab has placeholder iframe for projecteditor"); @@ -43,7 +44,7 @@ let test = asyncTest(function* () { gBrowser.removeCurrentTab(); }); - yield addTab("chrome://browser/content/devtools/projecteditor-test.html").then(() => { + yield addTab(loaderUrl).then(() => { let iframe = content.document.getElementById("projecteditor-iframe"); ok (iframe, "Tab has placeholder iframe for projecteditor"); @@ -56,6 +57,29 @@ let test = asyncTest(function* () { }); }); + yield addTab(loaderUrl).then(() => { + let iframe = content.document.getElementById("projecteditor-iframe"); + ok (iframe, "Tab has placeholder iframe for projecteditor"); + + let projecteditor = ProjectEditor.ProjectEditor(iframe); + ok (projecteditor, "ProjectEditor has been initialized"); + + let loadedDone = promise.defer(); + projecteditor.loaded.then(() => { + ok (false, "Loaded has finished after destroy() has been called"); + loadedDone.resolve(); + }, () => { + ok (true, "Loaded has been rejected after destroy() has been called"); + loadedDone.resolve(); + }); + + projecteditor.destroy(); + + return loadedDone.promise.then(() => { + gBrowser.removeCurrentTab(); + }); + }); + finish(); }); diff --git a/browser/devtools/projecteditor/test/browser_projecteditor_menubar_01.js b/browser/devtools/projecteditor/test/browser_projecteditor_menubar_01.js new file mode 100644 index 00000000000..1a0181a0158 --- /dev/null +++ b/browser/devtools/projecteditor/test/browser_projecteditor_menubar_01.js @@ -0,0 +1,28 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that menu bar appends to the correct document. + +let test = asyncTest(function*() { + let projecteditor = yield addProjectEditorTabForTempDirectory({ + menubar: false + }); + ok(projecteditor, "ProjectEditor has loaded"); + + let fileMenu = projecteditor.document.getElementById("file-menu"); + let editMenu = projecteditor.document.getElementById("edit-menu"); + ok (fileMenu, "The menu has loaded in the projecteditor document"); + ok (editMenu, "The menu has loaded in the projecteditor document"); + + let projecteditor2 = yield addProjectEditorTabForTempDirectory(); + let menubar = projecteditor2.menubar; + let fileMenu = projecteditor2.document.getElementById("file-menu"); + let editMenu = projecteditor2.document.getElementById("edit-menu"); + ok (!fileMenu, "The menu has NOT loaded in the projecteditor document"); + ok (!editMenu, "The menu has NOT loaded in the projecteditor document"); + ok (content.document.querySelector("#file-menu"), "The menu has loaded in the specified element"); + ok (content.document.querySelector("#edit-menu"), "The menu has loaded in the specified element"); +}); \ No newline at end of file diff --git a/browser/devtools/projecteditor/test/browser_projecteditor_menubar_02.js b/browser/devtools/projecteditor/test/browser_projecteditor_menubar_02.js new file mode 100644 index 00000000000..d43494056e6 --- /dev/null +++ b/browser/devtools/projecteditor/test/browser_projecteditor_menubar_02.js @@ -0,0 +1,126 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +loadHelperScript("helper_edits.js"); + +// Test menu bar enabled / disabled state. + +let test = asyncTest(function*() { + let projecteditor = yield addProjectEditorTabForTempDirectory(); + let menubar = projecteditor.menubar; + + // let projecteditor = yield addProjectEditorTabForTempDirectory(); + ok(projecteditor, "ProjectEditor has loaded"); + + let fileMenu = menubar.querySelector("#file-menu"); + let editMenu = menubar.querySelector("#edit-menu"); + ok (fileMenu, "The menu has loaded in the projecteditor document"); + ok (editMenu, "The menu has loaded in the projecteditor document"); + + let cmdNew = fileMenu.querySelector("[command=cmd-new]"); + let cmdSave = fileMenu.querySelector("[command=cmd-save]"); + let cmdSaveas = fileMenu.querySelector("[command=cmd-saveas]"); + + let cmdUndo = editMenu.querySelector("[command=cmd_undo]"); + let cmdRedo = editMenu.querySelector("[command=cmd_redo]"); + let cmdCut = editMenu.querySelector("[command=cmd_cut]"); + let cmdCopy = editMenu.querySelector("[command=cmd_copy]"); + let cmdPaste = editMenu.querySelector("[command=cmd_paste]"); + + info ("Checking initial state of menus"); + yield openAndCloseMenu(fileMenu); + yield openAndCloseMenu(editMenu); + + is (cmdNew.getAttribute("disabled"), "", "File menu item is enabled"); + is (cmdSave.getAttribute("disabled"), "true", "File menu item is disabled"); + is (cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled"); + + is (cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled"); + + projecteditor.menuEnabled = false; + + info ("Checking with menuEnabled = false"); + yield openAndCloseMenu(fileMenu); + yield openAndCloseMenu(editMenu); + + is (cmdNew.getAttribute("disabled"), "true", "File menu item is disabled"); + is (cmdSave.getAttribute("disabled"), "true", "File menu item is disabled"); + is (cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled"); + + is (cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled"); + + info ("Checking with menuEnabled=true"); + projecteditor.menuEnabled = true; + + yield openAndCloseMenu(fileMenu); + yield openAndCloseMenu(editMenu); + + is (cmdNew.getAttribute("disabled"), "", "File menu item is enabled"); + is (cmdSave.getAttribute("disabled"), "true", "File menu item is disabled"); + is (cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled"); + + is (cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled"); + + info ("Checking with resource selected"); + let resource = projecteditor.project.allResources()[2]; + yield selectFile(projecteditor, resource); + let editor = projecteditor.currentEditor; + + editor.editor.focus(); + EventUtils.synthesizeKey("foo", { }, projecteditor.window); + + yield openAndCloseMenu(fileMenu); + yield openAndCloseMenu(editMenu); + + is (cmdNew.getAttribute("disabled"), "", "File menu item is enabled"); + is (cmdSave.getAttribute("disabled"), "", "File menu item is enabled"); + is (cmdSaveas.getAttribute("disabled"), "", "File menu item is enabled"); + + is (cmdUndo.getAttribute("disabled"), "", "Edit menu item is enabled"); + is (cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled"); + is (cmdPaste.getAttribute("disabled"), "", "Edit menu item is enabled"); +}); + +function openAndCloseMenu(menu) { + let shown = onPopupShow(menu) + EventUtils.synthesizeMouseAtCenter(menu, {}, menu.ownerDocument.defaultView); + yield shown; + let hidden = onPopupHidden(menu) + EventUtils.synthesizeMouseAtCenter(menu, {}, menu.ownerDocument.defaultView); + yield hidden; +} + +function onPopupShow(menu) { + let defer = promise.defer(); + menu.addEventListener("popupshown", function onpopupshown() { + menu.removeEventListener("popupshown", onpopupshown); + defer.resolve(); + }); + return defer.promise; +} + +function onPopupHidden(menu) { + let defer = promise.defer(); + menu.addEventListener("popuphidden", function onpopupshown() { + menu.removeEventListener("popuphidden", onpopupshown); + defer.resolve(); + }); + return defer.promise; +} diff --git a/browser/devtools/projecteditor/test/head.js b/browser/devtools/projecteditor/test/head.js index 81d944a85f5..6adf5ab00fa 100644 --- a/browser/devtools/projecteditor/test/head.js +++ b/browser/devtools/projecteditor/test/head.js @@ -88,25 +88,29 @@ function loadHelperScript(filePath) { Services.scriptloader.loadSubScript(testDir + "/" + filePath, this); } -function addProjectEditorTabForTempDirectory() { +function addProjectEditorTabForTempDirectory(opts = {}) { TEMP_PATH = buildTempDirectoryStructure(); - let CUSTOM_OPTS = { + let customOpts = { name: "Test", iconUrl: "chrome://browser/skin/devtools/tool-options.svg", projectOverviewURL: SAMPLE_WEBAPP_URL }; - return addProjectEditorTab().then((projecteditor) => { - return projecteditor.setProjectToAppPath(TEMP_PATH, CUSTOM_OPTS).then(() => { + return addProjectEditorTab(opts).then((projecteditor) => { + return projecteditor.setProjectToAppPath(TEMP_PATH, customOpts).then(() => { return projecteditor; }); }); } -function addProjectEditorTab() { - return addTab("chrome://browser/content/devtools/projecteditor-test.html").then(() => { +function addProjectEditorTab(opts = {}) { + return addTab("chrome://browser/content/devtools/projecteditor-test.xul").then(() => { let iframe = content.document.getElementById("projecteditor-iframe"); - let projecteditor = ProjectEditor.ProjectEditor(iframe); + if (opts.menubar !== false) { + opts.menubar = content.document.querySelector("menubar"); + } + let projecteditor = ProjectEditor.ProjectEditor(iframe, opts); + ok (iframe, "Tab has placeholder iframe for projecteditor"); ok (projecteditor, "ProjectEditor has been initialized"); diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js index 63124787280..7a6d2acbb1b 100644 --- a/browser/devtools/webide/content/webide.js +++ b/browser/devtools/webide/content/webide.js @@ -321,13 +321,34 @@ let UI = { // ProjectEditor & details screen + destroyProjectEditor: function() { + if (this.projecteditor) { + this.projecteditor.destroy(); + this.projecteditor = null; + } + }, + + updateProjectEditorMenusVisibility: function() { + if (this.projecteditor) { + let panel = document.querySelector("#deck").selectedPanel; + if (panel && panel.id == "deck-panel-projecteditor") { + this.projecteditor.menuEnabled = true; + } else { + this.projecteditor.menuEnabled = false; + } + } + }, + getProjectEditor: function() { if (this.projecteditor) { return this.projecteditor.loaded; } let projecteditorIframe = document.querySelector("#deck-panel-projecteditor"); - this.projecteditor = ProjectEditor.ProjectEditor(projecteditorIframe); + this.projecteditor = ProjectEditor.ProjectEditor(projecteditorIframe, { + menubar: document.querySelector("#main-menubar"), + menuindex: 1 + }); this.projecteditor.on("onEditorSave", (editor, resource) => { AppManager.validateProject(AppManager.selectedProject); }); @@ -409,12 +430,14 @@ let UI = { let deck = document.querySelector("#deck"); let panel = deck.querySelector("#deck-panel-" + id); deck.selectedPanel = panel; + this.updateProjectEditorMenusVisibility(); }, resetDeck: function() { this.resetFocus(); let deck = document.querySelector("#deck"); deck.selectedPanel = null; + this.updateProjectEditorMenusVisibility(); }, /********** COMMANDS **********/ @@ -822,7 +845,11 @@ let Cmds = { }, toggleEditors: function() { - Services.prefs.setBoolPref("devtools.webide.showProjectEditor", !UI.isProjectEditorEnabled()); + let isNowEnabled = !UI.isProjectEditorEnabled(); + Services.prefs.setBoolPref("devtools.webide.showProjectEditor", isNowEnabled); + if (!isNowEnabled) { + UI.destroyProjectEditor(); + } UI.openProject(); }, diff --git a/browser/devtools/webide/content/webide.xul b/browser/devtools/webide/content/webide.xul index 298d6d84580..a9af2c9f630 100644 --- a/browser/devtools/webide/content/webide.xul +++ b/browser/devtools/webide/content/webide.xul @@ -9,6 +9,8 @@ %webideDTD; ]> + + @@ -23,6 +25,7 @@ width="640" height="480" persist="screenX screenY width height"> + diff --git a/browser/locales/en-US/chrome/browser/devtools/projecteditor.properties b/browser/locales/en-US/chrome/browser/devtools/projecteditor.properties index 338014e2f41..0881f97c41c 100644 --- a/browser/locales/en-US/chrome/browser/devtools/projecteditor.properties +++ b/browser/locales/en-US/chrome/browser/devtools/projecteditor.properties @@ -32,10 +32,19 @@ projecteditor.deleteFolderPromptMessage=Are you sure you want to delete this fol projecteditor.deleteFilePromptMessage=Are you sure you want to delete this file? # LOCALIZATION NOTE (projecteditor.newLabel): -# This string is displayed as a context menu item for adding a new file to +# This string is displayed as a menu item for adding a new file to # the directory. projecteditor.newLabel=New… +# LOCALIZATION NOTE (projecteditor.saveLabel): +# This string is displayed as a menu item for saving the current file. +projecteditor.saveLabel=Save + +# LOCALIZATION NOTE (projecteditor.saveAsLabel): +# This string is displayed as a menu item for saving the current file +# with a new name. +projecteditor.saveAsLabel=Save As… + # LOCALIZATION NOTE (projecteditor.selectFileLabel): # This string is displayed as the title on the file picker when saving a file. projecteditor.selectFileLabel=Select a File diff --git a/browser/themes/shared/devtools/projecteditor/projecteditor.css b/browser/themes/shared/devtools/projecteditor/projecteditor.css index e119cdd4158..73f4ed35717 100644 --- a/browser/themes/shared/devtools/projecteditor/projecteditor.css +++ b/browser/themes/shared/devtools/projecteditor/projecteditor.css @@ -26,8 +26,6 @@ } #projecteditor-menubar { - /* XXX: Hide menu bar until we have option to add menu items - to an existing one. */ display: none; }