Bug 947505 - Right-click in a designMode document should display a context menu. r=mbrubeck, r=sfoster
authorAleh Zasypkin <aleh.zasypkin@gmail.com>
Fri, 17 Jan 2014 10:47:02 +0100
changeset 164531 d8ab8158574d50d702aa7711c00fb03f8aa5fcf3
parent 164530 31dca1ad8cfb22f3e15365c4dedb7399f459f7c4
child 164532 de1374bf0c6edbc1e182078d15d2f99603f39dd6
push id26050
push userkwierso@gmail.com
push dateWed, 22 Jan 2014 01:28:12 +0000
treeherdermozilla-central@8f4ecbf938cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck, sfoster
bugs947505
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 947505 - Right-click in a designMode document should display a context menu. r=mbrubeck, r=sfoster
browser/metro/base/content/Util.js
browser/metro/base/content/contenthandlers/ContextMenuHandler.js
browser/metro/base/tests/mochitest/browser_context_menu_tests.js
--- a/browser/metro/base/content/Util.js
+++ b/browser/metro/base/content/Util.js
@@ -78,40 +78,54 @@ let Util = {
   },
 
   isTextInput: function isTextInput(aElement) {
     return ((aElement instanceof Ci.nsIDOMHTMLInputElement &&
              aElement.mozIsTextField(false)) ||
             aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
   },
 
+  /**
+   * Checks whether aElement's content can be edited either if it(or any of its
+   * parents) has "contenteditable" attribute set to "true" or aElement's
+   * ownerDocument is in design mode.
+   */
   isEditableContent: function isEditableContent(aElement) {
-    if (!aElement)
-      return false;
-    if (aElement.isContentEditable || aElement.designMode == "on")
-      return true;
-    return false;
+    return !!aElement && (aElement.isContentEditable ||
+                          this.isOwnerDocumentInDesignMode(aElement));
+
   },
 
   isEditable: function isEditable(aElement) {
-    if (!aElement)
+    if (!aElement) {
       return false;
-    if (this.isTextInput(aElement) || this.isEditableContent(aElement))
+    }
+
+    if (this.isTextInput(aElement) || this.isEditableContent(aElement)) {
       return true;
+    }
 
     // If a body element is editable and the body is the child of an
     // iframe or div we can assume this is an advanced HTML editor
     if ((aElement instanceof Ci.nsIDOMHTMLIFrameElement ||
          aElement instanceof Ci.nsIDOMHTMLDivElement) &&
         aElement.contentDocument &&
         this.isEditableContent(aElement.contentDocument.body)) {
       return true;
     }
 
-    return aElement.ownerDocument && aElement.ownerDocument.designMode == "on";
+    return false;
+  },
+
+  /**
+   * Checks whether aElement's owner document has design mode turned on.
+   */
+  isOwnerDocumentInDesignMode: function(aElement) {
+    return !!aElement && !!aElement.ownerDocument &&
+           aElement.ownerDocument.designMode == "on";
   },
 
   isMultilineInput: function isMultilineInput(aElement) {
     return (aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
   },
 
   isLink: function isLink(aElement) {
     return ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
--- a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
+++ b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
@@ -100,33 +100,35 @@ var ContextMenuHandler = {
   /******************************************************
    * ContextCommand handlers
    */
 
   _onSelectAll: function _onSelectAll() {
     if (Util.isTextInput(this._target)) {
       // select all text in the input control
       this._target.select();
+    } else if (Util.isEditableContent(this._target)) {
+      this._target.ownerDocument.execCommand("selectAll", false);
     } else {
       // select the entire document
       content.getSelection().selectAllChildren(content.document);
     }
     this.reset();
   },
 
   _onPaste: function _onPaste() {
     // paste text if this is an input control
     if (Util.isTextInput(this._target)) {
       let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
       if (edit) {
         edit.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
       } else {
         Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
       }
-    } else if (this._target.isContentEditable) {
+    } else if (Util.isEditableContent(this._target)) {
       try {
         this._target.ownerDocument.execCommand("paste",
                                                false,
                                                Ci.nsIClipboard.kGlobalClipboard);
       } catch (ex) {
         dump("ContextMenuHandler: exception pasting into contentEditable: " + ex.message + "\n");
       }
     }
@@ -140,17 +142,17 @@ var ContextMenuHandler = {
   _onCut: function _onCut() {
     if (Util.isTextInput(this._target)) {
       let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
       if (edit) {
         edit.editor.cut();
       } else {
         Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
       }
-    } else if (this._target.isContentEditable) {
+    } else if (Util.isEditableContent(this._target)) {
       try {
         this._target.ownerDocument.execCommand("cut", false);
       } catch (ex) {
         dump("ContextMenuHandler: exception cutting from contentEditable: " + ex.message + "\n");
       }
     }
     this.reset();
   },
@@ -254,17 +256,19 @@ var ContextMenuHandler = {
           linkUrl = state.linkURL;
           state.linkTitle = popupNode.textContent || popupNode.title;
           state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
           // mark as text so we can pickup on selection below
           isText = true;
           break;
         }
         // is the target contentEditable (not just inheriting contentEditable)
-        else if (elem.contentEditable == "true") {
+        // or the entire document in designer mode.
+        else if (elem.contentEditable == "true" ||
+                 Util.isOwnerDocumentInDesignMode(elem)) {
           this._target = elem;
           isEditableText = true;
           isText = true;
           uniqueStateTypes.add("input-text");
 
           if (elem.textContent.length) {
             uniqueStateTypes.add("selectable");
           } else {
--- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
+++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
@@ -704,12 +704,90 @@ gTests.push({
 
 gTests.push({
   desc: "bug 856264 - touch - context menu should reopen on other links",
   setUp: reopenSetUp,
   tearDown: reopenTearDown,
   run: getReopenTest(sendContextMenuClickToElement, sendTap)
 });
 
+gTests.push({
+  desc: "Bug 947505 - Right-click in a designMode document should display a " +
+        "context menu",
+  run: function test() {
+    info(chromeRoot + "browser_context_menu_tests_02.html");
+    yield addTab(chromeRoot + "browser_context_menu_tests_02.html");
+
+    purgeEventQueue();
+    emptyClipboard();
+    ContextUI.dismiss();
+
+    yield waitForCondition(() => !ContextUI.navbarVisible);
+
+    let tabWindow = Browser.selectedTab.browser.contentWindow;
+    let testSpan = tabWindow.document.getElementById("text1");
+
+    // Case #1: Document isn't in design mode and nothing is selected.
+    tabWindow.document.designMode = "off";
+
+    // Simulate right mouse click to reproduce the same step as noted in the
+    // appropriate bug. It's valid for non-touch case only.
+    synthesizeNativeMouseRDown(Browser.selectedTab.browser, 10, 10);
+    synthesizeNativeMouseRUp(Browser.selectedTab.browser, 10, 10);
+
+    yield waitForCondition(() => ContextUI.navbarVisible);
+
+    ok(ContextUI.navbarVisible, "Navbar is visible on context menu action.");
+    ok(ContextUI.tabbarVisible, "Tabbar is visible on context menu action.");
+
+    ContextUI.dismiss();
+    yield waitForCondition(() => !ContextUI.navbarVisible);
+
+    // Case #2: Document isn't in design mode and text is selected.
+    tabWindow.getSelection().selectAllChildren(testSpan);
+
+    let promise = waitForEvent(tabWindow.document, "popupshown");
+    sendContextMenuClickToSelection(tabWindow);
+    yield promise;
+
+    checkContextUIMenuItemVisibility(["context-copy", "context-search"]);
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    // Case #3: Document is in design mode and nothing is selected.
+    tabWindow.document.designMode = "on";
+    tabWindow.getSelection().removeAllRanges();
+
+    promise = waitForEvent(tabWindow.document, "popupshown");
+    sendContextMenuClickToElement(tabWindow, testSpan);
+    yield promise;
+
+    checkContextUIMenuItemVisibility(["context-select-all", "context-select"]);
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    // Case #4: Document is in design mode and text is selected.
+    tabWindow.getSelection().selectAllChildren(testSpan);
+
+    promise = waitForEvent(tabWindow.document, "popupshown");
+    sendContextMenuClickToSelection(tabWindow);
+    yield promise;
+
+    checkContextUIMenuItemVisibility(["context-cut", "context-copy",
+                                      "context-select-all", "context-select",
+                                      "context-search"]);
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    Browser.closeTab(Browser.selectedTab, { forceClose: true });
+  }
+});
+
 function test() {
   setDevPixelEqualToPx();
   runTests();
 }