From 67eca776d57dbbe719330e37ede11394cfec4125 Mon Sep 17 00:00:00 2001 From: Rodrigo Silveira Date: Wed, 6 Mar 2013 15:56:15 -0800 Subject: [PATCH] Bug 844371 - Add Cut to the text field context menu. r=fryn r=mbrubeck --- browser/metro/base/content/ContextCommands.js | 48 ++++++---- .../metro/base/content/bindings/bindings.xml | 1 + browser/metro/base/content/browser.xul | 8 +- .../contenthandlers/ContextMenuHandler.js | 54 ++++++++++- .../content/helperui/SelectionHelperUI.js | 10 -- .../base/tests/browser_context_menu_tests.js | 94 ++++++++++++++++--- .../metro/locales/en-US/chrome/browser.dtd | 1 + .../locales/en-US/chrome/browser.properties | 4 - 8 files changed, 170 insertions(+), 50 deletions(-) diff --git a/browser/metro/base/content/ContextCommands.js b/browser/metro/base/content/ContextCommands.js index e18de84c4c5..405ffee852c 100644 --- a/browser/metro/base/content/ContextCommands.js +++ b/browser/metro/base/content/ContextCommands.js @@ -34,24 +34,46 @@ var ContextCommands = { // Text specific + cut: function cc_cut() { + let target = ContextMenuUI.popupState.target; + + if (!target) + return; + + if (target.localName === "browser") { + // content + if (ContextMenuUI.popupState.string) { + this.sendCommand("cut"); + + SelectionHelperUI.closeEditSessionAndClear(); + } + } else { + // chrome + target.editor.cut(); + } + + target.focus(); + }, + copy: function cc_copy() { let target = ContextMenuUI.popupState.target; + + if (!target) + return; + if (target.localName == "browser") { // content - if (ContextMenuUI.popupState.string != "undefined") { - this.clipboard.copyString(ContextMenuUI.popupState.string, - this.docRef); - this.showToast(Strings.browser.GetStringFromName("selectionHelper.textCopied")); + if (ContextMenuUI.popupState.string) { + this.sendCommand("copy"); + SelectionHelperUI.closeEditSessionAndClear(); } } else { // chrome target.editor.copy(); - this.showToast(Strings.browser.GetStringFromName("selectionHelper.textCopied")); } - if (target) - target.focus(); + target.focus(); }, paste: function cc_paste() { @@ -149,7 +171,6 @@ var ContextCommands = { copyLink: function cc_copyLink() { this.clipboard.copyString(ContextMenuUI.popupState.linkURL, this.docRef); - this.showToast(Strings.browser.GetStringFromName("selectionHelper.linkCopied")); }, bookmarkLink: function cc_bookmarkLink() { @@ -162,8 +183,6 @@ var ContextCommands = { } catch (e) { return; } - - this.showToast(Strings.browser.GetStringFromName("alertLinkBookmarked")); }, // Image specific @@ -180,7 +199,6 @@ var ContextCommands = { copyImageSrc: function cc_copyImageSrc() { this.clipboard.copyString(ContextMenuUI.popupState.mediaURL, this.docRef); - this.showToast(Strings.browser.GetStringFromName("selectionHelper.linkCopied")); }, openImageInNewTab: function cc_openImageInNewTab() { @@ -196,7 +214,6 @@ var ContextCommands = { copyVideoSrc: function cc_copyVideoSrc() { this.clipboard.copyString(ContextMenuUI.popupState.mediaURL, this.docRef); - this.showToast(Strings.browser.GetStringFromName("selectionHelper.linkCopied")); }, openVideoInNewTab: function cc_openVideoInNewTab() { @@ -269,12 +286,7 @@ var ContextCommands = { }); }, - showToast: function showToast(aString) { - let toaster = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService); - toaster.showAlertNotification(null, aString, "", false, "", null); - }, - - sendCommand: function cc_playVideo(aCommand) { + sendCommand: function sendCommand(aCommand) { // Send via message manager over to ContextMenuHandler let browser = ContextMenuUI.popupState.target; browser.messageManager.sendAsyncMessage("Browser:ContextCommand", { command: aCommand }); diff --git a/browser/metro/base/content/bindings/bindings.xml b/browser/metro/base/content/bindings/bindings.xml index a2ba5420243..9651d58fcf1 100644 --- a/browser/metro/base/content/bindings/bindings.xml +++ b/browser/metro/base/content/bindings/bindings.xml @@ -375,6 +375,7 @@ let json = { types: ["input-text"], string: "" }; if (selectionStart != selectionEnd) { + json.types.push("cut"); json.types.push("copy"); json.string = aTextbox.value.slice(selectionStart, selectionEnd); } else if (aTextbox.value) { diff --git a/browser/metro/base/content/browser.xul b/browser/metro/base/content/browser.xul index f0c31f531d3..e6e523e9efc 100644 --- a/browser/metro/base/content/browser.xul +++ b/browser/metro/base/content/browser.xul @@ -546,8 +546,12 @@ - - + + + + + diff --git a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js index 4c8300bb5ea..acd4c736d9e 100644 --- a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js +++ b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js @@ -8,6 +8,7 @@ dump("### ContextMenuHandler.js loaded\n"); var ContextMenuHandler = { _types: [], + _previousState: null, init: function ch_init() { // Events we catch from content during the bubbling phase @@ -56,6 +57,18 @@ var ContextMenuHandler = { let command = aMessage.json.command; switch (command) { + case "cut": + this._onCut(); + break; + + case "copy": + this._onCopy(); + break; + + case "paste": + this._onPaste(); + break; + case "play": case "pause": if (node instanceof Ci.nsIDOMHTMLMediaElement) @@ -75,10 +88,6 @@ var ContextMenuHandler = { this._onSelectAll(); break; - case "paste": - this._onPaste(); - break; - case "copy-image-contents": this._onCopyImage(); break; @@ -88,7 +97,7 @@ var ContextMenuHandler = { /* * Handler for selection overlay context menu events. */ - _onContextAtPoint: function _onContextCommand(aMessage) { + _onContextAtPoint: function _onContextAtPoint(aMessage) { // we need to find popupNode as if the context menu were // invoked on underlying content. let { element, frameX, frameY } = @@ -151,6 +160,35 @@ var ContextMenuHandler = { Util.copyImageToClipboard(this._target); }, + _onCut: function _onCut() { + if (this._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"); + } + } + this.reset(); + }, + + _onCopy: function _onCopy() { + if (this._isTextInput(this._target)) { + let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement); + if (edit) { + edit.editor.copy(); + } else { + Util.dumpLn("error: target element does not support nsIDOMNSEditableElement"); + } + } else { + let selectionText = this._previousState.string; + + Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(Ci.nsIClipboardHelper).copyString(selectionText); + } + this.reset(); + }, + /****************************************************** * Utility routines */ @@ -205,6 +243,9 @@ var ContextMenuHandler = { linkTitle: "", linkProtocol: null, mediaURL: "", + contentType: "", + contentDisposition: "", + string: "", }; // Do checks for nodes that never have children. @@ -266,6 +307,7 @@ var ContextMenuHandler = { // Don't include "copy" for password fields. if (!(elem instanceof Ci.nsIDOMHTMLInputElement) || elem.mozIsTextField(true)) { if (selectionStart != selectionEnd) { + state.types.push("cut"); state.types.push("copy"); state.string = elem.value.slice(selectionStart, selectionEnd); } @@ -334,6 +376,8 @@ var ContextMenuHandler = { if (this._types[i].handler(state, popupNode)) state.types.push(this._types[i].name); + this._previousState = state; + sendAsyncMessage("Content:ContextMenu", state); }, diff --git a/browser/metro/base/content/helperui/SelectionHelperUI.js b/browser/metro/base/content/helperui/SelectionHelperUI.js index a46b4c2bc8a..41ac7fe6a19 100644 --- a/browser/metro/base/content/helperui/SelectionHelperUI.js +++ b/browser/metro/base/content/helperui/SelectionHelperUI.js @@ -502,9 +502,6 @@ var SelectionHelperUI = { }, _onSelectionCopied: function _onSelectionCopied(json) { - if (json.succeeded) { - this.showToast(Strings.browser.GetStringFromName("selectionHelper.textCopied")); - } this.closeEditSessionAndClear(); }, @@ -687,11 +684,4 @@ var SelectionHelperUI = { json.change = aMarker.tag; this._sendAsyncMessage("Browser:SelectionMove", json); }, - - showToast: function showToast(aString) { - let toaster = - Cc["@mozilla.org/toaster-alerts-service;1"] - .getService(Ci.nsIAlertsService); - toaster.showAlertNotification(null, aString, "", false, "", null); - }, }; diff --git a/browser/metro/base/tests/browser_context_menu_tests.js b/browser/metro/base/tests/browser_context_menu_tests.js index dbe5efd68b0..a0f1c6b5572 100644 --- a/browser/metro/base/tests/browser_context_menu_tests.js +++ b/browser/metro/base/tests/browser_context_menu_tests.js @@ -43,6 +43,7 @@ gTests.push({ yield addTab(chromeRoot + "browser_context_menu_tests_02.html"); purgeEventQueue(); + emptyClipboard(); let win = Browser.selectedTab.browser.contentWindow; @@ -68,10 +69,21 @@ gTests.push({ checkContextUIMenuItemVisibility(["context-copy", "context-search"]); + let menuItem = document.getElementById("context-copy"); promise = waitForEvent(document, "popuphidden"); - ContextMenuUI.hide(); + EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win); + yield promise; ok(promise && !(promise instanceof Error), "promise error"); + + // The wait is needed to give time to populate the clipboard. + let string = ""; + yield waitForCondition(function () { + string = SpecialPowers.getClipboardData("text/unicode"); + return string === span.textContent; + }); + ok(string === span.textContent, "copied selected text from span"); + win.getSelection().removeAllRanges(); //////////////////////////////////////////////////////////// @@ -136,21 +148,46 @@ gTests.push({ // should be visible ok(ContextMenuUI._menuPopup._visible, "is visible"); - checkContextUIMenuItemVisibility(["context-copy", + checkContextUIMenuItemVisibility(["context-select", + "context-select-all"]); + + // copy menu item should not exist when no text is selected + let menuItem = document.getElementById("context-copy"); + ok(menuItem && menuItem.hidden, "menu item is not visible"); + + promise = waitForEvent(document, "popuphidden"); + ContextMenuUI.hide(); + yield promise; + ok(promise && !(promise instanceof Error), "promise error"); + + //////////////////////////////////////////////////////////// + // context in input with selection copied to clipboard + + let input = win.document.getElementById("text3-input"); + input.value = "hello, I'm sorry but I must be going."; + input.setSelectionRange(0, 5); + promise = waitForEvent(document, "popupshown"); + sendContextMenuClickToElement(win, input, 20, 10); + yield promise; + ok(promise && !(promise instanceof Error), "promise error"); + + // should be visible + ok(ContextMenuUI._menuPopup._visible, "is visible"); + + checkContextUIMenuItemVisibility(["context-cut", + "context-copy", "context-select", "context-select-all"]); - // copy menu item should copy all text let menuItem = document.getElementById("context-copy"); - ok(menuItem, "menu item exists"); - ok(!menuItem.hidden, "menu item visible"); - let popupPromise = waitForEvent(document, "popuphidden"); EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win); + yield popupPromise; ok(popupPromise && !(popupPromise instanceof Error), "promise error"); + let string = SpecialPowers.getClipboardData("text/unicode"); - ok(string, "hello, I'm sorry but I must be going.", "copy all"); + ok(string === "hello", "copied selected text"); emptyClipboard(); @@ -168,8 +205,8 @@ gTests.push({ ok(ContextMenuUI._menuPopup._visible, "is visible"); // selected text context: - checkContextUIMenuItemVisibility(["context-copy", - "context-search"]); + checkContextUIMenuItemVisibility(["context-cut", + "context-copy"]); promise = waitForEvent(document, "popuphidden"); ContextMenuUI.hide(); @@ -191,8 +228,8 @@ gTests.push({ ok(ContextMenuUI._menuPopup._visible, "is visible"); // selected text context: - checkContextUIMenuItemVisibility(["context-copy", - "context-search", + checkContextUIMenuItemVisibility(["context-cut", + "context-copy", "context-paste"]); promise = waitForEvent(document, "popuphidden"); @@ -200,6 +237,41 @@ gTests.push({ yield promise; ok(promise && !(promise instanceof Error), "promise error"); + //////////////////////////////////////////////////////////// + // context in input with selection cut to clipboard + + emptyClipboard(); + + let input = win.document.getElementById("text3-input"); + input.value = "hello, I'm sorry but I must be going."; + input.setSelectionRange(0, 5); + promise = waitForEvent(document, "popupshown"); + sendContextMenuClickToElement(win, input, 20, 10); + yield promise; + ok(promise && !(promise instanceof Error), "promise error"); + + // should be visible + ok(ContextMenuUI._menuPopup._visible, "is visible"); + + checkContextUIMenuItemVisibility(["context-cut", + "context-copy", + "context-select", + "context-select-all"]); + + let menuItem = document.getElementById("context-cut"); + let popupPromise = waitForEvent(document, "popuphidden"); + EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win); + + yield popupPromise; + ok(popupPromise && !(popupPromise instanceof Error), "promise error"); + + let string = SpecialPowers.getClipboardData("text/unicode"); + let inputValue = input.value; + ok(string === "hello", "cut selected text in clipboard"); + ok(inputValue === ", I'm sorry but I must be going.", "cut selected text from input value"); + + emptyClipboard(); + //////////////////////////////////////////////////////////// // context in empty input, data on clipboard (paste operation) diff --git a/browser/metro/locales/en-US/chrome/browser.dtd b/browser/metro/locales/en-US/chrome/browser.dtd index 18e44462009..1220976a5d2 100644 --- a/browser/metro/locales/en-US/chrome/browser.dtd +++ b/browser/metro/locales/en-US/chrome/browser.dtd @@ -51,6 +51,7 @@ + diff --git a/browser/metro/locales/en-US/chrome/browser.properties b/browser/metro/locales/en-US/chrome/browser.properties index 27d6acdca56..a8232511748 100644 --- a/browser/metro/locales/en-US/chrome/browser.properties +++ b/browser/metro/locales/en-US/chrome/browser.properties @@ -87,7 +87,3 @@ opensearch.search=Search: %S # %S is the name of the app, like "YouTube" or "Picassa" openinapp.specific=Open in %S App openinapp.general=Open in Another App - -# Selection alerts -selectionHelper.textCopied=Text copied to clipboard -selectionHelper.linkCopied=Link copied to clipboard