Bug 844371 - Add Cut to the text field context menu. r=fryn r=mbrubeck

This commit is contained in:
Rodrigo Silveira 2013-03-06 15:56:15 -08:00
parent 0c3b8fa997
commit 5bb09db223
8 changed files with 170 additions and 50 deletions

View File

@ -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 });

View File

@ -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) {

View File

@ -546,8 +546,12 @@
<!-- ux spec: https://bug782810.bugzilla.mozilla.org/attachment.cgi?id=714804 -->
<!-- Text related -->
<!-- for text inputs, this will copy selected text, or if no text is selected, copy all -->
<richlistitem id="context-copy" type="copy,selectable" onclick="ContextCommands.copy();">
<!-- for text inputs, this will cut selected text -->
<richlistitem id="context-cut" type="cut" onclick="ContextCommands.cut();">
<label value="&contextTextCut.label;"/>
</richlistitem>
<!-- for text inputs, this will copy selected text -->
<richlistitem id="context-copy" type="copy" onclick="ContextCommands.copy();">
<label value="&contextTextCopy.label;"/>
</richlistitem>
<!-- only displayed if there is text on the clipboard -->

View File

@ -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);
},

View File

@ -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);
},
};

View File

@ -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)

View File

@ -51,6 +51,7 @@
<!ENTITY consoleErrColumn.label "Column:">
<!-- TEXT CONTEXT MENU -->
<!ENTITY contextTextCut.label "Cut">
<!ENTITY contextTextCopy.label "Copy">
<!ENTITY contextTextPaste.label "Paste">
<!-- unique item that is only added to the url bar context menu -->

View File

@ -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