Bug 1292592 - remove xul dependency in sourceeditor draft
authorFred Lin <gasolin@mozilla.com>
Tue, 30 Aug 2016 10:52:31 +0800
changeset 413049 845749955d911fb9a121fda6fa242c84aa876092
parent 413048 dac96d576734fbbeb06ecd236fb473e22c430136
child 531108 6155853de8ab65a5ca7593a26fc2b6c9ad2d791e
push id29303
push userbmo:gasolin@mozilla.com
push dateTue, 13 Sep 2016 10:16:45 +0000
bugs1292592
milestone51.0a1
Bug 1292592 - remove xul dependency in sourceeditor MozReview-Commit-ID: GhQiyrqF138
devtools/client/inspector/markup/html-editor.js
devtools/client/inspector/markup/markup.xhtml
devtools/client/locales/en-US/sourceeditor.properties
devtools/client/shared/theme-switching.js
devtools/client/sourceeditor/editor.js
--- a/devtools/client/inspector/markup/html-editor.js
+++ b/devtools/client/inspector/markup/html-editor.js
@@ -52,20 +52,18 @@ function HTMLEditor(htmlDocument) {
   config.extraKeys[ctrl("Enter")] = this.hide;
   config.extraKeys.F2 = this.hide;
   config.extraKeys.Esc = this.hide.bind(this, false);
 
   this.container.addEventListener("click", this.hide, false);
   this.editorInner.addEventListener("click", stopPropagation, false);
   this.editor = new Editor(config);
 
-  let iframe = this.editorInner.ownerDocument.createElement("iframe");
-  this.editor.appendTo(this.editorInner, iframe).then(() => {
-    this.hide(false);
-  }).then(null, (err) => console.log(err.message));
+  this.editor.appendToLocalElement(this.editorInner);
+  this.hide(false);
 }
 
 HTMLEditor.prototype = {
 
   /**
    * Need to refresh position by manually setting CSS values, so this will
    * need to be called on resizes and other sizing changes.
    */
--- a/devtools/client/inspector/markup/markup.xhtml
+++ b/devtools/client/inspector/markup/markup.xhtml
@@ -2,22 +2,25 @@
 <!-- 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/. -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <link rel="stylesheet" href="chrome://devtools/content/inspector/markup/markup.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/skin/markup.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" type="text/css"/>
 
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"/>
-
+  <script type="application/javascript;version=1.8"
+          src="chrome://devtools/content/sourceeditor/codemirror/codemirror.bundle.js"/>
 </head>
 <body class="theme-body devtools-monospace" role="application">
 
 <!-- NOTE THAT WE MAKE EXTENSIVE USE OF HTML COMMENTS IN THIS FILE IN ORDER -->
 <!-- TO MAKE SPANS READABLE WHILST AVOIDING SIGNIFICANT WHITESPACE          -->
 
   <div id="root-wrapper" role="presentation">
     <div id="root" role="presentation"></div>
--- a/devtools/client/locales/en-US/sourceeditor.properties
+++ b/devtools/client/locales/en-US/sourceeditor.properties
@@ -112,8 +112,40 @@ showInformation2.commandkey=Shift-Ctrl-S
 # conjunction with accel (Command on Mac or Ctrl on other platforms) to find
 # the typed search
 find.commandkey=F
 
 # LOCALIZATION NOTE  (findAgain.commandkey): This is the key to use in
 # conjunction with accel (Command on Mac or Ctrl on other platforms) to find
 # again the typed search
 findAgain.commandkey=G
+
+# LOCALIZATION NOTE  (find.key):
+# Key shortcut used to find the typed search
+find.key=CmdOrCtrl+F
+
+# LOCALIZATION NOTE  (findAgain.key):
+# Key shortcut used to find again the typed search
+findNext.key=CmdOrCtrl+G
+
+# LOCALIZATION NOTE (undo.key):
+# Key shortcut used to find the previous typed search
+findPrev.key=Shift+CmdOrCtrl+G
+
+# LOCALIZATION NOTE  (jumpToLine.key):
+# Key shortcut used to jump to a specific line in the editor.
+jumpToLine.key=CmdOrCtrl+J
+
+# LOCALIZATION NOTE (undo.key):
+# Key shortcut used to undo the last change.
+undo.key=CmdOrCtrl+Z
+
+# LOCALIZATION NOTE (redo.key):
+# Key shortcut used to redo the last change.
+redo.key=CmdOrCtrl+Y
+
+# LOCALIZATION NOTE (selectAll.key):
+# Key shortcut used to delete the character after the cursor
+delete.key=Delete
+
+# LOCALIZATION NOTE (selectAll.key):
+# Key shortcut used to select the whole content of the editor.
+selectAll.key=CmdOrCtrl+A
--- a/devtools/client/shared/theme-switching.js
+++ b/devtools/client/shared/theme-switching.js
@@ -1,16 +1,20 @@
 /* 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/. */
 
 /* eslint-env browser */
 "use strict";
 (function () {
-  const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-dark-theme.css";
+  const { utils: Cu } = Components;
+  const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+  const Services = require("Services");
+  const { gDevTools } = require("devtools/client/framework/devtools");
+  const { watchCSS } = require("devtools/client/shared/css-reload");
   let documentElement = document.documentElement;
 
   let os;
   let platform = navigator.platform;
   if (platform.startsWith("Win")) {
     os = "win";
   } else if (platform.startsWith("Mac")) {
     os = "mac";
@@ -105,29 +109,31 @@
 
     let loadEvents = [];
     for (let url of newThemeDef.stylesheets) {
       let {styleSheet, loadPromise} = appendStyleSheet(url);
       devtoolsStyleSheets.get(newThemeDef).push(styleSheet);
       loadEvents.push(loadPromise);
     }
 
-    // Floating scroll-bars like in OSX
-    let hiddenDOMWindow = Cc["@mozilla.org/appshell/appShellService;1"]
-                 .getService(Ci.nsIAppShellService)
-                 .hiddenDOMWindow;
-
-    // TODO: extensions might want to customize scrollbar styles too.
-    if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
-      if (newTheme == "dark") {
-        StylesheetUtils.loadSheet(window, SCROLLBARS_URL, "agent");
-      } else if (oldTheme == "dark") {
-        StylesheetUtils.removeSheet(window, SCROLLBARS_URL, "agent");
+    try {
+      const StylesheetUtils = require("sdk/stylesheet/utils");
+      const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-dark-theme.css";
+      // TODO: extensions might want to customize scrollbar styles too.
+      if (!Services.appShell.hiddenDOMWindow
+        .matchMedia("(-moz-overlay-scrollbars)").matches) {
+        if (newTheme == "dark") {
+          StylesheetUtils.loadSheet(window, SCROLLBARS_URL, "agent");
+        } else if (oldTheme == "dark") {
+          StylesheetUtils.removeSheet(window, SCROLLBARS_URL, "agent");
+        }
+        forceStyle();
       }
-      forceStyle();
+    } catch (e) {
+      console.warn("customize scrollbar styles is only supported in firefox");
     }
 
     Promise.all(loadEvents).then(() => {
       // Unload all stylesheets and classes from the old theme.
       if (oldThemeDef) {
         for (let name of oldThemeDef.classList) {
           documentElement.classList.remove(name);
         }
@@ -157,23 +163,16 @@
   }
 
   function handlePrefChange(event, data) {
     if (data.pref == "devtools.theme") {
       switchTheme(data.newValue, data.oldValue);
     }
   }
 
-  const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-  const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-  const Services = require("Services");
-  const { gDevTools } = require("devtools/client/framework/devtools");
-  const StylesheetUtils = require("sdk/stylesheet/utils");
-  const { watchCSS } = require("devtools/client/shared/css-reload");
-
   if (documentElement.hasAttribute("force-theme")) {
     switchTheme(documentElement.getAttribute("force-theme"));
   } else {
     switchTheme(Services.prefs.getCharPref("devtools.theme"));
 
     gDevTools.on("pref-changed", handlePrefChange);
     window.addEventListener("unload", function () {
       gDevTools.off("pref-changed", handlePrefChange);
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/sourceeditor/editor.js
@@ -29,16 +29,17 @@ const MAX_VERTICAL_OFFSET = 3;
 const RE_SCRATCHPAD_ERROR = /(?:@Scratchpad\/\d+:|\()(\d+):?(\d+)?(?:\)|\n)/;
 const RE_JUMP_TO_LINE = /^(\d+):?(\d+)?/;
 
 const Services = require("Services");
 const promise = require("promise");
 const events = require("devtools/shared/event-emitter");
 const { PrefObserver } = require("devtools/client/styleeditor/utils");
 const { getClientCssProperties } = require("devtools/shared/fronts/css-properties");
+const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/locale/sourceeditor.properties");
 
 const { OS } = Services.appinfo;
 
 // CM_STYLES, CM_SCRIPTS and CM_IFRAME represent the HTML,
 // JavaScript and CSS that is injected into an iframe in
@@ -288,33 +289,35 @@ Editor.prototype = {
     env.setAttribute("src", CM_IFRAME);
     el.appendChild(env);
 
     this.once("destroy", () => el.removeChild(env));
     return def.promise;
   },
 
   appendToLocalElement: function (el) {
-    this._setup(el);
+    this._setup(el, true);
   },
 
   /**
    * Do the actual appending and configuring of the CodeMirror instance. This is
    * used by both append functions above, and does all the hard work to
    * configure CodeMirror with all the right options/modes/etc.
    */
-  _setup: function (el) {
+  _setup: function (el, isHtml) {
     let win = el.ownerDocument.defaultView;
 
-    let scriptsToInject = CM_SCRIPTS.concat(this.config.externalScripts);
-    scriptsToInject.forEach(url => {
-      if (url.startsWith("chrome://")) {
-        Services.scriptloader.loadSubScript(url, win, "utf8");
-      }
-    });
+    if (!isHtml) {
+      let scriptsToInject = CM_SCRIPTS.concat(this.config.externalScripts);
+      scriptsToInject.forEach(url => {
+        if (url.startsWith("chrome://")) {
+          Services.scriptloader.loadSubScript(url, win, "utf8");
+        }
+      });
+    }
 
     // Replace the propertyKeywords, colorKeywords and valueKeywords
     // properties of the CSS MIME type with the values provided by the CSS properties
     // database.
     const {
       propertyKeywords,
       colorKeywords,
       valueKeywords
@@ -462,17 +465,21 @@ Editor.prototype = {
 
       this.emit("gutterClick", line, ev.button);
     });
 
     win.CodeMirror.defineExtension("l10n", (name) => {
       return L10N.getStr(name);
     });
 
-    cm.getInputField().controllers.insertControllerAt(0, controller(this));
+    if (!isHtml) {
+      cm.getInputField().controllers.insertControllerAt(0, controller(this));
+    } else {
+      this._initShortcuts(win);
+    }
 
     editors.set(this, cm);
 
     this.reloadPreferences = this.reloadPreferences.bind(this);
     this._prefObserver = new PrefObserver("devtools.editor.");
     this._prefObserver.on(TAB_SIZE, this.reloadPreferences);
     this._prefObserver.on(EXPAND_TAB, this.reloadPreferences);
     this._prefObserver.on(KEYMAP, this.reloadPreferences);
@@ -1253,16 +1260,99 @@ Editor.prototype = {
       if (foldGutterIndex !== -1) {
         let gutters = this.config.gutters.slice();
         gutters.splice(foldGutterIndex, 1);
         this.setOption("gutters", gutters);
       }
 
       this.setOption("foldGutter", false);
     }
+  },
+
+  /**
+   * Register all key shortcuts.
+   */
+  _initShortcuts: function (win) {
+    let shortcuts = new KeyShortcuts({
+      window: win,
+    });
+
+    this._onShortcut = this._onShortcut.bind(this);
+
+    // Process generic keys:
+    ["find.key",
+     "findNext.key",
+     "findPrev.key",
+     "jumpToLine.key",
+     "undo.key",
+     "redo.key",
+     "delete.key",
+     "selectAll.key"].forEach(name => {
+       let key = L10N.getStr(name);
+       shortcuts.on(key, (_, event) => this._onShortcut(name, event));
+     });
+  },
+
+  /**
+   * Key shortcut listener.
+   */
+  _onShortcut: function (name, event) {
+    if (!this._isInputOrTextarea(event.target)) {
+      return;
+    }
+    let cm = editors.get(this);
+    switch (name) {
+      // Localizable keys
+      case "find.key":
+        cm.execCommand("find");
+        break;
+      case "findNext.key":
+        if (cm.state.search != null && cm.state.search.query != null) {
+          cm.execCommand("findNext");
+        }
+        break;
+      case "findPrev.key":
+        cm.execCommand("findPrev");
+        break;
+      case "jumpToLine.key":
+        this.jumpToLine();
+        break;
+      case "undo.key":
+        if (this.canUndo()) {
+          cm.execCommand("undo");
+        }
+        break;
+      case "redo.key":
+        if (this.canRedo()) {
+          cm.execCommand("redo");
+        }
+        break;
+      case "delete.key":
+        if (this.somethingSelected()) {
+          cm.execCommand("delCharAfter");
+        }
+        break;
+      case "selectAll.key":
+        cm.execCommand("selectAll");
+        break;
+      default:
+        console.error("Unexpected editor key shortcut", name);
+        return;
+    }
+    // Prevent default for this action
+    event.stopPropagation();
+    event.preventDefault();
+  },
+
+  /**
+   * Check if a node is an input or textarea
+   */
+  _isInputOrTextarea: function (element) {
+    let name = element.tagName.toLowerCase();
+    return name === "input" || name === "textarea";
   }
 };
 
 // Since Editor is a thin layer over CodeMirror some methods
 // are mapped directly—without any changes.
 
 CM_MAPPING.forEach(name => {
   Editor.prototype[name] = function (...args) {