forked from mirrors/gecko-dev
Bug 1810655 - Make HTMLEditor check readonly flag when its public methods are called r=m_kato
The readonly flag of `HTMLEditor` can be set by chrome script or
`Document.execCommand("contentReadOnly")`. However, the XPCOM APIs should keep
working to update the editable content. E.g., if focused editor is a single
line text field, the app may want to change the value even if it's not editable
by the users.
Note that this patch does not fix all cases of all APIs because this is not
important for Firefox even though the status can be created by web apps (but
Firefox does not use XPCOM APIs basically, instead using XUL commands and it
should not work with readonly state since it may be kicked by user
interactions, e.g., executing a menu item or a shortcut key).
Therefore, it's enough to work it in current Thunderbird for now.
Differential Revision: https://phabricator.services.mozilla.com/D167353
This commit is contained in:
parent
f1dc274377
commit
d0da820599
6 changed files with 337 additions and 166 deletions
|
|
@ -723,14 +723,6 @@ Element* HTMLEditor::FindSelectionRoot(const nsINode& aNode) const {
|
|||
return GetDocument()->GetRootElement();
|
||||
}
|
||||
|
||||
// XXX If we have readonly flag, shouldn't return the element which has
|
||||
// contenteditable="true"? However, such case isn't there without chrome
|
||||
// permission script.
|
||||
if (IsReadonly()) {
|
||||
// We still want to allow selection in a readonly editor.
|
||||
return GetRoot();
|
||||
}
|
||||
|
||||
nsIContent* content = const_cast<nsIContent*>(aNode.AsContent());
|
||||
if (!content->HasFlag(NODE_IS_EDITABLE)) {
|
||||
// If the content is in read-write state but is not editable itself,
|
||||
|
|
@ -1900,6 +1892,10 @@ nsresult HTMLEditor::InsertElementAtSelectionAsAction(
|
|||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
AutoEditActionDataSetter editActionData(
|
||||
*this, HTMLEditUtils::GetEditActionForInsert(*aElement), aPrincipal);
|
||||
nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
|
||||
|
|
@ -1913,11 +1909,6 @@ nsresult HTMLEditor::InsertElementAtSelectionAsAction(
|
|||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||
"EditorBase::CommitComposition() failed, but ignored");
|
||||
|
||||
// XXX Oh, this should be done before dispatching `beforeinput` event.
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
{
|
||||
Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
|
||||
if (MOZ_UNLIKELY(result.isErr())) {
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ nsresult HTMLEditor::InsertDroppedDataTransferAsAction(
|
|||
MOZ_ASSERT(aDroppedAt.IsSet());
|
||||
MOZ_ASSERT(aDataTransfer.MozItemCount() > 0);
|
||||
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
aEditActionData.InitializeDataTransfer(&aDataTransfer);
|
||||
RefPtr<StaticRange> targetRange = StaticRange::Create(
|
||||
aDroppedAt.GetContainer(), aDroppedAt.Offset(), aDroppedAt.GetContainer(),
|
||||
|
|
@ -254,6 +258,12 @@ NS_IMETHODIMP HTMLEditor::InsertHTML(const nsAString& aInString) {
|
|||
|
||||
nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString,
|
||||
nsIPrincipal* aPrincipal) {
|
||||
// FIXME: This should keep handling inserting HTML if the caller is
|
||||
// nsIHTMLEditor::InsertHTML.
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
AutoEditActionDataSetter editActionData(*this, EditAction::eInsertHTML,
|
||||
aPrincipal);
|
||||
nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
|
||||
|
|
@ -525,10 +535,6 @@ nsresult HTMLEditor::InsertHTMLWithContextAsSubAction(
|
|||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
CommitComposition();
|
||||
|
||||
IgnoredErrorResult ignoredError;
|
||||
|
|
@ -2254,6 +2260,10 @@ HTMLEditor::HavePrivateHTMLFlavor HTMLEditor::ClipboardHasPrivateHTMLFlavor(
|
|||
nsresult HTMLEditor::PasteAsAction(int32_t aClipboardType,
|
||||
bool aDispatchPasteEvent,
|
||||
nsIPrincipal* aPrincipal) {
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
|
||||
aPrincipal);
|
||||
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
||||
|
|
@ -2396,6 +2406,12 @@ nsresult HTMLEditor::PasteInternal(int32_t aClipboardType) {
|
|||
|
||||
nsresult HTMLEditor::PasteTransferableAsAction(nsITransferable* aTransferable,
|
||||
nsIPrincipal* aPrincipal) {
|
||||
// FIXME: This may be called as a call of nsIEditor::PasteTransferable.
|
||||
// In this case, we should keep handling the paste even in the readonly mode.
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
|
||||
aPrincipal);
|
||||
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
||||
|
|
@ -2444,6 +2460,10 @@ nsresult HTMLEditor::PasteTransferableAsAction(nsITransferable* aTransferable,
|
|||
|
||||
nsresult HTMLEditor::PasteNoFormattingAsAction(int32_t aSelectionType,
|
||||
nsIPrincipal* aPrincipal) {
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
|
||||
aPrincipal);
|
||||
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
||||
|
|
@ -2814,10 +2834,6 @@ nsresult HTMLEditor::InsertWithQuotationsAsSubAction(
|
|||
const nsAString& aQuotedText) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
{
|
||||
Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
|
||||
if (MOZ_UNLIKELY(result.isErr())) {
|
||||
|
|
@ -3064,10 +3080,6 @@ nsresult HTMLEditor::InsertAsPlaintextQuotation(const nsAString& aQuotedText,
|
|||
*aNodeInserted = nullptr;
|
||||
}
|
||||
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
{
|
||||
Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
|
||||
if (MOZ_UNLIKELY(result.isErr())) {
|
||||
|
|
@ -3345,10 +3357,6 @@ nsresult HTMLEditor::InsertAsCitedQuotationInternal(
|
|||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(!IsInPlaintextMode());
|
||||
|
||||
if (IsReadonly()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
{
|
||||
Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
|
||||
if (MOZ_UNLIKELY(result.isErr())) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@
|
|||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(() => {
|
||||
(function testInputEvents() {
|
||||
function testInputEvents() {
|
||||
const inReadonlyMode = getEditor().flags & SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
const editorDescription = `(readonly=${!!inReadonlyMode})`;
|
||||
|
||||
const editor = document.querySelector("div[contenteditable]");
|
||||
const selection = getSelection();
|
||||
|
||||
|
|
@ -34,6 +37,7 @@ SimpleTest.waitForFocus(() => {
|
|||
editor.addEventListener("beforeinput", onBeforeInput);
|
||||
editor.addEventListener("input", onInput);
|
||||
|
||||
editor.innerHTML = "";
|
||||
editor.focus();
|
||||
selection.collapse(editor, 0);
|
||||
|
||||
|
|
@ -84,22 +88,45 @@ SimpleTest.waitForFocus(() => {
|
|||
inputEvents = [];
|
||||
getEditorMailSupport().insertAsCitedQuotation("this is quoted text\nAnd here is second line.", "this is cited text", false);
|
||||
|
||||
ok(selection.isCollapsed,
|
||||
"Selection should be collapsed after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
|
||||
is(selection.focusNode, editor,
|
||||
"focus node of Selection should be a child of the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
|
||||
is(selection.focusOffset, 1,
|
||||
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
|
||||
is(editor.innerHTML, '<span style="white-space: pre-wrap;">> this is quoted text<br>> And here is second line.<br><br></span>',
|
||||
"The quoted text should be inserted as plaintext into the plaintext editor");
|
||||
is(beforeInputEvents.length, 1,
|
||||
'One "beforeinput" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||
checkInputEvent(beforeInputEvents[0], "insertText", "this is quoted text\nAnd here is second line.",
|
||||
"after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
|
||||
is(inputEvents.length, 1,
|
||||
'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||
checkInputEvent(inputEvents[0], "insertText", "this is quoted text\nAnd here is second line.",
|
||||
"after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
|
||||
ok(
|
||||
selection.isCollapsed,
|
||||
`Selection should be collapsed after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
selection.focusNode,
|
||||
editor,
|
||||
`focus node of Selection should be a child of the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
1,
|
||||
`focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
editor.innerHTML,
|
||||
'<span style="white-space: pre-wrap;">> this is quoted text<br>> And here is second line.<br><br></span>',
|
||||
`The quoted text should be inserted as plaintext into the plaintext editor ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
beforeInputEvents.length,
|
||||
1,
|
||||
`One "beforeinput" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor ${editorDescription}`
|
||||
);
|
||||
checkInputEvent(
|
||||
beforeInputEvents[0],
|
||||
"insertText", "this is quoted text\nAnd here is second line.",
|
||||
`after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
inputEvents.length,
|
||||
1,
|
||||
`One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor ${editorDescription}`
|
||||
);
|
||||
checkInputEvent(
|
||||
inputEvents[0],
|
||||
"insertText", "this is quoted text\nAnd here is second line.",
|
||||
`after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor ${editorDescription}`
|
||||
);
|
||||
|
||||
// Tests when the editor is in HTML editor mode.
|
||||
getEditor().flags &= ~SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
|
|
@ -110,22 +137,47 @@ SimpleTest.waitForFocus(() => {
|
|||
inputEvents = [];
|
||||
getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>", "this is cited text", false);
|
||||
|
||||
ok(selection.isCollapsed,
|
||||
"Selection should be collapsed after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
||||
is(selection.focusNode, editor,
|
||||
"focus node of Selection should be a child of the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
||||
is(selection.focusOffset, 1,
|
||||
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
||||
is(editor.innerHTML,
|
||||
'<blockquote type="cite" cite="this is cited text">this is quoted text<br></blockquote>', "The quoted text should be inserted as plaintext into the HTML editor");
|
||||
is(beforeInputEvents.length, 1,
|
||||
'One "beforeinput" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||
checkInputEvent(beforeInputEvents[0], "", null,
|
||||
"after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
||||
is(inputEvents.length, 1,
|
||||
'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||
checkInputEvent(inputEvents[0], "", null,
|
||||
"after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
||||
ok(
|
||||
selection.isCollapsed,
|
||||
`Selection should be collapsed after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
selection.focusNode,
|
||||
editor,
|
||||
`focus node of Selection should be a child of the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
1,
|
||||
`focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
editor.innerHTML,
|
||||
'<blockquote type="cite" cite="this is cited text">this is quoted text<br></blockquote>',
|
||||
`The quoted text should be inserted as plaintext into the HTML editor ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
beforeInputEvents.length,
|
||||
1,
|
||||
`One "beforeinput" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext) ${editorDescription}`
|
||||
);
|
||||
checkInputEvent(
|
||||
beforeInputEvents[0],
|
||||
"",
|
||||
null,
|
||||
`after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
inputEvents.length,
|
||||
1,
|
||||
`One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext) ${editorDescription}`
|
||||
);
|
||||
checkInputEvent(
|
||||
inputEvents[0],
|
||||
"",
|
||||
null,
|
||||
`after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext) ${editorDescription}`
|
||||
);
|
||||
|
||||
editor.innerHTML = "";
|
||||
|
||||
|
|
@ -133,28 +185,56 @@ SimpleTest.waitForFocus(() => {
|
|||
inputEvents = [];
|
||||
getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>And here is second line.", "this is cited text", true);
|
||||
|
||||
ok(selection.isCollapsed,
|
||||
"Selection should be collapsed after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
||||
is(selection.focusNode, editor,
|
||||
"focus node of Selection should be a child of the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
||||
is(selection.focusOffset, 1,
|
||||
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
||||
is(editor.innerHTML, '<blockquote type="cite" cite="this is cited text">this is quoted text<br>And here is second line.</blockquote>',
|
||||
"The quoted text should be inserted as HTML source into the HTML editor");
|
||||
is(beforeInputEvents.length, 1,
|
||||
'One "beforeinput" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||
checkInputEvent(beforeInputEvents[0], "", null,
|
||||
"after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
||||
is(inputEvents.length, 1,
|
||||
'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||
checkInputEvent(inputEvents[0], "", null,
|
||||
"after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
||||
ok(
|
||||
selection.isCollapsed,
|
||||
`Selection should be collapsed after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
selection.focusNode,
|
||||
editor,
|
||||
`focus node of Selection should be a child of the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
1,
|
||||
`focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
editor.innerHTML,
|
||||
'<blockquote type="cite" cite="this is cited text">this is quoted text<br>And here is second line.</blockquote>',
|
||||
`The quoted text should be inserted as HTML source into the HTML editor ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
beforeInputEvents.length,
|
||||
1,
|
||||
`One "beforeinput" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source) ${editorDescription}`
|
||||
);
|
||||
checkInputEvent(
|
||||
beforeInputEvents[0],
|
||||
"",
|
||||
null,
|
||||
`after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source) ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
inputEvents.length,
|
||||
1,
|
||||
`One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source) ${editorDescription}`
|
||||
);
|
||||
checkInputEvent(
|
||||
inputEvents[0],
|
||||
"",
|
||||
null,
|
||||
`after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source) ${editorDescription}`
|
||||
);
|
||||
|
||||
editor.removeEventListener("beforeinput", onBeforeInput);
|
||||
editor.removeEventListener("input", onInput);
|
||||
})();
|
||||
}
|
||||
|
||||
function testStyleOfPlaintextMode() {
|
||||
const inReadonlyMode = getEditor().flags & SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
const editorDescription = `(readonly=${!!inReadonlyMode})`;
|
||||
|
||||
(function testStyleOfPlaintextMode() {
|
||||
getEditor().flags |= SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
|
||||
(function testInDiv() {
|
||||
|
|
@ -169,18 +249,18 @@ SimpleTest.waitForFocus(() => {
|
|||
is(
|
||||
editor.firstChild.tagName,
|
||||
"SPAN",
|
||||
"testStyleOfPlaintextMode: testInDiv: insertAsCitedQuotation should insert a `<span>` element"
|
||||
`testStyleOfPlaintextMode: testInDiv: insertAsCitedQuotation should insert a <span> element ${editorDescription}`
|
||||
);
|
||||
const computedSpanStyle = getComputedStyle(editor.firstChild);
|
||||
is(
|
||||
computedSpanStyle.display,
|
||||
"inline",
|
||||
"testStyleOfPlaintextMode: testInDiv: The inserted <span> element should be \"display: inline;\""
|
||||
`testStyleOfPlaintextMode: testInDiv: The inserted <span> element should be "display: inline;" ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
computedSpanStyle.whiteSpace,
|
||||
"pre-wrap",
|
||||
"testStyleOfPlaintextMode: testInDiv: The inserted <span> element should be \"white-space: pre-wrap;\""
|
||||
`testStyleOfPlaintextMode: testInDiv: The inserted <span> element should be "white-space: pre-wrap;" ${editorDescription}`
|
||||
);
|
||||
})();
|
||||
|
||||
|
|
@ -196,25 +276,33 @@ SimpleTest.waitForFocus(() => {
|
|||
is(
|
||||
document.body.firstChild.tagName,
|
||||
"SPAN",
|
||||
"testStyleOfPlaintextMode: testInBody: insertAsCitedQuotation should insert a `<span>` element in plaintext mode and in a <body> element"
|
||||
`testStyleOfPlaintextMode: testInBody: insertAsCitedQuotation should insert a <span> element in plaintext mode and in a <body> element ${editorDescription}`
|
||||
);
|
||||
const computedSpanStyle = getComputedStyle(document.body.firstChild);
|
||||
is(
|
||||
computedSpanStyle.display,
|
||||
"block",
|
||||
"testStyleOfPlaintextMode: testInBody: The inserted <span> element should be \"display: block;\" in plaintext mode and in a <body> element"
|
||||
`testStyleOfPlaintextMode: testInBody: The inserted <span> element should be "display: block;" in plaintext mode and in a <body> element ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
computedSpanStyle.whiteSpace,
|
||||
"pre-wrap",
|
||||
"testStyleOfPlaintextMode: testInBody: The inserted <span> element should be \"white-space: pre-wrap;\" in plaintext mode and in a <body> element"
|
||||
`testStyleOfPlaintextMode: testInBody: The inserted <span> element should be "white-space: pre-wrap;" in plaintext mode and in a <body> element ${editorDescription}`
|
||||
);
|
||||
document.body.firstChild.remove();
|
||||
})();
|
||||
} finally {
|
||||
document.body.contentEditable = false;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
testInputEvents();
|
||||
testStyleOfPlaintextMode();
|
||||
|
||||
// Even if the HTMLEditor is readonly, XPCOM API should keep working.
|
||||
getEditor().flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
testInputEvents();
|
||||
testStyleOfPlaintextMode();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,81 +7,89 @@
|
|||
</head>
|
||||
<body>
|
||||
<div contenteditable></div>
|
||||
<iframe srcdoc="<body contenteditable></body>"></iframe>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(() => {
|
||||
(function testStyleOfPlaintextMode() {
|
||||
getEditor().flags |= SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
const iframe = document.querySelector("iframe");
|
||||
|
||||
(function testInDiv() {
|
||||
const editor = document.querySelector("div[contenteditable]");
|
||||
editor.innerHTML = "";
|
||||
editor.focus();
|
||||
getEditorMailSupport().insertTextWithQuotations(
|
||||
"This is Text\n\n> This is a quote."
|
||||
);
|
||||
verify(editor, "TestInDiv");
|
||||
})();
|
||||
function testInDiv() {
|
||||
const inPlaintextMode = getEditor(window).flags & SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
const inReadonlyMode = getEditor(window).flags & SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
const editorDescription = `(readonly=${!!inReadonlyMode}, plaintext=${!!inPlaintextMode})`;
|
||||
const editor = document.querySelector("div[contenteditable]");
|
||||
editor.innerHTML = "";
|
||||
editor.focus();
|
||||
getEditorMailSupport(window).insertTextWithQuotations(
|
||||
"This is Text\n\n> This is a quote."
|
||||
);
|
||||
is(
|
||||
editor.innerHTML,
|
||||
'This is Text<br><br><span style="white-space: pre-wrap;">> This is a quote.</span><br>',
|
||||
`The <div contenteditable> should have the expected innerHTML ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
editor.querySelector("span")?.getAttribute("_moz_quote"),
|
||||
"true",
|
||||
`The <span> element in the <div contenteditable> should have _moz_quote="true" ${editorDescription}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
document.body.contentEditable = true;
|
||||
(function testInBody() {
|
||||
getSelection().collapse(document.body, 0);
|
||||
getEditorMailSupport().insertTextWithQuotations(
|
||||
"This is Text\n\n> This is a quote."
|
||||
);
|
||||
verify(document.body, "TestInBody");
|
||||
})();
|
||||
} finally {
|
||||
document.body.contentEditable = false;
|
||||
function testInBody() {
|
||||
const inPlaintextMode = getEditor(iframe.contentWindow).flags & SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
const inReadonlyMode = getEditor(iframe.contentWindow).flags & SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
const editorDescription = `(readonly=${!!inReadonlyMode}, plaintext=${!!inPlaintextMode})`;
|
||||
const editor = iframe.contentDocument.body;
|
||||
editor.innerHTML = "";
|
||||
iframe.contentWindow.getSelection().collapse(document.body, 0);
|
||||
getEditorMailSupport(iframe.contentWindow).insertTextWithQuotations(
|
||||
"This is Text\n\n> This is a quote."
|
||||
);
|
||||
is(
|
||||
editor.innerHTML,
|
||||
'This is Text<br><br><span style="white-space: pre-wrap; display: block; width: 98vw;">> This is a quote.</span><br>',
|
||||
`The <body> should have the expected innerHTML ${editorDescription}`
|
||||
);
|
||||
is(
|
||||
editor.querySelector("span")?.getAttribute("_moz_quote"),
|
||||
"true",
|
||||
`The <span> element in the <body> should have _moz_quote="true" ${editorDescription}`
|
||||
);
|
||||
}
|
||||
|
||||
for (const testReadOnly of [false, true]) {
|
||||
// Even if the HTMLEditor is readonly, XPCOM API should keep working.
|
||||
if (testReadOnly) {
|
||||
getEditor(window).flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
getEditor(iframe.contentWindow).flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
} else {
|
||||
getEditor(window).flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
getEditor(iframe.contentWindow).flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
}
|
||||
})();
|
||||
|
||||
getEditor(window).flags &= ~SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
getEditor(iframe.contentWindow).flags &= ~SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
testInDiv();
|
||||
testInBody();
|
||||
|
||||
getEditor(window).flags |= SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
getEditor(iframe.contentWindow).flags |= SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
|
||||
testInDiv();
|
||||
testInBody();
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
function getEditor() {
|
||||
var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
|
||||
return editingSession.getEditorForWindow(window);
|
||||
function getEditor(aWindow) {
|
||||
const editingSession = SpecialPowers.wrap(aWindow).docShell.editingSession;
|
||||
return editingSession.getEditorForWindow(aWindow);
|
||||
}
|
||||
|
||||
function getEditorMailSupport() {
|
||||
return getEditor().QueryInterface(SpecialPowers.Ci.nsIEditorMailSupport);
|
||||
}
|
||||
|
||||
function verify(editor, logHint) {
|
||||
is(
|
||||
editor.childNodes[0].textContent,
|
||||
"This is Text",
|
||||
`${logHint}: insertTextWithQuotations should insert the text directly`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[1].tagName,
|
||||
"BR",
|
||||
`${logHint}: insertTextWithQuotations should insert a BR tag`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[2].tagName,
|
||||
"BR",
|
||||
`${logHint}: insertTextWithQuotations should insert a second BR tag`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[3].tagName,
|
||||
"SPAN",
|
||||
`${logHint}: insertTextWithQuotations should insert a SPAN tag`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[3].getAttribute("_moz_quote"),
|
||||
"true",
|
||||
`${logHint}: insertTextWithQuotations should insert SPAN tag with _moz_quote = true`
|
||||
)
|
||||
is(
|
||||
editor.childNodes[3].textContent,
|
||||
"> This is a quote.",
|
||||
`${logHint}: insertTextWithQuotations should insert SPAN tag with the quoted text`
|
||||
)
|
||||
function getEditorMailSupport(aWindow) {
|
||||
return getEditor(aWindow).QueryInterface(SpecialPowers.Ci.nsIEditorMailSupport);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -61,22 +61,88 @@ const isHeadless = await SpecialPowers.spawnChrome([], () => {
|
|||
|
||||
const selection = window.getSelection();
|
||||
|
||||
// Paste into editable area
|
||||
let editable = document.getElementById("editable");
|
||||
selection.selectAllChildren(editable);
|
||||
selection.collapseToStart();
|
||||
doKey(editable, false);
|
||||
is(editable.innerHTML,
|
||||
isHeadless ? expectedText : expectedHTML, "paste into contenteditable");
|
||||
const editable = document.getElementById("editable");
|
||||
const innerHTMLBeforeTest = editable.innerHTML;
|
||||
|
||||
// Unformatted paste into editable area
|
||||
selection.selectAllChildren(editable);
|
||||
selection.collapseToEnd();
|
||||
doKey(editable, true);
|
||||
is(editable.innerHTML,
|
||||
isHeadless ? expectedText + expectedText :
|
||||
expectedHTML + expectedText,
|
||||
"paste unformatted into contenteditable");
|
||||
(function test_pasteWithFormatIntoEditableArea() {
|
||||
selection.selectAllChildren(editable);
|
||||
selection.collapseToStart();
|
||||
doKey(editable, false);
|
||||
is(
|
||||
editable.innerHTML,
|
||||
isHeadless ? expectedText : expectedHTML,
|
||||
"test_pasteWithFormatIntoEditableArea: Pasting with format should work as expected"
|
||||
);
|
||||
editable.innerHTML = innerHTMLBeforeTest;
|
||||
}());
|
||||
|
||||
(function test_pasteWithoutFormatIntoEditableArea() {
|
||||
selection.selectAllChildren(editable);
|
||||
selection.collapseToEnd();
|
||||
doKey(editable, true);
|
||||
is(
|
||||
editable.innerHTML,
|
||||
expectedText,
|
||||
"test_pasteWithoutFormatIntoEditableArea: Pasting without format should work as expected",
|
||||
);
|
||||
editable.innerHTML = innerHTMLBeforeTest;
|
||||
})();
|
||||
|
||||
(function test_pasteWithFormatIntoEditableAreaWhenHTMLEditorIsInReadonlyMode() {
|
||||
getEditor().flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
selection.selectAllChildren(editable);
|
||||
selection.collapseToStart();
|
||||
let beforeInputEvents = [];
|
||||
function onBeforeInput(aEvent) {
|
||||
beforeInputEvents.push(aEvent);
|
||||
}
|
||||
editable.addEventListener("beforeinput", onBeforeInput);
|
||||
doKey(editable, false);
|
||||
const description = "test_pasteWithFormatIntoEditableAreaWhenHTMLEditorIsInReadonlyMode";
|
||||
is(
|
||||
editable.innerHTML,
|
||||
innerHTMLBeforeTest,
|
||||
`${description}: Pasting with format should not work`
|
||||
);
|
||||
is(
|
||||
beforeInputEvents.length,
|
||||
0,
|
||||
`${description}: Pasting with format should not cause "beforeinput", but fired "${
|
||||
beforeInputEvents[0]?.inputType
|
||||
}"`
|
||||
);
|
||||
editable.removeEventListener("beforeinput", onBeforeInput);
|
||||
editable.innerHTML = innerHTMLBeforeTest;
|
||||
getEditor().flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
})();
|
||||
|
||||
(function test_pasteWithoutFormatIntoEditableAreaWhenHTMLEditorIsInReadonlyMode() {
|
||||
getEditor().flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
selection.selectAllChildren(editable);
|
||||
selection.collapseToStart();
|
||||
let beforeInputEvents = [];
|
||||
function onBeforeInput(aEvent) {
|
||||
beforeInputEvents.push(aEvent);
|
||||
}
|
||||
editable.addEventListener("beforeinput", onBeforeInput);
|
||||
doKey(editable, false);
|
||||
const description = "test_pasteWithoutFormatIntoEditableAreaWhenHTMLEditorIsInReadonlyMode";
|
||||
is(
|
||||
editable.innerHTML,
|
||||
innerHTMLBeforeTest,
|
||||
`${description}: Pasting with format should not work`
|
||||
);
|
||||
is(
|
||||
beforeInputEvents.length,
|
||||
0,
|
||||
`${description}: Pasting with format should not cause "beforeinput", but fired "${
|
||||
beforeInputEvents[0]?.inputType
|
||||
}"`
|
||||
);
|
||||
editable.removeEventListener("beforeinput", onBeforeInput);
|
||||
editable.innerHTML = innerHTMLBeforeTest;
|
||||
getEditor().flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
})();
|
||||
|
||||
let noneditable = document.getElementById("noneditable");
|
||||
selection.selectAllChildren(noneditable);
|
||||
|
|
@ -114,5 +180,10 @@ const isHeadless = await SpecialPowers.spawnChrome([], () => {
|
|||
// Formatted HTML text should not exist when pasting unformatted.
|
||||
is(result.html, "", "paste unformatted html into non-editable");
|
||||
});
|
||||
|
||||
function getEditor() {
|
||||
const editingSession = SpecialPowers.wrap(window).docShell.editingSession;
|
||||
return editingSession.getEditorForWindow(window);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -54,9 +54,14 @@ interface nsIEditor : nsISupports
|
|||
// don't specify this for HTML editor.
|
||||
const long eEditorPasswordMask = 0x0004;
|
||||
// When the editor should be in readonly mode (currently, same as "disabled"),
|
||||
// you can specify this flag with any editor instances. Note that setting
|
||||
// this flag does not change the style of editor. This just changes the
|
||||
// internal editor's readonly state.
|
||||
// you can specify this flag with any editor instances.
|
||||
// NOTE: Setting this flag does not change the style of editor. This just
|
||||
// changes the internal editor's readonly state.
|
||||
// NOTE: The readonly mode does NOT block XPCOM APIs which modify the editor
|
||||
// content. This just blocks edit operations from user input and editing
|
||||
// commands (both HTML Document.execCommand and the XUL commands).
|
||||
// FIXME: XPCOM methods of TextEditor may be blocked by this flag. If you
|
||||
// find it, file a bug.
|
||||
const long eEditorReadonlyMask = 0x0008;
|
||||
// If you want an HTML editor to work as an email composer, specify this flag.
|
||||
// And you can specify this to text editor too for making spellchecker for
|
||||
|
|
|
|||
Loading…
Reference in a new issue