import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
86
editor/AsyncSpellCheckTestHelper.jsm
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/* 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/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"onSpellCheck",
|
||||
];
|
||||
|
||||
const SPELL_CHECK_ENDED_TOPIC = "inlineSpellChecker-spellCheck-ended";
|
||||
const SPELL_CHECK_STARTED_TOPIC = "inlineSpellChecker-spellCheck-started";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
/**
|
||||
* Waits until spell checking has stopped on the given element.
|
||||
*
|
||||
* When a spell check is pending, this waits indefinitely until the spell check
|
||||
* ends. When a spell check is not pending, it waits a small number of turns of
|
||||
* the event loop: if a spell check begins, it resumes waiting indefinitely for
|
||||
* the end, and otherwise it stops waiting and calls the callback.
|
||||
*
|
||||
* This this can therefore trap spell checks that have not started at the time
|
||||
* of calling, spell checks that have already started, multiple consecutive
|
||||
* spell checks, and the absence of spell checks altogether.
|
||||
*
|
||||
* @param editableElement The element being spell checked.
|
||||
* @param callback Called when spell check has completed or enough turns
|
||||
* of the event loop have passed to determine it has not
|
||||
* started.
|
||||
*/
|
||||
function onSpellCheck(editableElement, callback) {
|
||||
let editor = editableElement.editor;
|
||||
if (!editor) {
|
||||
let win = editableElement.ownerDocument.defaultView;
|
||||
editor = win.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIWebNavigation).
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIEditingSession).
|
||||
getEditorForWindow(win);
|
||||
}
|
||||
if (!editor)
|
||||
throw new Error("Unable to find editor for element " + editableElement);
|
||||
|
||||
try {
|
||||
// False is important here. Pass false so that the inline spell checker
|
||||
// isn't created if it doesn't already exist.
|
||||
var isc = editor.getInlineSpellChecker(false);
|
||||
}
|
||||
catch (err) {
|
||||
// getInlineSpellChecker throws if spell checking is not enabled instead of
|
||||
// just returning null, which seems kind of lame. (Spell checking is not
|
||||
// enabled on Android.) The point here is only to determine whether spell
|
||||
// check is pending, and if getInlineSpellChecker throws, then it's not
|
||||
// pending.
|
||||
}
|
||||
let waitingForEnded = isc && isc.spellCheckPending;
|
||||
let count = 0;
|
||||
|
||||
function observe(subj, topic, data) {
|
||||
if (subj != editor)
|
||||
return;
|
||||
count = 0;
|
||||
let expectedTopic = waitingForEnded ? SPELL_CHECK_ENDED_TOPIC :
|
||||
SPELL_CHECK_STARTED_TOPIC;
|
||||
if (topic != expectedTopic)
|
||||
Cu.reportError("Expected " + expectedTopic + " but got " + topic + "!");
|
||||
waitingForEnded = !waitingForEnded;
|
||||
}
|
||||
|
||||
let os = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
os.addObserver(observe, SPELL_CHECK_STARTED_TOPIC, false);
|
||||
os.addObserver(observe, SPELL_CHECK_ENDED_TOPIC, false);
|
||||
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.init(function tick() {
|
||||
// Wait an arbitrarily large number -- 50 -- turns of the event loop before
|
||||
// declaring that no spell checks will start.
|
||||
if (waitingForEnded || ++count < 50)
|
||||
return;
|
||||
timer.cancel();
|
||||
os.removeObserver(observe, SPELL_CHECK_STARTED_TOPIC);
|
||||
os.removeObserver(observe, SPELL_CHECK_ENDED_TOPIC);
|
||||
callback();
|
||||
}, 0, Ci.nsITimer.TYPE_REPEATING_SLACK);
|
||||
};
|
||||
37
editor/composer/crashtests/351236-1.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<html><head>
|
||||
<title>Testcase bug 351236 - Crash [@ nsGetInterface::operator()] with designMode iframes, removing styles, removing iframes, reloading, etc</title>
|
||||
<script>
|
||||
function designmodes(i){
|
||||
try {
|
||||
window.frames[0].document.designMode='on';
|
||||
window.frames[0].focus();
|
||||
window.frames[0].getSelection().collapse(window.frames[0].document.body.childNodes[0],window.frames[0].document.body.childNodes[0].length-2)
|
||||
window.frames[0].document.execCommand('inserthtml', false, 'tesxt ');
|
||||
} catch(e) {}
|
||||
|
||||
setTimeout(designmodes,50);
|
||||
}
|
||||
|
||||
function removestyles(){
|
||||
document.getElementsByTagName('iframe')[0].removeAttribute('style');
|
||||
document.getElementsByTagName('q')[0].removeAttribute('style');
|
||||
}
|
||||
|
||||
function doe() {
|
||||
setTimeout(designmodes,200);
|
||||
setTimeout(removestyles,500);
|
||||
setTimeout(function() {document.removeChild(document.documentElement);}, 1000);
|
||||
setTimeout(function() {window.location.reload();}, 1500);
|
||||
}
|
||||
window.onload=doe;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
This page should not crash Mozilla within 2 seconds<br>
|
||||
<q style="display: table-row;">
|
||||
<iframe style="display: table-row;"></iframe>
|
||||
<iframe></iframe>
|
||||
</q>
|
||||
</body>
|
||||
</html>
|
||||
20
editor/composer/crashtests/407062-1.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<html contentEditable="true">
|
||||
<head>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function boom()
|
||||
{
|
||||
var r = document.documentElement;
|
||||
while(r.firstChild)
|
||||
r.removeChild(r.firstChild);
|
||||
|
||||
document.execCommand("contentReadOnly", false, "");
|
||||
document.documentElement.focus();
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="boom();"></body>
|
||||
</html>
|
||||
20
editor/composer/crashtests/419563-1.xhtml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
|
||||
function boom()
|
||||
{
|
||||
document.documentElement.appendChild(document.body);
|
||||
document.getElementById("s").contentEditable = "true";
|
||||
document.getElementById("v").focus();
|
||||
document.body.focus();
|
||||
document.execCommand("delete", false, null);
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<span id="s">thesewords arenot realwords</span><body contenteditable="true" onload="setTimeout(boom, 0);"><span contenteditable="false"><div id="v" contenteditable="true"></div>Five</span></body>
|
||||
|
||||
</html>
|
||||
4
editor/composer/crashtests/428844-1-inner.xhtml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml-stylesheet type="text/xsl" href="#a"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" onload="dump('Inner onload\n'); window.location.reload()" contenteditable="true">
|
||||
<xsl:stylesheet version="1.0" id="a" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
|
||||
</html>
|
||||
17
editor/composer/crashtests/428844-1.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<html class="reftest-wait">
|
||||
<head>
|
||||
<script>
|
||||
function boom() {
|
||||
var iframe = document.getElementById('inner');
|
||||
iframe.addEventListener("load", function() {
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, false);
|
||||
iframe.src = "data:text/html,";
|
||||
dump("Outer onload\n");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="boom()">
|
||||
<iframe src="428844-1-inner.xhtml" id="inner"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
25
editor/composer/crashtests/461049-1.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
|
||||
function uu()
|
||||
{
|
||||
document.removeEventListener("DOMSubtreeModified", uu, false);
|
||||
document.execCommand("undo", false, null);
|
||||
}
|
||||
|
||||
function boom()
|
||||
{
|
||||
document.execCommand("selectAll", false, null);
|
||||
document.execCommand("strikethrough", false, null);
|
||||
document.addEventListener("DOMSubtreeModified", uu, false);
|
||||
document.execCommand("undo", false, null);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body contenteditable="true" onload="boom();"><div></div></body>
|
||||
|
||||
</html>
|
||||
6
editor/composer/crashtests/crashtests.list
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
load 351236-1.html
|
||||
load 407062-1.html
|
||||
load 419563-1.xhtml
|
||||
load 428844-1.html
|
||||
load 461049-1.html
|
||||
load removing-editable-xslt.html
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml-stylesheet type="text/xsl" href="#a"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" contenteditable="true">
|
||||
<xsl:stylesheet version="1.0" id="a" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
|
||||
</html>
|
||||
17
editor/composer/crashtests/removing-editable-xslt.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<html class="reftest-wait">
|
||||
<head>
|
||||
<script>
|
||||
function boom()
|
||||
{
|
||||
document.getElementById("i").src = "removing-editable-xslt-inner.xhtml";
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(document.getElementById("i"));
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, 0);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="boom();">
|
||||
<iframe id="i"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
53
editor/composer/moz.build
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
MOCHITEST_MANIFESTS += ['test/mochitest.ini']
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIEditingSession.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'composer'
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'nsComposerCommands.cpp',
|
||||
'nsComposerCommandsUpdater.cpp',
|
||||
'nsComposerController.cpp',
|
||||
'nsComposerDocumentCommands.cpp',
|
||||
'nsComposerRegistration.cpp',
|
||||
'nsComposeTxtSrvFilter.cpp',
|
||||
'nsEditingSession.cpp',
|
||||
'nsEditorSpellCheck.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
RESOURCE_FILES += [
|
||||
'res/EditorOverride.css',
|
||||
'res/grabber.gif',
|
||||
'res/table-add-column-after-active.gif',
|
||||
'res/table-add-column-after-hover.gif',
|
||||
'res/table-add-column-after.gif',
|
||||
'res/table-add-column-before-active.gif',
|
||||
'res/table-add-column-before-hover.gif',
|
||||
'res/table-add-column-before.gif',
|
||||
'res/table-add-row-after-active.gif',
|
||||
'res/table-add-row-after-hover.gif',
|
||||
'res/table-add-row-after.gif',
|
||||
'res/table-add-row-before-active.gif',
|
||||
'res/table-add-row-before-hover.gif',
|
||||
'res/table-add-row-before.gif',
|
||||
'res/table-remove-column-active.gif',
|
||||
'res/table-remove-column-hover.gif',
|
||||
'res/table-remove-column.gif',
|
||||
'res/table-remove-row-active.gif',
|
||||
'res/table-remove-row-hover.gif',
|
||||
'res/table-remove-row.gif',
|
||||
]
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
||||
62
editor/composer/nsComposeTxtSrvFilter.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsComposeTxtSrvFilter.h"
|
||||
#include "nsError.h" // for NS_OK
|
||||
#include "nsIContent.h" // for nsIContent
|
||||
#include "nsIDOMNode.h" // for nsIDOMNode
|
||||
#include "nsNameSpaceManager.h" // for kNameSpaceID_None
|
||||
#include "nsLiteralString.h" // for NS_LITERAL_STRING
|
||||
#include "nscore.h" // for NS_IMETHODIMP
|
||||
|
||||
nsComposeTxtSrvFilter::nsComposeTxtSrvFilter() :
|
||||
mIsForMail(false)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsComposeTxtSrvFilter, nsITextServicesFilter)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposeTxtSrvFilter::Skip(nsIDOMNode* aNode, bool *_retval)
|
||||
{
|
||||
*_retval = false;
|
||||
|
||||
// Check to see if we can skip this node
|
||||
// For nodes that are blockquotes, we must make sure
|
||||
// their type is "cite"
|
||||
nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
|
||||
if (content) {
|
||||
if (content->IsHTMLElement(nsGkAtoms::blockquote)) {
|
||||
if (mIsForMail) {
|
||||
*_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
|
||||
nsGkAtoms::cite, eIgnoreCase);
|
||||
}
|
||||
} else if (content->IsHTMLElement(nsGkAtoms::span)) {
|
||||
if (mIsForMail) {
|
||||
*_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozquote,
|
||||
nsGkAtoms::_true, eIgnoreCase);
|
||||
if (!*_retval) {
|
||||
*_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_class,
|
||||
nsGkAtoms::mozsignature, eCaseMatters);
|
||||
}
|
||||
}
|
||||
} else if (content->IsAnyOfHTMLElements(nsGkAtoms::script,
|
||||
nsGkAtoms::textarea,
|
||||
nsGkAtoms::select,
|
||||
nsGkAtoms::style,
|
||||
nsGkAtoms::map)) {
|
||||
*_retval = true;
|
||||
} else if (content->IsHTMLElement(nsGkAtoms::table)) {
|
||||
if (mIsForMail) {
|
||||
*_retval =
|
||||
content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_class,
|
||||
NS_LITERAL_STRING("moz-email-headers-table"),
|
||||
eCaseMatters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
55
editor/composer/nsComposeTxtSrvFilter.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsComposeTxtSrvFilter_h__
|
||||
#define nsComposeTxtSrvFilter_h__
|
||||
|
||||
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS
|
||||
#include "nsITextServicesFilter.h"
|
||||
|
||||
/**
|
||||
* This class implements a filter interface, that enables
|
||||
* those using it to skip over certain nodes when traversing content
|
||||
*
|
||||
* This filter is used to skip over various form control nodes and
|
||||
* mail's cite nodes
|
||||
*/
|
||||
class nsComposeTxtSrvFilter final : public nsITextServicesFilter
|
||||
{
|
||||
public:
|
||||
nsComposeTxtSrvFilter();
|
||||
|
||||
// nsISupports interface...
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
// nsITextServicesFilter
|
||||
NS_DECL_NSITEXTSERVICESFILTER
|
||||
|
||||
// Helper - Intializer
|
||||
void Init(bool aIsForMail) { mIsForMail = aIsForMail; }
|
||||
|
||||
private:
|
||||
~nsComposeTxtSrvFilter() {}
|
||||
|
||||
bool mIsForMail;
|
||||
};
|
||||
|
||||
#define NS_COMPOSERTXTSRVFILTER_CID \
|
||||
{/* {171E72DB-0F8A-412a-8461-E4C927A3A2AC}*/ \
|
||||
0x171e72db, 0xf8a, 0x412a, \
|
||||
{ 0x84, 0x61, 0xe4, 0xc9, 0x27, 0xa3, 0xa2, 0xac} }
|
||||
|
||||
#define NS_COMPOSERTXTSRVFILTERMAIL_CID \
|
||||
{/* {7FBD2146-5FF4-4674-B069-A7BBCE66E773}*/ \
|
||||
0x7fbd2146, 0x5ff4, 0x4674, \
|
||||
{ 0xb0, 0x69, 0xa7, 0xbb, 0xce, 0x66, 0xe7, 0x73} }
|
||||
|
||||
// Generic for the editor
|
||||
#define COMPOSER_TXTSRVFILTER_CONTRACTID "@mozilla.org/editor/txtsrvfilter;1"
|
||||
|
||||
// This is the same but includes "cite" typed blocked quotes
|
||||
#define COMPOSER_TXTSRVFILTERMAIL_CONTRACTID "@mozilla.org/editor/txtsrvfiltermail;1"
|
||||
|
||||
#endif
|
||||
1525
editor/composer/nsComposerCommands.cpp
Normal file
281
editor/composer/nsComposerCommands.h
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsComposerCommands_h_
|
||||
#define nsComposerCommands_h_
|
||||
|
||||
#include "nsIControllerCommand.h"
|
||||
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS_INHERITED, etc
|
||||
#include "nscore.h" // for nsresult, NS_IMETHOD
|
||||
|
||||
class nsIAtom;
|
||||
class nsICommandParams;
|
||||
class nsIEditor;
|
||||
class nsISupports;
|
||||
class nsString;
|
||||
|
||||
// This is a virtual base class for commands registered with the composer controller.
|
||||
// Note that such commands are instantiated once per composer, so can store state.
|
||||
// Also note that IsCommandEnabled can be called with an editor that may not
|
||||
// have an editor yet (because the document is loading). Most commands will want
|
||||
// to return false in this case.
|
||||
// Don't hold on to any references to the editor or document from
|
||||
// your command. This will cause leaks. Also, be aware that the document the
|
||||
// editor is editing can change under you (if the user Reverts the file, for
|
||||
// instance).
|
||||
class nsBaseComposerCommand : public nsIControllerCommand
|
||||
{
|
||||
protected:
|
||||
virtual ~nsBaseComposerCommand() {}
|
||||
|
||||
public:
|
||||
|
||||
nsBaseComposerCommand();
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
// nsIControllerCommand. Declared longhand so we can make them pure virtual
|
||||
NS_IMETHOD IsCommandEnabled(const char * aCommandName, nsISupports *aCommandRefCon, bool *_retval) override = 0;
|
||||
NS_IMETHOD DoCommand(const char * aCommandName, nsISupports *aCommandRefCon) override = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#define NS_DECL_COMPOSER_COMMAND(_cmd) \
|
||||
class _cmd : public nsBaseComposerCommand \
|
||||
{ \
|
||||
public: \
|
||||
NS_DECL_NSICONTROLLERCOMMAND \
|
||||
};
|
||||
|
||||
// virtual base class for commands that need to save and update Boolean state (like styles etc)
|
||||
class nsBaseStateUpdatingCommand : public nsBaseComposerCommand
|
||||
{
|
||||
public:
|
||||
explicit nsBaseStateUpdatingCommand(nsIAtom* aTagName);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_DECL_NSICONTROLLERCOMMAND
|
||||
|
||||
protected:
|
||||
virtual ~nsBaseStateUpdatingCommand();
|
||||
|
||||
// get the current state (on or off) for this style or block format
|
||||
virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams) = 0;
|
||||
|
||||
// add/remove the style
|
||||
virtual nsresult ToggleState(nsIEditor* aEditor) = 0;
|
||||
|
||||
protected:
|
||||
nsIAtom* mTagName;
|
||||
};
|
||||
|
||||
|
||||
// Shared class for the various style updating commands like bold, italics etc.
|
||||
// Suitable for commands whose state is either 'on' or 'off'.
|
||||
class nsStyleUpdatingCommand : public nsBaseStateUpdatingCommand
|
||||
{
|
||||
public:
|
||||
explicit nsStyleUpdatingCommand(nsIAtom* aTagName);
|
||||
|
||||
protected:
|
||||
|
||||
// get the current state (on or off) for this style or block format
|
||||
virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams);
|
||||
|
||||
// add/remove the style
|
||||
virtual nsresult ToggleState(nsIEditor* aEditor);
|
||||
};
|
||||
|
||||
|
||||
class nsInsertTagCommand : public nsBaseComposerCommand
|
||||
{
|
||||
public:
|
||||
explicit nsInsertTagCommand(nsIAtom* aTagName);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_DECL_NSICONTROLLERCOMMAND
|
||||
|
||||
protected:
|
||||
virtual ~nsInsertTagCommand();
|
||||
|
||||
nsIAtom* mTagName;
|
||||
};
|
||||
|
||||
|
||||
class nsListCommand : public nsBaseStateUpdatingCommand
|
||||
{
|
||||
public:
|
||||
explicit nsListCommand(nsIAtom* aTagName);
|
||||
|
||||
protected:
|
||||
|
||||
// get the current state (on or off) for this style or block format
|
||||
virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams);
|
||||
|
||||
// add/remove the style
|
||||
virtual nsresult ToggleState(nsIEditor* aEditor);
|
||||
};
|
||||
|
||||
class nsListItemCommand : public nsBaseStateUpdatingCommand
|
||||
{
|
||||
public:
|
||||
explicit nsListItemCommand(nsIAtom* aTagName);
|
||||
|
||||
protected:
|
||||
|
||||
// get the current state (on or off) for this style or block format
|
||||
virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams);
|
||||
|
||||
// add/remove the style
|
||||
virtual nsresult ToggleState(nsIEditor* aEditor);
|
||||
};
|
||||
|
||||
// Base class for commands whose state consists of a string (e.g. para format)
|
||||
class nsMultiStateCommand : public nsBaseComposerCommand
|
||||
{
|
||||
public:
|
||||
|
||||
nsMultiStateCommand();
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSICONTROLLERCOMMAND
|
||||
|
||||
protected:
|
||||
virtual ~nsMultiStateCommand();
|
||||
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams) =0;
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState) = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class nsParagraphStateCommand : public nsMultiStateCommand
|
||||
{
|
||||
public:
|
||||
nsParagraphStateCommand();
|
||||
|
||||
protected:
|
||||
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams);
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState);
|
||||
};
|
||||
|
||||
class nsFontFaceStateCommand : public nsMultiStateCommand
|
||||
{
|
||||
public:
|
||||
nsFontFaceStateCommand();
|
||||
|
||||
protected:
|
||||
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams);
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState);
|
||||
};
|
||||
|
||||
class nsFontSizeStateCommand : public nsMultiStateCommand
|
||||
{
|
||||
public:
|
||||
nsFontSizeStateCommand();
|
||||
|
||||
protected:
|
||||
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor,
|
||||
nsICommandParams* aParams);
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState);
|
||||
};
|
||||
|
||||
class nsHighlightColorStateCommand : public nsMultiStateCommand
|
||||
{
|
||||
public:
|
||||
nsHighlightColorStateCommand();
|
||||
|
||||
protected:
|
||||
|
||||
NS_IMETHOD IsCommandEnabled(const char *aCommandName, nsISupports *aCommandRefCon, bool *_retval);
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams);
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState);
|
||||
|
||||
};
|
||||
|
||||
class nsFontColorStateCommand : public nsMultiStateCommand
|
||||
{
|
||||
public:
|
||||
nsFontColorStateCommand();
|
||||
|
||||
protected:
|
||||
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams);
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState);
|
||||
};
|
||||
|
||||
class nsAlignCommand : public nsMultiStateCommand
|
||||
{
|
||||
public:
|
||||
nsAlignCommand();
|
||||
|
||||
protected:
|
||||
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams);
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState);
|
||||
};
|
||||
|
||||
class nsBackgroundColorStateCommand : public nsMultiStateCommand
|
||||
{
|
||||
public:
|
||||
nsBackgroundColorStateCommand();
|
||||
|
||||
protected:
|
||||
|
||||
virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams);
|
||||
virtual nsresult SetState(nsIEditor *aEditor, nsString& newState);
|
||||
};
|
||||
|
||||
class nsAbsolutePositioningCommand : public nsBaseStateUpdatingCommand
|
||||
{
|
||||
public:
|
||||
nsAbsolutePositioningCommand();
|
||||
|
||||
protected:
|
||||
|
||||
NS_IMETHOD IsCommandEnabled(const char *aCommandName, nsISupports *aCommandRefCon, bool *_retval);
|
||||
virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams);
|
||||
virtual nsresult ToggleState(nsIEditor* aEditor);
|
||||
};
|
||||
|
||||
// composer commands
|
||||
|
||||
NS_DECL_COMPOSER_COMMAND(nsCloseCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsDocumentStateCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsSetDocumentStateCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsSetDocumentOptionsCommand)
|
||||
//NS_DECL_COMPOSER_COMMAND(nsPrintingCommands)
|
||||
|
||||
NS_DECL_COMPOSER_COMMAND(nsDecreaseZIndexCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsIncreaseZIndexCommand)
|
||||
|
||||
// Generic commands
|
||||
|
||||
// File menu
|
||||
NS_DECL_COMPOSER_COMMAND(nsNewCommands) // handles 'new' anything
|
||||
|
||||
// Edit menu
|
||||
NS_DECL_COMPOSER_COMMAND(nsPasteNoFormattingCommand)
|
||||
|
||||
// Block transformations
|
||||
NS_DECL_COMPOSER_COMMAND(nsIndentCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsOutdentCommand)
|
||||
|
||||
NS_DECL_COMPOSER_COMMAND(nsRemoveListCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsRemoveStylesCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsIncreaseFontSizeCommand)
|
||||
NS_DECL_COMPOSER_COMMAND(nsDecreaseFontSizeCommand)
|
||||
|
||||
// Insert content commands
|
||||
NS_DECL_COMPOSER_COMMAND(nsInsertHTMLCommand)
|
||||
|
||||
#endif // nsComposerCommands_h_
|
||||
381
editor/composer/nsComposerCommandsUpdater.cpp
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* 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/. */
|
||||
|
||||
#include "mozilla/mozalloc.h" // for operator new
|
||||
#include "nsAString.h"
|
||||
#include "nsComponentManagerUtils.h" // for do_CreateInstance
|
||||
#include "nsComposerCommandsUpdater.h"
|
||||
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
|
||||
#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc
|
||||
#include "nsICommandManager.h" // for nsICommandManager
|
||||
#include "nsID.h" // for NS_GET_IID, etc
|
||||
#include "nsIDOMWindow.h" // for nsIDOMWindow
|
||||
#include "nsIDocShell.h" // for nsIDocShell
|
||||
#include "nsIInterfaceRequestorUtils.h" // for do_GetInterface
|
||||
#include "nsISelection.h" // for nsISelection
|
||||
#include "nsITransactionManager.h" // for nsITransactionManager
|
||||
#include "nsLiteralString.h" // for NS_LITERAL_STRING
|
||||
#include "nsPICommandUpdater.h" // for nsPICommandUpdater
|
||||
#include "nsPIDOMWindow.h" // for nsPIDOMWindow
|
||||
|
||||
class nsIDOMDocument;
|
||||
class nsITransaction;
|
||||
|
||||
nsComposerCommandsUpdater::nsComposerCommandsUpdater()
|
||||
: mDirtyState(eStateUninitialized)
|
||||
, mSelectionCollapsed(eStateUninitialized)
|
||||
, mFirstDoOfFirstUndo(true)
|
||||
{
|
||||
}
|
||||
|
||||
nsComposerCommandsUpdater::~nsComposerCommandsUpdater()
|
||||
{
|
||||
// cancel any outstanding update timer
|
||||
if (mUpdateTimer) {
|
||||
mUpdateTimer->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsComposerCommandsUpdater, nsISelectionListener,
|
||||
nsIDocumentStateListener, nsITransactionListener, nsITimerCallback)
|
||||
|
||||
#if 0
|
||||
#pragma mark -
|
||||
#endif
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::NotifyDocumentCreated()
|
||||
{
|
||||
// Trigger an nsIObserve notification that the document has been created
|
||||
UpdateOneCommand("obs_documentCreated");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::NotifyDocumentWillBeDestroyed()
|
||||
{
|
||||
// cancel any outstanding update timer
|
||||
if (mUpdateTimer) {
|
||||
mUpdateTimer->Cancel();
|
||||
mUpdateTimer = nullptr;
|
||||
}
|
||||
|
||||
// We can't call this right now; it is too late in some cases and the window
|
||||
// is already partially destructed (e.g. JS objects may be gone).
|
||||
#if 0
|
||||
// Trigger an nsIObserve notification that the document will be destroyed
|
||||
UpdateOneCommand("obs_documentWillBeDestroyed");
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::NotifyDocumentStateChanged(bool aNowDirty)
|
||||
{
|
||||
// update document modified. We should have some other notifications for this too.
|
||||
return UpdateDirtyState(aNowDirty);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::NotifySelectionChanged(nsIDOMDocument *,
|
||||
nsISelection *, int16_t)
|
||||
{
|
||||
return PrimeUpdateTimer();
|
||||
}
|
||||
|
||||
#if 0
|
||||
#pragma mark -
|
||||
#endif
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::WillDo(nsITransactionManager *aManager,
|
||||
nsITransaction *aTransaction, bool *aInterrupt)
|
||||
{
|
||||
*aInterrupt = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::DidDo(nsITransactionManager *aManager,
|
||||
nsITransaction *aTransaction, nsresult aDoResult)
|
||||
{
|
||||
// only need to update if the status of the Undo menu item changes.
|
||||
int32_t undoCount;
|
||||
aManager->GetNumberOfUndoItems(&undoCount);
|
||||
if (undoCount == 1) {
|
||||
if (mFirstDoOfFirstUndo) {
|
||||
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
|
||||
}
|
||||
mFirstDoOfFirstUndo = false;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::WillUndo(nsITransactionManager *aManager,
|
||||
nsITransaction *aTransaction,
|
||||
bool *aInterrupt)
|
||||
{
|
||||
*aInterrupt = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::DidUndo(nsITransactionManager *aManager,
|
||||
nsITransaction *aTransaction,
|
||||
nsresult aUndoResult)
|
||||
{
|
||||
int32_t undoCount;
|
||||
aManager->GetNumberOfUndoItems(&undoCount);
|
||||
if (undoCount == 0)
|
||||
mFirstDoOfFirstUndo = true; // reset the state for the next do
|
||||
|
||||
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::WillRedo(nsITransactionManager *aManager,
|
||||
nsITransaction *aTransaction,
|
||||
bool *aInterrupt)
|
||||
{
|
||||
*aInterrupt = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::DidRedo(nsITransactionManager *aManager,
|
||||
nsITransaction *aTransaction,
|
||||
nsresult aRedoResult)
|
||||
{
|
||||
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::WillBeginBatch(nsITransactionManager *aManager,
|
||||
bool *aInterrupt)
|
||||
{
|
||||
*aInterrupt = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::DidBeginBatch(nsITransactionManager *aManager,
|
||||
nsresult aResult)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::WillEndBatch(nsITransactionManager *aManager,
|
||||
bool *aInterrupt)
|
||||
{
|
||||
*aInterrupt = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::DidEndBatch(nsITransactionManager *aManager,
|
||||
nsresult aResult)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::WillMerge(nsITransactionManager *aManager,
|
||||
nsITransaction *aTopTransaction,
|
||||
nsITransaction *aTransactionToMerge,
|
||||
bool *aInterrupt)
|
||||
{
|
||||
*aInterrupt = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsComposerCommandsUpdater::DidMerge(nsITransactionManager *aManager,
|
||||
nsITransaction *aTopTransaction,
|
||||
nsITransaction *aTransactionToMerge,
|
||||
bool aDidMerge, nsresult aMergeResult)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#pragma mark -
|
||||
#endif
|
||||
|
||||
nsresult
|
||||
nsComposerCommandsUpdater::Init(nsPIDOMWindowOuter* aDOMWindow)
|
||||
{
|
||||
NS_ENSURE_ARG(aDOMWindow);
|
||||
mDOMWindow = do_GetWeakReference(aDOMWindow);
|
||||
mDocShell = do_GetWeakReference(aDOMWindow->GetDocShell());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsComposerCommandsUpdater::PrimeUpdateTimer()
|
||||
{
|
||||
if (!mUpdateTimer) {
|
||||
nsresult rv = NS_OK;
|
||||
mUpdateTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
const uint32_t kUpdateTimerDelay = 150;
|
||||
return mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this),
|
||||
kUpdateTimerDelay,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
|
||||
void nsComposerCommandsUpdater::TimerCallback()
|
||||
{
|
||||
// if the selection state has changed, update stuff
|
||||
bool isCollapsed = SelectionIsCollapsed();
|
||||
if (static_cast<int8_t>(isCollapsed) != mSelectionCollapsed) {
|
||||
UpdateCommandGroup(NS_LITERAL_STRING("select"));
|
||||
mSelectionCollapsed = isCollapsed;
|
||||
}
|
||||
|
||||
// isn't this redundant with the UpdateCommandGroup above?
|
||||
// can we just nuke the above call? or create a meta command group?
|
||||
UpdateCommandGroup(NS_LITERAL_STRING("style"));
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsComposerCommandsUpdater::UpdateDirtyState(bool aNowDirty)
|
||||
{
|
||||
if (mDirtyState != static_cast<int8_t>(aNowDirty)) {
|
||||
UpdateCommandGroup(NS_LITERAL_STRING("save"));
|
||||
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
|
||||
mDirtyState = aNowDirty;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsComposerCommandsUpdater::UpdateCommandGroup(const nsAString& aCommandGroup)
|
||||
{
|
||||
nsCOMPtr<nsPICommandUpdater> commandUpdater = GetCommandUpdater();
|
||||
NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE);
|
||||
|
||||
|
||||
// This hardcoded list of commands is temporary.
|
||||
// This code should use nsIControllerCommandGroup.
|
||||
if (aCommandGroup.EqualsLiteral("undo")) {
|
||||
commandUpdater->CommandStatusChanged("cmd_undo");
|
||||
commandUpdater->CommandStatusChanged("cmd_redo");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aCommandGroup.EqualsLiteral("select") ||
|
||||
aCommandGroup.EqualsLiteral("style")) {
|
||||
commandUpdater->CommandStatusChanged("cmd_bold");
|
||||
commandUpdater->CommandStatusChanged("cmd_italic");
|
||||
commandUpdater->CommandStatusChanged("cmd_underline");
|
||||
commandUpdater->CommandStatusChanged("cmd_tt");
|
||||
|
||||
commandUpdater->CommandStatusChanged("cmd_strikethrough");
|
||||
commandUpdater->CommandStatusChanged("cmd_superscript");
|
||||
commandUpdater->CommandStatusChanged("cmd_subscript");
|
||||
commandUpdater->CommandStatusChanged("cmd_nobreak");
|
||||
|
||||
commandUpdater->CommandStatusChanged("cmd_em");
|
||||
commandUpdater->CommandStatusChanged("cmd_strong");
|
||||
commandUpdater->CommandStatusChanged("cmd_cite");
|
||||
commandUpdater->CommandStatusChanged("cmd_abbr");
|
||||
commandUpdater->CommandStatusChanged("cmd_acronym");
|
||||
commandUpdater->CommandStatusChanged("cmd_code");
|
||||
commandUpdater->CommandStatusChanged("cmd_samp");
|
||||
commandUpdater->CommandStatusChanged("cmd_var");
|
||||
|
||||
commandUpdater->CommandStatusChanged("cmd_increaseFont");
|
||||
commandUpdater->CommandStatusChanged("cmd_decreaseFont");
|
||||
|
||||
commandUpdater->CommandStatusChanged("cmd_paragraphState");
|
||||
commandUpdater->CommandStatusChanged("cmd_fontFace");
|
||||
commandUpdater->CommandStatusChanged("cmd_fontColor");
|
||||
commandUpdater->CommandStatusChanged("cmd_backgroundColor");
|
||||
commandUpdater->CommandStatusChanged("cmd_highlight");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aCommandGroup.EqualsLiteral("save")) {
|
||||
// save commands (most are not in C++)
|
||||
commandUpdater->CommandStatusChanged("cmd_setDocumentModified");
|
||||
commandUpdater->CommandStatusChanged("cmd_save");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsComposerCommandsUpdater::UpdateOneCommand(const char *aCommand)
|
||||
{
|
||||
nsCOMPtr<nsPICommandUpdater> commandUpdater = GetCommandUpdater();
|
||||
NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE);
|
||||
|
||||
commandUpdater->CommandStatusChanged(aCommand);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
nsComposerCommandsUpdater::SelectionIsCollapsed()
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindowOuter> domWindow = do_QueryReferent(mDOMWindow);
|
||||
NS_ENSURE_TRUE(domWindow, true);
|
||||
|
||||
nsCOMPtr<nsISelection> domSelection = domWindow->GetSelection();
|
||||
if (NS_WARN_IF(!domSelection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool selectionCollapsed = false;
|
||||
domSelection->GetIsCollapsed(&selectionCollapsed);
|
||||
return selectionCollapsed;
|
||||
}
|
||||
|
||||
already_AddRefed<nsPICommandUpdater>
|
||||
nsComposerCommandsUpdater::GetCommandUpdater()
|
||||
{
|
||||
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
|
||||
NS_ENSURE_TRUE(docShell, nullptr);
|
||||
nsCOMPtr<nsICommandManager> manager = docShell->GetCommandManager();
|
||||
nsCOMPtr<nsPICommandUpdater> updater = do_QueryInterface(manager);
|
||||
return updater.forget();
|
||||
}
|
||||
|
||||
#if 0
|
||||
#pragma mark -
|
||||
#endif
|
||||
|
||||
nsresult
|
||||
nsComposerCommandsUpdater::Notify(nsITimer *timer)
|
||||
{
|
||||
NS_ASSERTION(timer == mUpdateTimer.get(), "Hey, this ain't my timer!");
|
||||
TimerCallback();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#pragma mark -
|
||||
#endif
|
||||
|
||||
|
||||
nsresult
|
||||
NS_NewComposerCommandsUpdater(nsISelectionListener** aInstancePtrResult)
|
||||
{
|
||||
RefPtr<nsComposerCommandsUpdater> newThang = new nsComposerCommandsUpdater;
|
||||
newThang.forget(aInstancePtrResult);
|
||||
return NS_OK;
|
||||
}
|
||||
102
editor/composer/nsComposerCommandsUpdater.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* 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/. */
|
||||
|
||||
|
||||
|
||||
|
||||
#ifndef nsComposerCommandsUpdater_h__
|
||||
#define nsComposerCommandsUpdater_h__
|
||||
|
||||
#include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr
|
||||
#include "nsIDocumentStateListener.h"
|
||||
#include "nsISelectionListener.h"
|
||||
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS
|
||||
#include "nsITimer.h" // for NS_DECL_NSITIMERCALLBACK, etc
|
||||
#include "nsITransactionListener.h" // for nsITransactionListener
|
||||
#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
|
||||
#include "nscore.h" // for NS_IMETHOD, nsresult, etc
|
||||
|
||||
class nsPIDOMWindowOuter;
|
||||
class nsITransaction;
|
||||
class nsITransactionManager;
|
||||
class nsPICommandUpdater;
|
||||
|
||||
class nsComposerCommandsUpdater : public nsISelectionListener,
|
||||
public nsIDocumentStateListener,
|
||||
public nsITransactionListener,
|
||||
public nsITimerCallback
|
||||
{
|
||||
public:
|
||||
|
||||
nsComposerCommandsUpdater();
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
// nsISelectionListener
|
||||
NS_DECL_NSISELECTIONLISTENER
|
||||
|
||||
// nsIDocumentStateListener
|
||||
NS_DECL_NSIDOCUMENTSTATELISTENER
|
||||
|
||||
// nsITimerCallback interfaces
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
/** nsITransactionListener interfaces
|
||||
*/
|
||||
NS_IMETHOD WillDo(nsITransactionManager *aManager, nsITransaction *aTransaction, bool *aInterrupt) override;
|
||||
NS_IMETHOD DidDo(nsITransactionManager *aManager, nsITransaction *aTransaction, nsresult aDoResult) override;
|
||||
NS_IMETHOD WillUndo(nsITransactionManager *aManager, nsITransaction *aTransaction, bool *aInterrupt) override;
|
||||
NS_IMETHOD DidUndo(nsITransactionManager *aManager, nsITransaction *aTransaction, nsresult aUndoResult) override;
|
||||
NS_IMETHOD WillRedo(nsITransactionManager *aManager, nsITransaction *aTransaction, bool *aInterrupt) override;
|
||||
NS_IMETHOD DidRedo(nsITransactionManager *aManager, nsITransaction *aTransaction, nsresult aRedoResult) override;
|
||||
NS_IMETHOD WillBeginBatch(nsITransactionManager *aManager, bool *aInterrupt) override;
|
||||
NS_IMETHOD DidBeginBatch(nsITransactionManager *aManager, nsresult aResult) override;
|
||||
NS_IMETHOD WillEndBatch(nsITransactionManager *aManager, bool *aInterrupt) override;
|
||||
NS_IMETHOD DidEndBatch(nsITransactionManager *aManager, nsresult aResult) override;
|
||||
NS_IMETHOD WillMerge(nsITransactionManager *aManager, nsITransaction *aTopTransaction,
|
||||
nsITransaction *aTransactionToMerge, bool *aInterrupt) override;
|
||||
NS_IMETHOD DidMerge(nsITransactionManager *aManager, nsITransaction *aTopTransaction,
|
||||
nsITransaction *aTransactionToMerge,
|
||||
bool aDidMerge, nsresult aMergeResult) override;
|
||||
|
||||
|
||||
nsresult Init(nsPIDOMWindowOuter* aDOMWindow);
|
||||
|
||||
protected:
|
||||
|
||||
virtual ~nsComposerCommandsUpdater();
|
||||
|
||||
enum {
|
||||
eStateUninitialized = -1,
|
||||
eStateOff = false,
|
||||
eStateOn = true
|
||||
};
|
||||
|
||||
bool SelectionIsCollapsed();
|
||||
nsresult UpdateDirtyState(bool aNowDirty);
|
||||
nsresult UpdateOneCommand(const char* aCommand);
|
||||
nsresult UpdateCommandGroup(const nsAString& aCommandGroup);
|
||||
|
||||
already_AddRefed<nsPICommandUpdater> GetCommandUpdater();
|
||||
|
||||
nsresult PrimeUpdateTimer();
|
||||
void TimerCallback();
|
||||
nsCOMPtr<nsITimer> mUpdateTimer;
|
||||
|
||||
nsWeakPtr mDOMWindow;
|
||||
nsWeakPtr mDocShell;
|
||||
int8_t mDirtyState;
|
||||
int8_t mSelectionCollapsed;
|
||||
bool mFirstDoOfFirstUndo;
|
||||
|
||||
|
||||
};
|
||||
|
||||
extern "C" nsresult NS_NewComposerCommandsUpdater(nsISelectionListener** aInstancePtrResult);
|
||||
|
||||
|
||||
#endif // nsComposerCommandsUpdater_h__
|
||||
140
editor/composer/nsComposerController.cpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "mozilla/mozalloc.h" // for operator new
|
||||
#include "nsComposerCommands.h" // for nsStyleUpdatingCommand, etc
|
||||
#include "nsComposerController.h"
|
||||
#include "nsError.h" // for NS_OK
|
||||
#include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::a, etc
|
||||
#include "nsIControllerCommandTable.h" // for nsIControllerCommandTable
|
||||
|
||||
class nsIControllerCommand;
|
||||
|
||||
#define NS_REGISTER_ONE_COMMAND(_cmdClass, _cmdName) \
|
||||
{ \
|
||||
_cmdClass* theCmd = new _cmdClass(); \
|
||||
inCommandTable->RegisterCommand(_cmdName, \
|
||||
static_cast<nsIControllerCommand *>(theCmd)); \
|
||||
}
|
||||
|
||||
#define NS_REGISTER_FIRST_COMMAND(_cmdClass, _cmdName) \
|
||||
{ \
|
||||
_cmdClass* theCmd = new _cmdClass(); \
|
||||
inCommandTable->RegisterCommand(_cmdName, \
|
||||
static_cast<nsIControllerCommand *>(theCmd));
|
||||
|
||||
#define NS_REGISTER_NEXT_COMMAND(_cmdClass, _cmdName) \
|
||||
inCommandTable->RegisterCommand(_cmdName, \
|
||||
static_cast<nsIControllerCommand *>(theCmd));
|
||||
|
||||
#define NS_REGISTER_LAST_COMMAND(_cmdClass, _cmdName) \
|
||||
inCommandTable->RegisterCommand(_cmdName, \
|
||||
static_cast<nsIControllerCommand *>(theCmd)); \
|
||||
}
|
||||
|
||||
#define NS_REGISTER_STYLE_COMMAND(_cmdClass, _cmdName, _styleTag) \
|
||||
{ \
|
||||
_cmdClass* theCmd = new _cmdClass(_styleTag); \
|
||||
inCommandTable->RegisterCommand(_cmdName, \
|
||||
static_cast<nsIControllerCommand *>(theCmd)); \
|
||||
}
|
||||
|
||||
#define NS_REGISTER_TAG_COMMAND(_cmdClass, _cmdName, _tagName) \
|
||||
{ \
|
||||
_cmdClass* theCmd = new _cmdClass(_tagName); \
|
||||
inCommandTable->RegisterCommand(_cmdName, \
|
||||
static_cast<nsIControllerCommand *>(theCmd)); \
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
nsresult
|
||||
nsComposerController::RegisterEditorDocStateCommands(
|
||||
nsIControllerCommandTable *inCommandTable)
|
||||
{
|
||||
// observer commands for document state
|
||||
NS_REGISTER_FIRST_COMMAND(nsDocumentStateCommand, "obs_documentCreated")
|
||||
NS_REGISTER_NEXT_COMMAND(nsDocumentStateCommand, "obs_documentWillBeDestroyed")
|
||||
NS_REGISTER_LAST_COMMAND(nsDocumentStateCommand, "obs_documentLocationChanged")
|
||||
|
||||
// commands that may get or change state
|
||||
NS_REGISTER_FIRST_COMMAND(nsSetDocumentStateCommand, "cmd_setDocumentModified")
|
||||
NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_setDocumentUseCSS")
|
||||
NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_setDocumentReadOnly")
|
||||
NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_insertBrOnReturn")
|
||||
NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_enableObjectResizing")
|
||||
NS_REGISTER_LAST_COMMAND(nsSetDocumentStateCommand, "cmd_enableInlineTableEditing")
|
||||
|
||||
NS_REGISTER_ONE_COMMAND(nsSetDocumentOptionsCommand, "cmd_setDocumentOptions")
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
nsComposerController::RegisterHTMLEditorCommands(
|
||||
nsIControllerCommandTable *inCommandTable)
|
||||
{
|
||||
// Edit menu
|
||||
NS_REGISTER_ONE_COMMAND(nsPasteNoFormattingCommand, "cmd_pasteNoFormatting");
|
||||
|
||||
// indent/outdent
|
||||
NS_REGISTER_ONE_COMMAND(nsIndentCommand, "cmd_indent");
|
||||
NS_REGISTER_ONE_COMMAND(nsOutdentCommand, "cmd_outdent");
|
||||
|
||||
// Styles
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_bold", nsGkAtoms::b);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_italic", nsGkAtoms::i);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_underline", nsGkAtoms::u);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_tt", nsGkAtoms::tt);
|
||||
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_strikethrough", nsGkAtoms::strike);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_superscript", nsGkAtoms::sup);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_subscript", nsGkAtoms::sub);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_nobreak", nsGkAtoms::nobr);
|
||||
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_em", nsGkAtoms::em);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_strong", nsGkAtoms::strong);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_cite", nsGkAtoms::cite);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_abbr", nsGkAtoms::abbr);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_acronym", nsGkAtoms::acronym);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_code", nsGkAtoms::code);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_samp", nsGkAtoms::samp);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_var", nsGkAtoms::var);
|
||||
NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_removeLinks", nsGkAtoms::href);
|
||||
|
||||
// lists
|
||||
NS_REGISTER_STYLE_COMMAND(nsListCommand, "cmd_ol", nsGkAtoms::ol);
|
||||
NS_REGISTER_STYLE_COMMAND(nsListCommand, "cmd_ul", nsGkAtoms::ul);
|
||||
NS_REGISTER_STYLE_COMMAND(nsListItemCommand, "cmd_dt", nsGkAtoms::dt);
|
||||
NS_REGISTER_STYLE_COMMAND(nsListItemCommand, "cmd_dd", nsGkAtoms::dd);
|
||||
NS_REGISTER_ONE_COMMAND(nsRemoveListCommand, "cmd_removeList");
|
||||
|
||||
// format stuff
|
||||
NS_REGISTER_ONE_COMMAND(nsParagraphStateCommand, "cmd_paragraphState");
|
||||
NS_REGISTER_ONE_COMMAND(nsFontFaceStateCommand, "cmd_fontFace");
|
||||
NS_REGISTER_ONE_COMMAND(nsFontSizeStateCommand, "cmd_fontSize");
|
||||
NS_REGISTER_ONE_COMMAND(nsFontColorStateCommand, "cmd_fontColor");
|
||||
NS_REGISTER_ONE_COMMAND(nsBackgroundColorStateCommand, "cmd_backgroundColor");
|
||||
NS_REGISTER_ONE_COMMAND(nsHighlightColorStateCommand, "cmd_highlight");
|
||||
|
||||
NS_REGISTER_ONE_COMMAND(nsAlignCommand, "cmd_align");
|
||||
NS_REGISTER_ONE_COMMAND(nsRemoveStylesCommand, "cmd_removeStyles");
|
||||
|
||||
NS_REGISTER_ONE_COMMAND(nsIncreaseFontSizeCommand, "cmd_increaseFont");
|
||||
NS_REGISTER_ONE_COMMAND(nsDecreaseFontSizeCommand, "cmd_decreaseFont");
|
||||
|
||||
// Insert content
|
||||
NS_REGISTER_ONE_COMMAND(nsInsertHTMLCommand, "cmd_insertHTML");
|
||||
NS_REGISTER_TAG_COMMAND(nsInsertTagCommand, "cmd_insertLinkNoUI", nsGkAtoms::a);
|
||||
NS_REGISTER_TAG_COMMAND(nsInsertTagCommand, "cmd_insertImageNoUI", nsGkAtoms::img);
|
||||
NS_REGISTER_TAG_COMMAND(nsInsertTagCommand, "cmd_insertHR", nsGkAtoms::hr);
|
||||
|
||||
NS_REGISTER_ONE_COMMAND(nsAbsolutePositioningCommand, "cmd_absPos");
|
||||
NS_REGISTER_ONE_COMMAND(nsDecreaseZIndexCommand, "cmd_decreaseZIndex");
|
||||
NS_REGISTER_ONE_COMMAND(nsIncreaseZIndexCommand, "cmd_increaseZIndex");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
34
editor/composer/nsComposerController.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsComposerController_h__
|
||||
#define nsComposerController_h__
|
||||
|
||||
|
||||
#include "nscore.h" // for nsresult
|
||||
|
||||
class nsIControllerCommandTable;
|
||||
|
||||
|
||||
// The plaintext editor controller is used for basic text editing and html editing
|
||||
// commands in composer
|
||||
// The refCon that gets passed to its commands is initially nsIEditingSession,
|
||||
// and after successfule editor creation it is changed to nsIEditor.
|
||||
#define NS_EDITORDOCSTATECONTROLLER_CID \
|
||||
{ 0x50e95301, 0x17a8, 0x11d4, { 0x9f, 0x7e, 0xdd, 0x53, 0x0d, 0x5f, 0x05, 0x7c } }
|
||||
|
||||
// The HTMLEditor controller is used only for HTML editors and takes nsIEditor as refCon
|
||||
#define NS_HTMLEDITORCONTROLLER_CID \
|
||||
{ 0x62db0002, 0xdbb6, 0x43f4, { 0x8f, 0xb7, 0x9d, 0x25, 0x38, 0xbc, 0x57, 0x47 } }
|
||||
|
||||
|
||||
class nsComposerController
|
||||
{
|
||||
public:
|
||||
static nsresult RegisterEditorDocStateCommands(nsIControllerCommandTable* inCommandTable);
|
||||
static nsresult RegisterHTMLEditorCommands(nsIControllerCommandTable* inCommandTable);
|
||||
};
|
||||
|
||||
#endif /* nsComposerController_h__ */
|
||||
480
editor/composer/nsComposerDocumentCommands.cpp
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
|
||||
#include "nsCOMPtr.h" // for nsCOMPtr, do_QueryInterface, etc
|
||||
#include "nsCRT.h" // for nsCRT
|
||||
#include "nsComposerCommands.h" // for nsSetDocumentOptionsCommand, etc
|
||||
#include "nsDebug.h" // for NS_ENSURE_ARG_POINTER, etc
|
||||
#include "nsError.h" // for NS_ERROR_INVALID_ARG, etc
|
||||
#include "nsICommandParams.h" // for nsICommandParams
|
||||
#include "nsIDOMDocument.h" // for nsIDOMDocument
|
||||
#include "nsIDocShell.h" // for nsIDocShell
|
||||
#include "nsIDocument.h" // for nsIDocument
|
||||
#include "nsIEditingSession.h" // for nsIEditingSession, etc
|
||||
#include "nsIEditor.h" // for nsIEditor
|
||||
#include "nsIHTMLEditor.h" // for nsIHTMLEditor
|
||||
#include "nsIHTMLInlineTableEditor.h" // for nsIHTMLInlineTableEditor
|
||||
#include "nsIHTMLObjectResizer.h" // for nsIHTMLObjectResizer
|
||||
#include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc
|
||||
#include "nsIPresShell.h" // for nsIPresShell
|
||||
#include "nsISelectionController.h" // for nsISelectionController
|
||||
#include "nsISupportsImpl.h" // for nsPresContext::Release
|
||||
#include "nsISupportsUtils.h" // for NS_IF_ADDREF
|
||||
#include "nsIURI.h" // for nsIURI
|
||||
#include "nsPresContext.h" // for nsPresContext
|
||||
#include "nscore.h" // for NS_IMETHODIMP, nsresult, etc
|
||||
|
||||
class nsISupports;
|
||||
|
||||
//defines
|
||||
#define STATE_ENABLED "state_enabled"
|
||||
#define STATE_ALL "state_all"
|
||||
#define STATE_ATTRIBUTE "state_attribute"
|
||||
#define STATE_DATA "state_data"
|
||||
|
||||
static
|
||||
nsresult
|
||||
GetPresContextFromEditor(nsIEditor *aEditor, nsPresContext **aResult)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aResult);
|
||||
*aResult = nullptr;
|
||||
NS_ENSURE_ARG_POINTER(aEditor);
|
||||
|
||||
nsCOMPtr<nsISelectionController> selCon;
|
||||
nsresult rv = aEditor->GetSelectionController(getter_AddRefs(selCon));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
|
||||
|
||||
nsCOMPtr<nsIPresShell> presShell = do_QueryInterface(selCon);
|
||||
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
|
||||
|
||||
NS_IF_ADDREF(*aResult = presShell->GetPresContext());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentOptionsCommand::IsCommandEnabled(const char * aCommandName,
|
||||
nsISupports *refCon,
|
||||
bool *outCmdEnabled)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(outCmdEnabled);
|
||||
nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
|
||||
if (editor) {
|
||||
return editor->GetIsSelectionEditable(outCmdEnabled);
|
||||
}
|
||||
|
||||
*outCmdEnabled = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentOptionsCommand::DoCommand(const char *aCommandName,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentOptionsCommand::DoCommandParams(const char *aCommandName,
|
||||
nsICommandParams *aParams,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
|
||||
nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
RefPtr<nsPresContext> presContext;
|
||||
nsresult rv = GetPresContextFromEditor(editor, getter_AddRefs(presContext));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
|
||||
|
||||
int32_t animationMode;
|
||||
rv = aParams->GetLongValue("imageAnimation", &animationMode);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// for possible values of animation mode, see:
|
||||
// http://lxr.mozilla.org/seamonkey/source/image/public/imgIContainer.idl
|
||||
presContext->SetImageAnimationMode(animationMode);
|
||||
}
|
||||
|
||||
bool allowPlugins;
|
||||
rv = aParams->GetBooleanValue("plugins", &allowPlugins);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIDocShell> docShell(presContext->GetDocShell());
|
||||
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
|
||||
|
||||
rv = docShell->SetAllowPlugins(allowPlugins);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentOptionsCommand::GetCommandStateParams(const char *aCommandName,
|
||||
nsICommandParams *aParams,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
NS_ENSURE_ARG_POINTER(refCon);
|
||||
|
||||
// The base editor owns most state info
|
||||
nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
// Always get the enabled state
|
||||
bool outCmdEnabled = false;
|
||||
IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
|
||||
nsresult rv = aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// get pres context
|
||||
RefPtr<nsPresContext> presContext;
|
||||
rv = GetPresContextFromEditor(editor, getter_AddRefs(presContext));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
|
||||
|
||||
int32_t animationMode;
|
||||
rv = aParams->GetLongValue("imageAnimation", &animationMode);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// for possible values of animation mode, see
|
||||
// http://lxr.mozilla.org/seamonkey/source/image/public/imgIContainer.idl
|
||||
rv = aParams->SetLongValue("imageAnimation",
|
||||
presContext->ImageAnimationMode());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
bool allowPlugins = false;
|
||||
rv = aParams->GetBooleanValue("plugins", &allowPlugins);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIDocShell> docShell(presContext->GetDocShell());
|
||||
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
|
||||
|
||||
allowPlugins = docShell->PluginsAllowedInCurrentDoc();
|
||||
|
||||
rv = aParams->SetBooleanValue("plugins", allowPlugins);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Commands for document state that may be changed via doCommandParams
|
||||
* As of 11/11/02, this is just "cmd_setDocumentModified"
|
||||
* Note that you can use the same command class, nsSetDocumentStateCommand,
|
||||
* for more than one of this type of command
|
||||
* We check the input command param for different behavior
|
||||
*/
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentStateCommand::IsCommandEnabled(const char * aCommandName,
|
||||
nsISupports *refCon,
|
||||
bool *outCmdEnabled)
|
||||
{
|
||||
// These commands are always enabled
|
||||
NS_ENSURE_ARG_POINTER(outCmdEnabled);
|
||||
*outCmdEnabled = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentStateCommand::DoCommand(const char *aCommandName,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentStateCommand::DoCommandParams(const char *aCommandName,
|
||||
nsICommandParams *aParams,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentModified")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
|
||||
bool modified;
|
||||
nsresult rv = aParams->GetBooleanValue(STATE_ATTRIBUTE, &modified);
|
||||
|
||||
// Should we fail if this param wasn't set?
|
||||
// I'm not sure we should be that strict
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (modified) {
|
||||
return editor->IncrementModificationCount(1);
|
||||
}
|
||||
|
||||
return editor->ResetModificationCount();
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentReadOnly")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
bool isReadOnly;
|
||||
nsresult rvRO = aParams->GetBooleanValue(STATE_ATTRIBUTE, &isReadOnly);
|
||||
NS_ENSURE_SUCCESS(rvRO, rvRO);
|
||||
|
||||
uint32_t flags;
|
||||
editor->GetFlags(&flags);
|
||||
if (isReadOnly) {
|
||||
flags |= nsIPlaintextEditor::eEditorReadonlyMask;
|
||||
} else {
|
||||
flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
|
||||
}
|
||||
|
||||
return editor->SetFlags(flags);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentUseCSS")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool desireCSS;
|
||||
nsresult rvCSS = aParams->GetBooleanValue(STATE_ATTRIBUTE, &desireCSS);
|
||||
NS_ENSURE_SUCCESS(rvCSS, rvCSS);
|
||||
|
||||
return htmleditor->SetIsCSSEnabled(desireCSS);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_insertBrOnReturn")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool insertBrOnReturn;
|
||||
nsresult rvBR = aParams->GetBooleanValue(STATE_ATTRIBUTE,
|
||||
&insertBrOnReturn);
|
||||
NS_ENSURE_SUCCESS(rvBR, rvBR);
|
||||
|
||||
return htmleditor->SetReturnInParagraphCreatesNewParagraph(!insertBrOnReturn);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_enableObjectResizing")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLObjectResizer> resizer = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(resizer, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool enabled;
|
||||
nsresult rvOR = aParams->GetBooleanValue(STATE_ATTRIBUTE, &enabled);
|
||||
NS_ENSURE_SUCCESS(rvOR, rvOR);
|
||||
|
||||
return resizer->SetObjectResizingEnabled(enabled);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_enableInlineTableEditing")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLInlineTableEditor> editor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool enabled;
|
||||
nsresult rvOR = aParams->GetBooleanValue(STATE_ATTRIBUTE, &enabled);
|
||||
NS_ENSURE_SUCCESS(rvOR, rvOR);
|
||||
|
||||
return editor->SetInlineTableEditingEnabled(enabled);
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSetDocumentStateCommand::GetCommandStateParams(const char *aCommandName,
|
||||
nsICommandParams *aParams,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
NS_ENSURE_ARG_POINTER(refCon);
|
||||
|
||||
// The base editor owns most state info
|
||||
nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
// Always get the enabled state
|
||||
bool outCmdEnabled = false;
|
||||
IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
|
||||
nsresult rv = aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentModified")) {
|
||||
bool modified;
|
||||
rv = editor->GetDocumentModified(&modified);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return aParams->SetBooleanValue(STATE_ATTRIBUTE, modified);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentReadOnly")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
|
||||
uint32_t flags;
|
||||
editor->GetFlags(&flags);
|
||||
bool isReadOnly = flags & nsIPlaintextEditor::eEditorReadonlyMask;
|
||||
return aParams->SetBooleanValue(STATE_ATTRIBUTE, isReadOnly);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentUseCSS")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool isCSS;
|
||||
htmleditor->GetIsCSSEnabled(&isCSS);
|
||||
return aParams->SetBooleanValue(STATE_ALL, isCSS);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_insertBrOnReturn")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool createPOnReturn;
|
||||
htmleditor->GetReturnInParagraphCreatesNewParagraph(&createPOnReturn);
|
||||
return aParams->SetBooleanValue(STATE_ATTRIBUTE, !createPOnReturn);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_enableObjectResizing")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLObjectResizer> resizer = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(resizer, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool enabled;
|
||||
resizer->GetObjectResizingEnabled(&enabled);
|
||||
return aParams->SetBooleanValue(STATE_ATTRIBUTE, enabled);
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "cmd_enableInlineTableEditing")) {
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
nsCOMPtr<nsIHTMLInlineTableEditor> editor = do_QueryInterface(refCon);
|
||||
NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG);
|
||||
|
||||
bool enabled;
|
||||
editor->GetInlineTableEditingEnabled(&enabled);
|
||||
return aParams->SetBooleanValue(STATE_ATTRIBUTE, enabled);
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commands just for state notification
|
||||
* As of 11/21/02, possible commands are:
|
||||
* "obs_documentCreated"
|
||||
* "obs_documentWillBeDestroyed"
|
||||
* "obs_documentLocationChanged"
|
||||
* Note that you can use the same command class, nsDocumentStateCommand
|
||||
* for these or future observer commands.
|
||||
* We check the input command param for different behavior
|
||||
*
|
||||
* How to use:
|
||||
* 1. Get the nsICommandManager for the current editor
|
||||
* 2. Implement an nsIObserve object, e.g:
|
||||
*
|
||||
* void Observe(
|
||||
* in nsISupports aSubject, // The nsICommandManager calling this Observer
|
||||
* in string aTopic, // command name, e.g.:"obs_documentCreated"
|
||||
* // or "obs_documentWillBeDestroyed"
|
||||
in wstring aData ); // ignored (set to "command_status_changed")
|
||||
*
|
||||
* 3. Add the observer by:
|
||||
* commandManager.addObserver(observeobject, obs_documentCreated);
|
||||
* 4. In the appropriate location in editorSession, editor, or commands code,
|
||||
* trigger the notification of this observer by something like:
|
||||
*
|
||||
* nsCOMPtr<nsICommandManager> commandManager = mDocShell->GetCommandManager();
|
||||
* nsCOMPtr<nsPICommandUpdater> commandUpdater = do_QueryInterface(commandManager);
|
||||
* NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE);
|
||||
* commandUpdater->CommandStatusChanged(obs_documentCreated);
|
||||
*
|
||||
* 5. Use GetCommandStateParams() to obtain state information
|
||||
* e.g., any creation state codes when creating an editor are
|
||||
* supplied for "obs_documentCreated" command in the
|
||||
* "state_data" param's value
|
||||
*
|
||||
*/
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentStateCommand::IsCommandEnabled(const char* aCommandName,
|
||||
nsISupports *refCon,
|
||||
bool *outCmdEnabled)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(outCmdEnabled);
|
||||
// Always return false to discourage callers from using DoCommand()
|
||||
*outCmdEnabled = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentStateCommand::DoCommand(const char *aCommandName,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentStateCommand::DoCommandParams(const char *aCommandName,
|
||||
nsICommandParams *aParams,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentStateCommand::GetCommandStateParams(const char *aCommandName,
|
||||
nsICommandParams *aParams,
|
||||
nsISupports *refCon)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aParams);
|
||||
NS_ENSURE_ARG_POINTER(aCommandName);
|
||||
nsresult rv;
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "obs_documentCreated")) {
|
||||
uint32_t editorStatus = nsIEditingSession::eEditorErrorUnknown;
|
||||
|
||||
nsCOMPtr<nsIEditingSession> editingSession = do_QueryInterface(refCon);
|
||||
if (editingSession) {
|
||||
// refCon is initially set to nsIEditingSession until editor
|
||||
// is successfully created and source doc is loaded
|
||||
// Embedder gets error status if this fails
|
||||
// If called before startup is finished,
|
||||
// status = eEditorCreationInProgress
|
||||
rv = editingSession->GetEditorStatus(&editorStatus);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
// If refCon is an editor, then everything started up OK!
|
||||
nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
|
||||
if (editor) {
|
||||
editorStatus = nsIEditingSession::eEditorOK;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that if refCon is not-null, but is neither
|
||||
// an nsIEditingSession or nsIEditor, we return "eEditorErrorUnknown"
|
||||
aParams->SetLongValue(STATE_DATA, editorStatus);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aCommandName, "obs_documentLocationChanged")) {
|
||||
nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
|
||||
if (!editor) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMDocument> domDoc;
|
||||
editor->GetDocument(getter_AddRefs(domDoc));
|
||||
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
|
||||
|
||||
nsIURI *uri = doc->GetDocumentURI();
|
||||
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
|
||||
|
||||
return aParams->SetISupportsValue(STATE_DATA, (nsISupports*)uri);
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
226
editor/composer/nsComposerRegistration.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include <stddef.h> // for nullptr
|
||||
|
||||
#include "mozilla/Module.h" // for Module, Module::CIDEntry, etc
|
||||
#include "mozilla/ModuleUtils.h"
|
||||
#include "mozilla/mozalloc.h" // for operator new
|
||||
#include "nsCOMPtr.h" // for nsCOMPtr, getter_AddRefs, etc
|
||||
#include "nsComponentManagerUtils.h" // for do_CreateInstance
|
||||
#include "nsComposeTxtSrvFilter.h" // for nsComposeTxtSrvFilter, etc
|
||||
#include "nsComposerController.h" // for nsComposerController, etc
|
||||
#include "nsDebug.h" // for NS_ENSURE_SUCCESS
|
||||
#include "nsEditingSession.h" // for NS_EDITINGSESSION_CID, etc
|
||||
#include "nsEditorSpellCheck.h" // for NS_EDITORSPELLCHECK_CID, etc
|
||||
#include "nsError.h" // for NS_ERROR_NO_AGGREGATION, etc
|
||||
#include "nsIController.h" // for nsIController
|
||||
#include "nsIControllerCommandTable.h" // for nsIControllerCommandTable, etc
|
||||
#include "nsIControllerContext.h" // for nsIControllerContext
|
||||
#include "nsID.h" // for NS_DEFINE_NAMED_CID, etc
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE
|
||||
#include "nsServiceManagerUtils.h" // for do_GetService
|
||||
#include "nscore.h" // for nsresult
|
||||
|
||||
class nsISupports;
|
||||
|
||||
#define NS_HTMLEDITOR_COMMANDTABLE_CID \
|
||||
{ 0x13e50d8d, 0x9cee, 0x4ad1, { 0xa3, 0xa2, 0x4a, 0x44, 0x2f, 0xdf, 0x7d, 0xfa } }
|
||||
|
||||
#define NS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID \
|
||||
{ 0xa33982d3, 0x1adf, 0x4162, { 0x99, 0x41, 0xf7, 0x34, 0xbc, 0x45, 0xe4, 0xed } }
|
||||
|
||||
|
||||
static NS_DEFINE_CID(kHTMLEditorCommandTableCID, NS_HTMLEDITOR_COMMANDTABLE_CID);
|
||||
static NS_DEFINE_CID(kHTMLEditorDocStateCommandTableCID, NS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Define the contructor function for the objects
|
||||
//
|
||||
// NOTE: This creates an instance of objects by using the default constructor
|
||||
//
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsEditingSession)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsEditorSpellCheck)
|
||||
|
||||
// There are no macros that enable us to have 2 constructors
|
||||
// for the same object
|
||||
//
|
||||
// Here we are creating the same object with two different contract IDs
|
||||
// and then initializing it different.
|
||||
// Basically, we need to tell the filter whether it is doing mail or not
|
||||
static nsresult
|
||||
nsComposeTxtSrvFilterConstructor(nsISupports *aOuter, REFNSIID aIID,
|
||||
void **aResult, bool aIsForMail)
|
||||
{
|
||||
*aResult = nullptr;
|
||||
if (aOuter) {
|
||||
return NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
nsComposeTxtSrvFilter * inst = new nsComposeTxtSrvFilter();
|
||||
if (!inst) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
NS_ADDREF(inst);
|
||||
inst->Init(aIsForMail);
|
||||
nsresult rv = inst->QueryInterface(aIID, aResult);
|
||||
NS_RELEASE(inst);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
nsComposeTxtSrvFilterConstructorForComposer(nsISupports *aOuter,
|
||||
REFNSIID aIID,
|
||||
void **aResult)
|
||||
{
|
||||
return nsComposeTxtSrvFilterConstructor(aOuter, aIID, aResult, false);
|
||||
}
|
||||
|
||||
static nsresult
|
||||
nsComposeTxtSrvFilterConstructorForMail(nsISupports *aOuter,
|
||||
REFNSIID aIID,
|
||||
void **aResult)
|
||||
{
|
||||
return nsComposeTxtSrvFilterConstructor(aOuter, aIID, aResult, true);
|
||||
}
|
||||
|
||||
|
||||
// Constructor for a controller set up with a command table specified
|
||||
// by the CID passed in. This function uses do_GetService to get the
|
||||
// command table, so that every controller shares a single command
|
||||
// table, for space-efficiency.
|
||||
//
|
||||
// The only reason to go via the service manager for the command table
|
||||
// is that it holds onto the singleton for us, avoiding static variables here.
|
||||
static nsresult
|
||||
CreateControllerWithSingletonCommandTable(const nsCID& inCommandTableCID, nsIController **aResult)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIController> controller = do_CreateInstance("@mozilla.org/embedcomp/base-command-controller;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIControllerCommandTable> composerCommandTable = do_GetService(inCommandTableCID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// this guy is a singleton, so make it immutable
|
||||
composerCommandTable->MakeImmutable();
|
||||
|
||||
nsCOMPtr<nsIControllerContext> controllerContext = do_QueryInterface(controller, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = controllerContext->Init(composerCommandTable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aResult = controller;
|
||||
NS_ADDREF(*aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
// Here we make an instance of the controller that holds doc state commands.
|
||||
// We set it up with a singleton command table.
|
||||
static nsresult
|
||||
nsHTMLEditorDocStateControllerConstructor(nsISupports *aOuter, REFNSIID aIID,
|
||||
void **aResult)
|
||||
{
|
||||
nsCOMPtr<nsIController> controller;
|
||||
nsresult rv = CreateControllerWithSingletonCommandTable(kHTMLEditorDocStateCommandTableCID, getter_AddRefs(controller));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return controller->QueryInterface(aIID, aResult);
|
||||
}
|
||||
|
||||
// Tere we make an instance of the controller that holds composer commands.
|
||||
// We set it up with a singleton command table.
|
||||
static nsresult
|
||||
nsHTMLEditorControllerConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult)
|
||||
{
|
||||
nsCOMPtr<nsIController> controller;
|
||||
nsresult rv = CreateControllerWithSingletonCommandTable(kHTMLEditorCommandTableCID, getter_AddRefs(controller));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return controller->QueryInterface(aIID, aResult);
|
||||
}
|
||||
|
||||
// Constructor for a command table that is pref-filled with HTML editor commands
|
||||
static nsresult
|
||||
nsHTMLEditorCommandTableConstructor(nsISupports *aOuter, REFNSIID aIID,
|
||||
void **aResult)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIControllerCommandTable> commandTable =
|
||||
do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = nsComposerController::RegisterHTMLEditorCommands(commandTable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// we don't know here whether we're being created as an instance,
|
||||
// or a service, so we can't become immutable
|
||||
|
||||
return commandTable->QueryInterface(aIID, aResult);
|
||||
}
|
||||
|
||||
|
||||
// Constructor for a command table that is pref-filled with HTML editor doc state commands
|
||||
static nsresult
|
||||
nsHTMLEditorDocStateCommandTableConstructor(nsISupports *aOuter, REFNSIID aIID,
|
||||
void **aResult)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIControllerCommandTable> commandTable =
|
||||
do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = nsComposerController::RegisterEditorDocStateCommands(commandTable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// we don't know here whether we're being created as an instance,
|
||||
// or a service, so we can't become immutable
|
||||
|
||||
return commandTable->QueryInterface(aIID, aResult);
|
||||
}
|
||||
|
||||
NS_DEFINE_NAMED_CID(NS_HTMLEDITORCONTROLLER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_EDITORDOCSTATECONTROLLER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_COMMANDTABLE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_EDITINGSESSION_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_EDITORSPELLCHECK_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_COMPOSERTXTSRVFILTER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_COMPOSERTXTSRVFILTERMAIL_CID);
|
||||
|
||||
|
||||
static const mozilla::Module::CIDEntry kComposerCIDs[] = {
|
||||
{ &kNS_HTMLEDITORCONTROLLER_CID, false, nullptr, nsHTMLEditorControllerConstructor },
|
||||
{ &kNS_EDITORDOCSTATECONTROLLER_CID, false, nullptr, nsHTMLEditorDocStateControllerConstructor },
|
||||
{ &kNS_HTMLEDITOR_COMMANDTABLE_CID, false, nullptr, nsHTMLEditorCommandTableConstructor },
|
||||
{ &kNS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID, false, nullptr, nsHTMLEditorDocStateCommandTableConstructor },
|
||||
{ &kNS_EDITINGSESSION_CID, false, nullptr, nsEditingSessionConstructor },
|
||||
{ &kNS_EDITORSPELLCHECK_CID, false, nullptr, nsEditorSpellCheckConstructor },
|
||||
{ &kNS_COMPOSERTXTSRVFILTER_CID, false, nullptr, nsComposeTxtSrvFilterConstructorForComposer },
|
||||
{ &kNS_COMPOSERTXTSRVFILTERMAIL_CID, false, nullptr, nsComposeTxtSrvFilterConstructorForMail },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kComposerContracts[] = {
|
||||
{ "@mozilla.org/editor/htmleditorcontroller;1", &kNS_HTMLEDITORCONTROLLER_CID },
|
||||
{ "@mozilla.org/editor/editordocstatecontroller;1", &kNS_EDITORDOCSTATECONTROLLER_CID },
|
||||
{ "@mozilla.org/editor/editingsession;1", &kNS_EDITINGSESSION_CID },
|
||||
{ "@mozilla.org/editor/editorspellchecker;1", &kNS_EDITORSPELLCHECK_CID },
|
||||
{ COMPOSER_TXTSRVFILTER_CONTRACTID, &kNS_COMPOSERTXTSRVFILTER_CID },
|
||||
{ COMPOSER_TXTSRVFILTERMAIL_CONTRACTID, &kNS_COMPOSERTXTSRVFILTERMAIL_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module kComposerModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kComposerCIDs,
|
||||
kComposerContracts
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsComposerModule) = &kComposerModule;
|
||||
1392
editor/composer/nsEditingSession.cpp
Normal file
147
editor/composer/nsEditingSession.h
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsEditingSession_h__
|
||||
#define nsEditingSession_h__
|
||||
|
||||
|
||||
#ifndef nsWeakReference_h__
|
||||
#include "nsWeakReference.h" // for nsSupportsWeakReference, etc
|
||||
#endif
|
||||
|
||||
#include "nsCOMPtr.h" // for nsCOMPtr
|
||||
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS
|
||||
#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
|
||||
#include "nsWeakReference.h" // for nsSupportsWeakReference, etc
|
||||
#include "nscore.h" // for nsresult
|
||||
|
||||
#ifndef __gen_nsIWebProgressListener_h__
|
||||
#include "nsIWebProgressListener.h"
|
||||
#endif
|
||||
|
||||
#ifndef __gen_nsIEditingSession_h__
|
||||
#include "nsIEditingSession.h" // for NS_DECL_NSIEDITINGSESSION, etc
|
||||
#endif
|
||||
|
||||
#include "nsString.h" // for nsCString
|
||||
|
||||
class mozIDOMWindowProxy;
|
||||
class nsIDOMWindow;
|
||||
class nsISupports;
|
||||
class nsITimer;
|
||||
|
||||
#define NS_EDITINGSESSION_CID \
|
||||
{ 0xbc26ff01, 0xf2bd, 0x11d4, { 0xa7, 0x3c, 0xe5, 0xa4, 0xb5, 0xa8, 0xbd, 0xfc } }
|
||||
|
||||
|
||||
class nsComposerCommandsUpdater;
|
||||
class nsIChannel;
|
||||
class nsIControllers;
|
||||
class nsIDocShell;
|
||||
class nsIEditor;
|
||||
class nsIWebProgress;
|
||||
|
||||
class nsEditingSession final : public nsIEditingSession,
|
||||
public nsIWebProgressListener,
|
||||
public nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
|
||||
nsEditingSession();
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
// nsIWebProgressListener
|
||||
NS_DECL_NSIWEBPROGRESSLISTENER
|
||||
|
||||
// nsIEditingSession
|
||||
NS_DECL_NSIEDITINGSESSION
|
||||
|
||||
protected:
|
||||
virtual ~nsEditingSession();
|
||||
|
||||
nsresult SetupEditorCommandController(const char *aControllerClassName,
|
||||
mozIDOMWindowProxy* aWindow,
|
||||
nsISupports *aContext,
|
||||
uint32_t *aControllerId);
|
||||
|
||||
nsresult SetContextOnControllerById(nsIControllers* aControllers,
|
||||
nsISupports* aContext,
|
||||
uint32_t aID);
|
||||
|
||||
nsresult PrepareForEditing(nsPIDOMWindowOuter* aWindow);
|
||||
|
||||
static void TimerCallback(nsITimer *aTimer, void *aClosure);
|
||||
nsCOMPtr<nsITimer> mLoadBlankDocTimer;
|
||||
|
||||
// progress load stuff
|
||||
nsresult StartDocumentLoad(nsIWebProgress *aWebProgress,
|
||||
bool isToBeMadeEditable);
|
||||
nsresult EndDocumentLoad(nsIWebProgress *aWebProgress,
|
||||
nsIChannel* aChannel, nsresult aStatus,
|
||||
bool isToBeMadeEditable);
|
||||
nsresult StartPageLoad(nsIChannel *aChannel);
|
||||
nsresult EndPageLoad(nsIWebProgress *aWebProgress,
|
||||
nsIChannel* aChannel, nsresult aStatus);
|
||||
|
||||
bool IsProgressForTargetDocument(nsIWebProgress *aWebProgress);
|
||||
|
||||
void RemoveEditorControllers(nsPIDOMWindowOuter* aWindow);
|
||||
void RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow);
|
||||
void RestoreAnimationMode(nsPIDOMWindowOuter* aWindow);
|
||||
void RemoveListenersAndControllers(nsPIDOMWindowOuter* aWindow,
|
||||
nsIEditor *aEditor);
|
||||
|
||||
protected:
|
||||
|
||||
bool mDoneSetup; // have we prepared for editing yet?
|
||||
|
||||
// Used to prevent double creation of editor because nsIWebProgressListener
|
||||
// receives a STATE_STOP notification before the STATE_START
|
||||
// for our document, so we wait for the STATE_START, then STATE_STOP
|
||||
// before creating an editor
|
||||
bool mCanCreateEditor;
|
||||
|
||||
bool mInteractive;
|
||||
bool mMakeWholeDocumentEditable;
|
||||
|
||||
bool mDisabledJSAndPlugins;
|
||||
|
||||
// True if scripts were enabled before the editor turned scripts
|
||||
// off, otherwise false.
|
||||
bool mScriptsEnabled;
|
||||
|
||||
// True if plugins were enabled before the editor turned plugins
|
||||
// off, otherwise false.
|
||||
bool mPluginsEnabled;
|
||||
|
||||
bool mProgressListenerRegistered;
|
||||
|
||||
// The image animation mode before it was turned off.
|
||||
uint16_t mImageAnimationMode;
|
||||
|
||||
// THE REMAINING MEMBER VARIABLES WILL BECOME A SET WHEN WE EDIT
|
||||
// MORE THAN ONE EDITOR PER EDITING SESSION
|
||||
RefPtr<nsComposerCommandsUpdater> mStateMaintainer;
|
||||
|
||||
// Save the editor type so we can create the editor after loading uri
|
||||
nsCString mEditorType;
|
||||
uint32_t mEditorFlags;
|
||||
uint32_t mEditorStatus;
|
||||
uint32_t mBaseCommandControllerId;
|
||||
uint32_t mDocStateControllerId;
|
||||
uint32_t mHTMLCommandControllerId;
|
||||
|
||||
// Make sure the docshell we use is safe
|
||||
nsWeakPtr mDocShell;
|
||||
|
||||
// See if we can reuse an existing editor
|
||||
nsWeakPtr mExistingEditor;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // nsEditingSession_h__
|
||||
1005
editor/composer/nsEditorSpellCheck.cpp
Normal file
85
editor/composer/nsEditorSpellCheck.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsEditorSpellCheck_h___
|
||||
#define nsEditorSpellCheck_h___
|
||||
|
||||
|
||||
#include "nsCOMPtr.h" // for nsCOMPtr
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIEditorSpellCheck.h" // for NS_DECL_NSIEDITORSPELLCHECK, etc
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsString.h" // for nsString
|
||||
#include "nsTArray.h" // for nsTArray
|
||||
#include "nscore.h" // for nsresult
|
||||
|
||||
class nsIEditor;
|
||||
class nsISpellChecker;
|
||||
class nsITextServicesFilter;
|
||||
|
||||
#define NS_EDITORSPELLCHECK_CID \
|
||||
{ /* {75656ad9-bd13-4c5d-939a-ec6351eea0cc} */ \
|
||||
0x75656ad9, 0xbd13, 0x4c5d, \
|
||||
{ 0x93, 0x9a, 0xec, 0x63, 0x51, 0xee, 0xa0, 0xcc }\
|
||||
}
|
||||
|
||||
class DictionaryFetcher;
|
||||
|
||||
enum dictCompare {
|
||||
DICT_NORMAL_COMPARE,
|
||||
DICT_COMPARE_CASE_INSENSITIVE,
|
||||
DICT_COMPARE_DASHMATCH
|
||||
};
|
||||
|
||||
class nsEditorSpellCheck final : public nsIEditorSpellCheck
|
||||
{
|
||||
friend class DictionaryFetcher;
|
||||
|
||||
public:
|
||||
nsEditorSpellCheck();
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(nsEditorSpellCheck)
|
||||
|
||||
/* Declare all methods in the nsIEditorSpellCheck interface */
|
||||
NS_DECL_NSIEDITORSPELLCHECK
|
||||
|
||||
protected:
|
||||
virtual ~nsEditorSpellCheck();
|
||||
|
||||
nsCOMPtr<nsISpellChecker> mSpellChecker;
|
||||
|
||||
nsTArray<nsString> mSuggestedWordList;
|
||||
int32_t mSuggestedWordIndex;
|
||||
|
||||
// these are the words in the current personal dictionary,
|
||||
// GetPersonalDictionary must be called to load them.
|
||||
nsTArray<nsString> mDictionaryList;
|
||||
int32_t mDictionaryIndex;
|
||||
|
||||
nsresult DeleteSuggestedWordList();
|
||||
|
||||
nsCOMPtr<nsITextServicesFilter> mTxtSrvFilter;
|
||||
nsCOMPtr<nsIEditor> mEditor;
|
||||
|
||||
nsString mPreferredLang;
|
||||
|
||||
uint32_t mDictionaryFetcherGroup;
|
||||
|
||||
bool mUpdateDictionaryRunning;
|
||||
|
||||
nsresult TryDictionary(const nsAString& aDictName, nsTArray<nsString>& aDictList,
|
||||
enum dictCompare aCompareType);
|
||||
|
||||
nsresult DictionaryFetched(DictionaryFetcher* aFetchState);
|
||||
|
||||
public:
|
||||
void BeginUpdateDictionary() { mUpdateDictionaryRunning = true ;}
|
||||
void EndUpdateDictionary() { mUpdateDictionaryRunning = false ;}
|
||||
};
|
||||
|
||||
#endif // nsEditorSpellCheck_h___
|
||||
|
||||
|
||||
104
editor/composer/nsIEditingSession.idl
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "domstubs.idl"
|
||||
|
||||
interface mozIDOMWindowProxy;
|
||||
interface nsIEditor;
|
||||
|
||||
[scriptable, uuid(24f963d1-e6fc-43ea-a206-99ac5fcc5265)]
|
||||
|
||||
interface nsIEditingSession : nsISupports
|
||||
{
|
||||
/**
|
||||
* Error codes when we fail to create an editor
|
||||
* is placed in attribute editorStatus
|
||||
*/
|
||||
const long eEditorOK = 0;
|
||||
const long eEditorCreationInProgress = 1;
|
||||
const long eEditorErrorCantEditMimeType = 2;
|
||||
const long eEditorErrorFileNotFound = 3;
|
||||
const long eEditorErrorCantEditFramesets = 8;
|
||||
const long eEditorErrorUnknown = 9;
|
||||
|
||||
/**
|
||||
* Status after editor creation and document loading
|
||||
* Value is one of the above error codes
|
||||
*/
|
||||
readonly attribute unsigned long editorStatus;
|
||||
|
||||
/**
|
||||
* Make this window editable
|
||||
* @param aWindow nsIDOMWindow, the window the embedder needs to make editable
|
||||
* @param aEditorType string, "html" "htmlsimple" "text" "textsimple"
|
||||
* @param aMakeWholeDocumentEditable if PR_TRUE make the whole document in
|
||||
* aWindow editable, otherwise it's the
|
||||
* embedder who should make the document
|
||||
* (or part of it) editable.
|
||||
* @param aInteractive if PR_FALSE turn off scripting and plugins
|
||||
*/
|
||||
void makeWindowEditable(in mozIDOMWindowProxy window,
|
||||
in string aEditorType,
|
||||
in boolean doAfterUriLoad,
|
||||
in boolean aMakeWholeDocumentEditable,
|
||||
in boolean aInteractive);
|
||||
|
||||
/**
|
||||
* Test whether a specific window has had its editable flag set; it may have an editor
|
||||
* now, or will get one after the uri load.
|
||||
*
|
||||
* Use this, passing the content root window, to test if we've set up editing
|
||||
* for this content.
|
||||
*/
|
||||
boolean windowIsEditable(in mozIDOMWindowProxy window);
|
||||
|
||||
/**
|
||||
* Get the editor for this window. May return null
|
||||
*/
|
||||
nsIEditor getEditorForWindow(in mozIDOMWindowProxy window);
|
||||
|
||||
/**
|
||||
* Setup editor and related support objects
|
||||
*/
|
||||
void setupEditorOnWindow(in mozIDOMWindowProxy window);
|
||||
|
||||
/**
|
||||
* Destroy editor and related support objects
|
||||
*/
|
||||
void tearDownEditorOnWindow(in mozIDOMWindowProxy window);
|
||||
|
||||
void setEditorOnControllers(in mozIDOMWindowProxy aWindow,
|
||||
in nsIEditor aEditor);
|
||||
|
||||
/**
|
||||
* Disable scripts and plugins in aWindow.
|
||||
*/
|
||||
void disableJSAndPlugins(in mozIDOMWindowProxy aWindow);
|
||||
|
||||
/**
|
||||
* Restore JS and plugins (enable/disable them) according to the state they
|
||||
* were before the last call to disableJSAndPlugins.
|
||||
*/
|
||||
void restoreJSAndPlugins(in mozIDOMWindowProxy aWindow);
|
||||
|
||||
/**
|
||||
* Removes all the editor's controllers/listeners etc and makes the window
|
||||
* uneditable.
|
||||
*/
|
||||
void detachFromWindow(in mozIDOMWindowProxy aWindow);
|
||||
|
||||
/**
|
||||
* Undos detachFromWindow(), reattaches this editing session/editor
|
||||
* to the window.
|
||||
*/
|
||||
void reattachToWindow(in mozIDOMWindowProxy aWindow);
|
||||
|
||||
/**
|
||||
* Whether this session has disabled JS and plugins.
|
||||
*/
|
||||
readonly attribute boolean jsAndPluginsDisabled;
|
||||
};
|
||||
|
||||
323
editor/composer/res/EditorOverride.css
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/* 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/. */
|
||||
|
||||
*|* {
|
||||
-moz-user-modify: read-write;
|
||||
}
|
||||
|
||||
/* Styles to alter look of things in the Editor content window
|
||||
* that should NOT be removed when we display in completely WYSIWYG
|
||||
* "Browser Preview" mode.
|
||||
* Anything that should change, like appearance of table borders
|
||||
* and Named Anchors, should be placed in EditorContent.css instead of here.
|
||||
*/
|
||||
|
||||
/* Primary cursor is text I-beam */
|
||||
|
||||
::-moz-canvas, a:link {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Use default arrow over objects with size that
|
||||
are selected when clicked on.
|
||||
Override the browser's pointer cursor over links
|
||||
*/
|
||||
|
||||
img, img[usemap], area,
|
||||
object, object[usemap],
|
||||
applet, hr, button, input, textarea, select,
|
||||
a:link img, a:visited img, a:active img,
|
||||
a[name]:-moz-only-whitespace {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
a:visited, a:active {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Prevent clicking on links from going to link */
|
||||
a:link img, a:visited img {
|
||||
-moz-user-input: none;
|
||||
}
|
||||
|
||||
/* We suppress user/author's prefs for link underline,
|
||||
so we must set explicitly. This isn't good!
|
||||
*/
|
||||
a:link {
|
||||
text-decoration: underline -moz-anchor-decoration;
|
||||
color: -moz-hyperlinktext;
|
||||
}
|
||||
|
||||
/* Allow double-clicks on these widgets to open properties dialogs
|
||||
XXX except when the widget has disabled attribute */
|
||||
input, button, textarea {
|
||||
-moz-user-select: all !important;
|
||||
-moz-user-input: auto !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
/* XXX Still need a better way of blocking other events to these widgets */
|
||||
select, input[disabled], input[type="checkbox"], input[type="radio"], input[type="file"] {
|
||||
-moz-user-select: all !important;
|
||||
-moz-user-input: none !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
input[type="hidden"] {
|
||||
border: 1px solid black !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
label {
|
||||
-moz-user-select: all !important;
|
||||
}
|
||||
|
||||
::-moz-display-comboboxcontrol-frame {
|
||||
-moz-user-select: text !important;
|
||||
}
|
||||
|
||||
option {
|
||||
-moz-user-select: text !important;
|
||||
}
|
||||
|
||||
#mozToc.readonly {
|
||||
-moz-user-select: all !important;
|
||||
-moz-user-input: none !important;
|
||||
}
|
||||
|
||||
/* the following rules are for Image Resizing */
|
||||
|
||||
span[\_moz_anonclass="mozResizer"] {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
position: absolute;
|
||||
border: 1px black solid;
|
||||
background-color: white;
|
||||
-moz-user-select: none;
|
||||
z-index: 2147483646; /* max value -1 for this property */
|
||||
}
|
||||
|
||||
/* we can't use :active below */
|
||||
span[\_moz_anonclass="mozResizer"][\_moz_activated],
|
||||
span[\_moz_anonclass="mozResizer"]:hover {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
span[\_moz_anonclass="mozResizer"].hidden,
|
||||
span[\_moz_anonclass="mozResizingShadow"].hidden,
|
||||
img[\_moz_anonclass="mozResizingShadow"].hidden,
|
||||
span[\_moz_anonclass="mozGrabber"].hidden,
|
||||
span[\_moz_anonclass="mozResizingInfo"].hidden,
|
||||
a[\_moz_anonclass="mozTableRemoveRow"].hidden,
|
||||
a[\_moz_anonclass="mozTableRemoveColumn"].hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="nw"] {
|
||||
cursor: nw-resize;
|
||||
}
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="n"] {
|
||||
cursor: n-resize;
|
||||
}
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="ne"] {
|
||||
cursor: ne-resize;
|
||||
}
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="w"] {
|
||||
cursor: w-resize;
|
||||
}
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="e"] {
|
||||
cursor: e-resize;
|
||||
}
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="sw"] {
|
||||
cursor: sw-resize;
|
||||
}
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="s"] {
|
||||
cursor: s-resize;
|
||||
}
|
||||
span[\_moz_anonclass="mozResizer"][anonlocation="se"] {
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
||||
span[\_moz_anonclass="mozResizingShadow"],
|
||||
img[\_moz_anonclass="mozResizingShadow"] {
|
||||
outline: thin dashed black;
|
||||
-moz-user-select: none;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
}
|
||||
|
||||
span[\_moz_anonclass="mozResizingInfo"] {
|
||||
font-family: sans-serif;
|
||||
font-size: x-small;
|
||||
color: black;
|
||||
background-color: #d0d0d0;
|
||||
border: ridge 2px #d0d0d0;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
}
|
||||
|
||||
img[\_moz_resizing] {
|
||||
outline: thin solid black;
|
||||
}
|
||||
|
||||
*[\_moz_abspos] {
|
||||
outline: silver ridge 2px;
|
||||
z-index: 2147483645 !important; /* max value -2 for this property */
|
||||
}
|
||||
*[\_moz_abspos="white"] {
|
||||
background-color: white !important;
|
||||
}
|
||||
*[\_moz_abspos="black"] {
|
||||
background-color: black !important;
|
||||
}
|
||||
|
||||
span[\_moz_anonclass="mozGrabber"] {
|
||||
outline: ridge 2px silver;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-image: url("resource://gre/res/grabber.gif");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-moz-user-select: none;
|
||||
cursor: move;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
}
|
||||
|
||||
/* INLINE TABLE EDITING */
|
||||
|
||||
a[\_moz_anonclass="mozTableAddColumnBefore"] {
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
text-decoration: none !important;
|
||||
border: none 0px !important;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
background-image: url("resource://gre/res/table-add-column-before.gif");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-moz-user-select: none !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddColumnBefore"]:hover {
|
||||
background-image: url("resource://gre/res/table-add-column-before-hover.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddColumnBefore"]:active {
|
||||
background-image: url("resource://gre/res/table-add-column-before-active.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddColumnAfter"] {
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
text-decoration: none !important;
|
||||
border: none 0px !important;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
background-image: url("resource://gre/res/table-add-column-after.gif");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-moz-user-select: none !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddColumnAfter"]:hover {
|
||||
background-image: url("resource://gre/res/table-add-column-after-hover.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddColumnAfter"]:active {
|
||||
background-image: url("resource://gre/res/table-add-column-after-active.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableRemoveColumn"] {
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
text-decoration: none !important;
|
||||
border: none 0px !important;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-image: url("resource://gre/res/table-remove-column.gif");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-moz-user-select: none !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableRemoveColumn"]:hover {
|
||||
background-image: url("resource://gre/res/table-remove-column-hover.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableRemoveColumn"]:active {
|
||||
background-image: url("resource://gre/res/table-remove-column-active.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddRowBefore"] {
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
text-decoration: none !important;
|
||||
border: none 0px !important;
|
||||
width: 8px;
|
||||
height: 4px;
|
||||
background-image: url("resource://gre/res/table-add-row-before.gif");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-moz-user-select: none !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddRowBefore"]:hover {
|
||||
background-image: url("resource://gre/res/table-add-row-before-hover.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddRowBefore"]:active {
|
||||
background-image: url("resource://gre/res/table-add-row-before-active.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddRowAfter"] {
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
text-decoration: none !important;
|
||||
border: none 0px !important;
|
||||
width: 8px;
|
||||
height: 4px;
|
||||
background-image: url("resource://gre/res/table-add-row-after.gif");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-moz-user-select: none !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddRowAfter"]:hover {
|
||||
background-image: url("resource://gre/res/table-add-row-after-hover.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableAddRowAfter"]:active {
|
||||
background-image: url("resource://gre/res/table-add-row-after-active.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableRemoveRow"] {
|
||||
position: absolute;
|
||||
z-index: 2147483647; /* max value for this property */
|
||||
text-decoration: none !important;
|
||||
border: none 0px !important;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-image: url("resource://gre/res/table-remove-row.gif");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
-moz-user-select: none !important;
|
||||
-moz-user-focus: none !important;
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableRemoveRow"]:hover {
|
||||
background-image: url("resource://gre/res/table-remove-row-hover.gif");
|
||||
}
|
||||
|
||||
a[\_moz_anonclass="mozTableRemoveRow"]:active {
|
||||
background-image: url("resource://gre/res/table-remove-row-active.gif");
|
||||
}
|
||||
BIN
editor/composer/res/grabber.gif
Normal file
|
After Width: | Height: | Size: 858 B |
BIN
editor/composer/res/table-add-column-after-active.gif
Normal file
|
After Width: | Height: | Size: 58 B |
BIN
editor/composer/res/table-add-column-after-hover.gif
Normal file
|
After Width: | Height: | Size: 826 B |
BIN
editor/composer/res/table-add-column-after.gif
Normal file
|
After Width: | Height: | Size: 826 B |
BIN
editor/composer/res/table-add-column-before-active.gif
Normal file
|
After Width: | Height: | Size: 57 B |
BIN
editor/composer/res/table-add-column-before-hover.gif
Normal file
|
After Width: | Height: | Size: 825 B |
BIN
editor/composer/res/table-add-column-before.gif
Normal file
|
After Width: | Height: | Size: 825 B |
BIN
editor/composer/res/table-add-row-after-active.gif
Normal file
|
After Width: | Height: | Size: 57 B |
BIN
editor/composer/res/table-add-row-after-hover.gif
Normal file
|
After Width: | Height: | Size: 826 B |
BIN
editor/composer/res/table-add-row-after.gif
Normal file
|
After Width: | Height: | Size: 826 B |
BIN
editor/composer/res/table-add-row-before-active.gif
Normal file
|
After Width: | Height: | Size: 57 B |
BIN
editor/composer/res/table-add-row-before-hover.gif
Normal file
|
After Width: | Height: | Size: 825 B |
BIN
editor/composer/res/table-add-row-before.gif
Normal file
|
After Width: | Height: | Size: 825 B |
BIN
editor/composer/res/table-remove-column-active.gif
Normal file
|
After Width: | Height: | Size: 835 B |
BIN
editor/composer/res/table-remove-column-hover.gif
Normal file
|
After Width: | Height: | Size: 841 B |
BIN
editor/composer/res/table-remove-column.gif
Normal file
|
After Width: | Height: | Size: 841 B |
BIN
editor/composer/res/table-remove-row-active.gif
Normal file
|
After Width: | Height: | Size: 835 B |
BIN
editor/composer/res/table-remove-row-hover.gif
Normal file
|
After Width: | Height: | Size: 841 B |
BIN
editor/composer/res/table-remove-row.gif
Normal file
|
After Width: | Height: | Size: 841 B |
14
editor/composer/test/bug1200533_subframe.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Language" content="en-US">
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="none">root en-US</textarea>
|
||||
<textarea id="en-GB" lang="en-GB">root en-US, but element en-GB</textarea>
|
||||
<textarea id="en-gb" lang="en-gb">root en-US, but element en-gb (lower case)</textarea>
|
||||
<textarea id="en-ZA-not-avail" lang="en-ZA">root en-US, but element en-ZA (which is not installed)</textarea>
|
||||
<textarea id="en-generic" lang="en">root en-US, but element en</textarea>
|
||||
<textarea id="ko-not-avail" lang="ko">root en-US, but element ko (which is not installed)</textarea>
|
||||
</body>
|
||||
</html>
|
||||
11
editor/composer/test/bug1204147_subframe.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="en-GB" lang="en-GB">element en-GB</textarea>
|
||||
<textarea id="en-US" lang="testing-XX">element should default to en-US</textarea>
|
||||
|
||||
<div id="trouble-maker" contenteditable>the presence of this div triggers the faulty code path</div>
|
||||
</body>
|
||||
</html>
|
||||
9
editor/composer/test/bug1204147_subframe2.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="en-GB" lang="en-GB">element en-GB</textarea>
|
||||
<textarea id="en-US" lang="testing-XX">element should default to en-US</textarea>
|
||||
</body>
|
||||
</html>
|
||||
8
editor/composer/test/bug678842_subframe.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="textarea" lang="testing-XXX"></textarea>
|
||||
</body>
|
||||
</html>
|
||||
8
editor/composer/test/bug717433_subframe.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="textarea" lang="en"></textarea>
|
||||
</body>
|
||||
</html>
|
||||
5
editor/composer/test/chrome.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[DEFAULT]
|
||||
skip-if = os == 'android'
|
||||
|
||||
[test_bug434998.xul]
|
||||
[test_bug1266815.html]
|
||||
2
editor/composer/test/de-DE/de_DE.aff
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Affix file for German English dictionary
|
||||
# Fake file, nothing here.
|
||||
6
editor/composer/test/de-DE/de_DE.dic
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
5
|
||||
ein
|
||||
guter
|
||||
heute
|
||||
ist
|
||||
Tag
|
||||
2
editor/composer/test/en-AU/en_AU.aff
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Affix file for British English dictionary
|
||||
# Fake file, nothing here.
|
||||
4
editor/composer/test/en-AU/en_AU.dic
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
3
|
||||
Mary
|
||||
Paul
|
||||
Peter
|
||||
2
editor/composer/test/en-GB/en_GB.aff
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Affix file for British English dictionary
|
||||
# Fake file, nothing here.
|
||||
4
editor/composer/test/en-GB/en_GB.dic
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
3
|
||||
Mary
|
||||
Paul
|
||||
Peter
|
||||
40
editor/composer/test/mochitest.ini
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
bug678842_subframe.html
|
||||
bug717433_subframe.html
|
||||
bug1200533_subframe.html
|
||||
bug1204147_subframe.html
|
||||
bug1204147_subframe2.html
|
||||
en-GB/en_GB.dic
|
||||
en-GB/en_GB.aff
|
||||
en-AU/en_AU.dic
|
||||
en-AU/en_AU.aff
|
||||
de-DE/de_DE.dic
|
||||
de-DE/de_DE.aff
|
||||
|
||||
[test_async_UpdateCurrentDictionary.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug338427.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug348497.html]
|
||||
[test_bug384147.html]
|
||||
[test_bug389350.html]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_bug519928.html]
|
||||
[test_bug678842.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug697981.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug717433.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug738440.html]
|
||||
[test_bug1200533.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug1204147.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug1205983.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug1209414.html]
|
||||
skip-if = os == 'android'
|
||||
[test_bug1219928.html]
|
||||
skip-if = e10s || os == 'android'
|
||||
75
editor/composer/test/test_async_UpdateCurrentDictionary.html
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=856270
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 856270 - Async UpdateCurrentDictionary</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=856270">Mozilla Bug 856270</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<textarea id="editor" spellcheck="true"></textarea>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript;version=1.8">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(start);
|
||||
|
||||
function start() {
|
||||
var textarea = document.getElementById("editor");
|
||||
textarea.focus();
|
||||
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck(textarea, function () {
|
||||
var isc = SpecialPowers.wrap(textarea).editor.getInlineSpellChecker(false);
|
||||
ok(isc, "Inline spell checker should exist after focus and spell check");
|
||||
var sc = isc.spellChecker;
|
||||
isnot(sc.GetCurrentDictionary(), lang,
|
||||
"Current dictionary should not be set yet.");
|
||||
|
||||
// First, set the lang attribute on the textarea, call Update, and make
|
||||
// sure the spell checker's language was updated appropriately.
|
||||
var lang = "en-US";
|
||||
textarea.setAttribute("lang", lang);
|
||||
sc.UpdateCurrentDictionary(function () {
|
||||
is(sc.GetCurrentDictionary(), lang,
|
||||
"UpdateCurrentDictionary should set the current dictionary.");
|
||||
|
||||
// Second, make some Update calls, but then do a Set. The Set should
|
||||
// effectively cancel the Updates, but the Updates' callbacks should be
|
||||
// called nonetheless.
|
||||
var numCalls = 3;
|
||||
for (var i = 0; i < numCalls; i++) {
|
||||
sc.UpdateCurrentDictionary(function () {
|
||||
is(sc.GetCurrentDictionary(), "",
|
||||
"No dictionary should be active after Update.");
|
||||
if (--numCalls == 0) {
|
||||
// This will clear the content preferences and reset "spellchecker.dictionary".
|
||||
sc.SetCurrentDictionary("");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
sc.SetCurrentDictionary("testing-XX");
|
||||
}
|
||||
catch (err) {
|
||||
// Set throws NS_ERROR_NOT_AVAILABLE because "testing-XX" isn't really
|
||||
// an available dictionary.
|
||||
}
|
||||
is(sc.GetCurrentDictionary(), "",
|
||||
"No dictionary should be active after Set.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
139
editor/composer/test/test_bug1200533.html
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1200533
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1200533</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1200533">Mozilla Bug 1200533</a>
|
||||
<p id="display"></p>
|
||||
<iframe id="content"></iframe>
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" ttype="application/javascript">
|
||||
|
||||
/** Test for Bug 1200533 **/
|
||||
/** Visit the elements defined above and check the dictionary we got **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var content = document.getElementById('content');
|
||||
|
||||
var tests = [
|
||||
// text area, value of spellchecker.dictionary, result.
|
||||
// Result: Document language.
|
||||
[ "none", "", "en-US" ],
|
||||
// Result: Element language.
|
||||
[ "en-GB", "", "en-GB" ],
|
||||
[ "en-gb", "", "en-GB" ],
|
||||
// Result: Random en-*.
|
||||
[ "en-ZA-not-avail", "", "*" ],
|
||||
[ "en-generic", "", "*" ],
|
||||
// Result: Locale.
|
||||
[ "ko-not-avail", "", "en-US" ],
|
||||
|
||||
// Result: Preference value in all cases.
|
||||
[ "en-ZA-not-avail", "en-AU", "en-AU" ],
|
||||
[ "en-generic", "en-AU", "en-AU" ],
|
||||
[ "ko-not-avail", "en-AU", "en-AU" ],
|
||||
|
||||
// Result: Random en-*.
|
||||
[ "en-ZA-not-avail", "de-DE", "*" ],
|
||||
[ "en-generic", "de-DE", "*" ],
|
||||
// Result: Preference value.
|
||||
[ "ko-not-avail", "de-DE", "de-DE" ],
|
||||
];
|
||||
|
||||
var loadCount = 0;
|
||||
var script;
|
||||
|
||||
var loadListener = function(evt) {
|
||||
if (loadCount == 0) {
|
||||
script = SpecialPowers.loadChromeScript(function() {
|
||||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties)
|
||||
.get("CurWorkD", Components.interfaces.nsIFile);
|
||||
dir.append("tests");
|
||||
dir.append("editor");
|
||||
dir.append("composer");
|
||||
dir.append("test");
|
||||
|
||||
var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Components.interfaces.mozISpellCheckingEngine);
|
||||
|
||||
// Install en-GB, en-AU and de-DE dictionaries.
|
||||
var en_GB = dir.clone();
|
||||
var en_AU = dir.clone();
|
||||
var de_DE = dir.clone();
|
||||
en_GB.append("en-GB");
|
||||
en_AU.append("en-AU");
|
||||
de_DE.append("de-DE");
|
||||
hunspell.addDirectory(en_GB);
|
||||
hunspell.addDirectory(en_AU);
|
||||
hunspell.addDirectory(de_DE);
|
||||
|
||||
addMessageListener("check-existence",
|
||||
() => [en_GB.exists(), en_AU.exists(),
|
||||
de_DE.exists()]);
|
||||
addMessageListener("destroy", () => {
|
||||
hunspell.removeDirectory(en_GB);
|
||||
hunspell.removeDirectory(en_AU);
|
||||
hunspell.removeDirectory(de_DE);
|
||||
});
|
||||
});
|
||||
var existenceChecks = script.sendSyncMessage("check-existence")[0][0];
|
||||
is(existenceChecks[0], true, "true expected (en-GB directory should exist)");
|
||||
is(existenceChecks[1], true, "true expected (en-AU directory should exist)");
|
||||
is(existenceChecks[2], true, "true expected (de-DE directory should exist)");
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({set: [["spellchecker.dictionary", tests[loadCount][1]]]},
|
||||
function() { continueTest(evt) });
|
||||
}
|
||||
|
||||
function continueTest(evt) {
|
||||
var doc = evt.target.contentDocument;
|
||||
var elem = doc.getElementById(tests[loadCount][0]);
|
||||
var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
|
||||
editor.setSpellcheckUserOverride(true);
|
||||
var inlineSpellChecker = editor.getInlineSpellChecker(true);
|
||||
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck(elem, function () {
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
var dict = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
if (tests[loadCount][2] != "*") {
|
||||
is (dict, tests[loadCount][2], "expected " + tests[loadCount][2]);
|
||||
} else {
|
||||
var gotEn = (dict == "en-GB" || dict == "en-AU" || dict == "en-US");
|
||||
is (gotEn, true, "expected en-AU or en-GB or en-US");
|
||||
}
|
||||
|
||||
loadCount++;
|
||||
if (loadCount < tests.length) {
|
||||
// Load the iframe again.
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1200533_subframe.html?firstload=false';
|
||||
} else {
|
||||
// Remove the fake dictionaries again, since it's otherwise picked up by later tests.
|
||||
script.sendSyncMessage("destroy");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
content.addEventListener('load', loadListener, false);
|
||||
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1200533_subframe.html?firstload=true';
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
112
editor/composer/test/test_bug1204147.html
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1204147
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1204147</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1204147">Mozilla Bug 1204147</a>
|
||||
<p id="display"></p>
|
||||
<iframe id="content"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 1204147 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var content = document.getElementById('content');
|
||||
// Load a subframe containing an editor with using "en-GB". At first
|
||||
// load, it will set the dictionary to "en-GB". The bug was that a content preference
|
||||
// was also created. At second load, we check the dictionary for another element,
|
||||
// one that should use "en-US". With the bug corrected, we get "en-US", before
|
||||
// we got "en-GB" from the content preference.
|
||||
|
||||
var firstLoad = true;
|
||||
var script;
|
||||
|
||||
var loadListener = function(evt) {
|
||||
if (firstLoad) {
|
||||
script = SpecialPowers.loadChromeScript(function() {
|
||||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties)
|
||||
.get("CurWorkD", Components.interfaces.nsIFile);
|
||||
dir.append("tests");
|
||||
dir.append("editor");
|
||||
dir.append("composer");
|
||||
dir.append("test");
|
||||
|
||||
var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Components.interfaces.mozISpellCheckingEngine);
|
||||
|
||||
// Install en-GB dictionary.
|
||||
en_GB = dir.clone();
|
||||
en_GB.append("en-GB");
|
||||
hunspell.addDirectory(en_GB);
|
||||
|
||||
addMessageListener("en_GB-exists", () => en_GB.exists());
|
||||
addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
|
||||
});
|
||||
is(script.sendSyncMessage("en_GB-exists")[0][0], true,
|
||||
"true expected (en-GB directory should exist)");
|
||||
}
|
||||
|
||||
var doc = evt.target.contentDocument;
|
||||
var elem;
|
||||
if (firstLoad) {
|
||||
elem = doc.getElementById('en-GB');
|
||||
} else {
|
||||
elem = doc.getElementById('en-US');
|
||||
}
|
||||
|
||||
var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
|
||||
editor.setSpellcheckUserOverride(true);
|
||||
var inlineSpellChecker = editor.getInlineSpellChecker(true);
|
||||
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck(elem, function () {
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
var currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
if (firstLoad) {
|
||||
firstLoad = false;
|
||||
|
||||
// First time around, the element's language should be used.
|
||||
is (currentDictonary, "en-GB", "unexpected lang " + currentDictonary + " instead of en-GB");
|
||||
|
||||
// Note that on second load, we load a different page, which does NOT have the trouble-causing
|
||||
// contenteditable in it. Sadly, loading the same page with the trouble-maker in it
|
||||
// doesn't allow the retrieval of the spell check dictionary used for the element,
|
||||
// because the trouble-maker causes the 'global' spell check dictionary to be set to "en-GB"
|
||||
// (since it picks the first one from the list) before we have the chance to retrieve
|
||||
// the dictionary for the element (which happens asynchonously after the spell check has completed).
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1204147_subframe2.html?firstload=false';
|
||||
} else {
|
||||
// Second time around, the element should default to en-US.
|
||||
// Without the fix, the first run sets the content preference to en-GB for the whole site.
|
||||
is (currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US");
|
||||
content.removeEventListener('load', loadListener, false);
|
||||
|
||||
// Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
|
||||
script.sendSyncMessage("destroy");
|
||||
|
||||
// Reset the preference, so the last value we set doesn't collide with the next test.
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
content.addEventListener('load', loadListener, false);
|
||||
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1204147_subframe.html?firstload=true';
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
137
editor/composer/test/test_bug1205983.html
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1205983
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1205983</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205983">Mozilla Bug 1205983</a>
|
||||
<p id="display"></p>
|
||||
</div>
|
||||
|
||||
<div contenteditable id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</div>
|
||||
<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
function getMisspelledWords(editor) {
|
||||
return editor.selectionController.getSelection(SpecialPowers.Ci.nsISelectionController.SELECTION_SPELLCHECK).toString();
|
||||
}
|
||||
|
||||
var elem_de;
|
||||
var editor_de;
|
||||
var selcon_de;
|
||||
var script;
|
||||
|
||||
var onSpellCheck =
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck;
|
||||
|
||||
/** Test for Bug 1205983 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
script = SpecialPowers.loadChromeScript(function() {
|
||||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties)
|
||||
.get("CurWorkD", Components.interfaces.nsIFile);
|
||||
dir.append("tests");
|
||||
dir.append("editor");
|
||||
dir.append("composer");
|
||||
dir.append("test");
|
||||
|
||||
var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Components.interfaces.mozISpellCheckingEngine);
|
||||
|
||||
// Install de-DE dictionary.
|
||||
var de_DE = dir.clone();
|
||||
de_DE.append("de-DE");
|
||||
hunspell.addDirectory(de_DE);
|
||||
|
||||
addMessageListener("de_DE-exists", () => de_DE.exists());
|
||||
addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
|
||||
});
|
||||
is(script.sendSyncMessage("de_DE-exists")[0][0], true,
|
||||
"true expected (de_DE directory should exist)");
|
||||
|
||||
document.getElementById('de-DE').focus();
|
||||
});
|
||||
|
||||
function deFocus() {
|
||||
elem_de = document.getElementById('de-DE');
|
||||
|
||||
onSpellCheck(elem_de, function () {
|
||||
var Ci = SpecialPowers.Ci;
|
||||
var editingSession = SpecialPowers.wrap(window)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
editor_de = editingSession.getEditorForWindow(window);
|
||||
selcon_de = editor_de.selectionController;
|
||||
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
|
||||
|
||||
// Check that we spelled in German, so there is only one misspelled word.
|
||||
is(sel.toString(), "German", "one misspelled word expected: German");
|
||||
|
||||
// Now focus the textarea, which requires English spelling.
|
||||
document.getElementById('en-US').focus();
|
||||
});
|
||||
}
|
||||
|
||||
function enFocus() {
|
||||
var elem_en = document.getElementById('en-US');
|
||||
var editor_en =
|
||||
SpecialPowers.wrap(elem_en)
|
||||
.QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement)
|
||||
.editor;
|
||||
editor_en.setSpellcheckUserOverride(true);
|
||||
var inlineSpellChecker = editor_en.getInlineSpellChecker(true);
|
||||
|
||||
onSpellCheck(elem_en, function () {
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
// Check that the English dictionary is loaded and that the spell check has worked.
|
||||
is(currentDictonary, "en-US", "expected en-US");
|
||||
is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
|
||||
|
||||
// So far all was boring. The important thing is whether the spell check result
|
||||
// in the de-DE editor is still the same. After losing focus, no spell check
|
||||
// updates should take place there.
|
||||
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
|
||||
is(sel.toString(), "German", "one misspelled word expected: German");
|
||||
|
||||
// Remove the fake de_DE dictionary again.
|
||||
script.sendSyncMessage("destroy");
|
||||
|
||||
// Focus again, so the spelling gets updated, but before we need to kill the focus handler.
|
||||
elem_de.onfocus = null;
|
||||
elem_de.blur();
|
||||
elem_de.focus();
|
||||
|
||||
// After removal, the de_DE editor should refresh the spelling with en-US.
|
||||
onSpellCheck(elem_de, function () {
|
||||
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
|
||||
is(sel.toString(), "heute" + "ist" + "ein" + "guter",
|
||||
"some misspelled words expected: heute ist ein guter");
|
||||
|
||||
// If we don't reset this, we cause massive leaks.
|
||||
selcon_de = null;
|
||||
editor_de = null;
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
150
editor/composer/test/test_bug1209414.html
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1209414
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1209414</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1209414">Mozilla Bug 1209414</a>
|
||||
<p id="display"></p>
|
||||
</div>
|
||||
|
||||
<textarea id="de-DE" lang="de-DE">heute ist ein guter Tag - today is a good day</textarea>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
const Ci = SpecialPowers.Ci;
|
||||
|
||||
function getMisspelledWords(editor) {
|
||||
return editor.selectionController.getSelection(Ci.nsISelectionController.SELECTION_SPELLCHECK).toString();
|
||||
}
|
||||
|
||||
var elem_de;
|
||||
var editor_de;
|
||||
var script;
|
||||
|
||||
/** Test for Bug 1209414 **/
|
||||
/*
|
||||
* All we want to do in this test is change the spelling using a right-click and selection from the menu.
|
||||
* This is necessary since all the other tests use SetCurrentDictionary() which doesn't reflect
|
||||
* user behaviour.
|
||||
*/
|
||||
|
||||
var onSpellCheck =
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck;
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
script = SpecialPowers.loadChromeScript(function() {
|
||||
const Ci = Components.interfaces;
|
||||
var chromeWin = browserElement.ownerDocument.defaultView
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow);
|
||||
var contextMenu = chromeWin.document.getElementById("contentAreaContextMenu");
|
||||
contextMenu.addEventListener("popupshown",
|
||||
() => sendAsyncMessage("popupshown"), false);
|
||||
|
||||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties)
|
||||
.get("CurWorkD", Ci.nsIFile);
|
||||
dir.append("tests");
|
||||
dir.append("editor");
|
||||
dir.append("composer");
|
||||
dir.append("test");
|
||||
|
||||
var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Ci.mozISpellCheckingEngine);
|
||||
|
||||
// Install de-DE dictionary.
|
||||
de_DE = dir.clone();
|
||||
de_DE.append("de-DE");
|
||||
hunspell.addDirectory(de_DE);
|
||||
|
||||
addMessageListener("hidepopup", function() {
|
||||
var state = contextMenu.state;
|
||||
|
||||
// Select Language from the menu. Take a look at
|
||||
// toolkit/modules/InlineSpellChecker.jsm to see how the menu works.
|
||||
|
||||
contextMenu.ownerDocument.getElementById("spell-check-dictionary-en-US")
|
||||
.doCommand();
|
||||
contextMenu.hidePopup();
|
||||
|
||||
return state;
|
||||
});
|
||||
addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
|
||||
addMessageListener("contextMenu-not-null", () => contextMenu != null);
|
||||
addMessageListener("de_DE-exists", () => de_DE.exists());
|
||||
});
|
||||
is(script.sendSyncMessage("contextMenu-not-null")[0][0], true,
|
||||
"Got context menu XUL");
|
||||
is(script.sendSyncMessage("de_DE-exists")[0][0], true,
|
||||
"true expected (de_DE directory should exist)");
|
||||
script.addMessageListener("popupshown", handlePopup);
|
||||
|
||||
elem_de = document.getElementById('de-DE');
|
||||
editor_de = SpecialPowers.wrap(elem_de)
|
||||
.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
|
||||
editor_de.setSpellcheckUserOverride(true);
|
||||
|
||||
onSpellCheck(elem_de, function () {
|
||||
var inlineSpellChecker = editor_de.getInlineSpellChecker(true);
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
var currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
// Check that the German dictionary is loaded and that the spell check has worked.
|
||||
is(currentDictonary, "de-DE", "expected de-DE");
|
||||
is(getMisspelledWords(editor_de), "today" + "is" + "a" + "good" + "day", "some misspelled words expected: today is a good day");
|
||||
|
||||
// Focus again, just to be sure that the context-click won't trigger another spell check.
|
||||
elem_de.focus();
|
||||
|
||||
// Make sure all spell checking action is done before right-click to select the en-US dictionary.
|
||||
onSpellCheck(elem_de, function () {
|
||||
synthesizeMouse(elem_de, 2, 2, { type : "contextmenu", button : 2 }, window);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function handlePopup() {
|
||||
var state = script.sendSyncMessage("hidepopup")[0][0];
|
||||
is(state, "open", "checking if popup is open");
|
||||
|
||||
onSpellCheck(elem_de, function () {
|
||||
var inlineSpellChecker = editor_de.getInlineSpellChecker(true);
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
// Check that the English dictionary is loaded and that the spell check has worked.
|
||||
is(currentDictonary, "en-US", "expected en-US");
|
||||
is(getMisspelledWords(editor_de), "heute" + "ist" + "ein" + "guter", "some misspelled words expected: heute ist ein guter");
|
||||
|
||||
// Remove the fake de_DE dictionary again.
|
||||
script.sendSyncMessage("destroy");
|
||||
|
||||
// This will clear the content preferences and reset "spellchecker.dictionary".
|
||||
spellchecker.SetCurrentDictionary("");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
76
editor/composer/test/test_bug1219928.html
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1219928
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1219928</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1219928">Mozilla Bug 1219928</a>
|
||||
<p id="display"></p>
|
||||
|
||||
<div contenteditable id="en-US" lang="en-US">
|
||||
<p>And here a missspelled word</p>
|
||||
<style>
|
||||
<!-- and here another onnee in a style comment -->
|
||||
</style>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 1219928 **/
|
||||
/* Very simple test to check that <style> blocks are skipped in the spell check */
|
||||
|
||||
var spellchecker;
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm",
|
||||
window);
|
||||
|
||||
var elem = document.getElementById('en-US');
|
||||
elem.focus();
|
||||
|
||||
onSpellCheck(elem, function () {
|
||||
var Ci = SpecialPowers.Ci;
|
||||
var editingSession = SpecialPowers.wrap(window)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
var editor = editingSession.getEditorForWindow(window);
|
||||
var selcon = editor.selectionController;
|
||||
var sel = selcon.getSelection(selcon.SELECTION_SPELLCHECK);
|
||||
|
||||
is(sel.toString(), "missspelled", "one misspelled word expected: missspelled");
|
||||
|
||||
spellchecker = SpecialPowers.Cc['@mozilla.org/editor/editorspellchecker;1']
|
||||
.createInstance(Ci.nsIEditorSpellCheck);
|
||||
var filterContractId = "@mozilla.org/editor/txtsrvfilter;1";
|
||||
spellchecker.setFilter(SpecialPowers.Cc[filterContractId]
|
||||
.createInstance(Ci.nsITextServicesFilter));
|
||||
spellchecker.InitSpellChecker(editor, false, spellCheckStarted);
|
||||
});
|
||||
});
|
||||
|
||||
function spellCheckStarted() {
|
||||
var misspelledWord = spellchecker.GetNextMisspelledWord();
|
||||
is(misspelledWord, "missspelled", "first misspelled word expected: missspelled");
|
||||
|
||||
// Without the fix, the next misspelled word was 'onnee', so we check that we don't get it.
|
||||
misspelledWord = spellchecker.GetNextMisspelledWord();
|
||||
isnot(misspelledWord, "onnee", "second misspelled word should not be: onnee");
|
||||
|
||||
spellchecker = "";
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
82
editor/composer/test/test_bug1266815.html
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<script type="text/javascript">
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
const Cu = SpecialPowers.Cu;
|
||||
|
||||
const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
|
||||
const HELPERAPP_DIALOG_CID =
|
||||
SpecialPowers.wrap(SpecialPowers.Components)
|
||||
.ID(Cc["@mozilla.org/helperapplauncherdialog;1"].number);
|
||||
const HELPERAPP_DIALOG_CONTRACT_ID = "@mozilla.org/helperapplauncherdialog;1";
|
||||
const MOCK_HELPERAPP_DIALOG_CID =
|
||||
SpecialPowers.wrap(SpecialPowers.Components)
|
||||
.ID("{391832c8-5232-4676-b838-cc8ad373f3d8}");
|
||||
|
||||
var registrar = SpecialPowers.wrap(Components).manager
|
||||
.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
|
||||
var helperAppDlgPromise = new Promise(function(resolve) {
|
||||
var mockHelperAppService;
|
||||
|
||||
function HelperAppLauncherDialog() {
|
||||
}
|
||||
|
||||
HelperAppLauncherDialog.prototype = {
|
||||
show: function(aLauncher, aWindowContext, aReason) {
|
||||
ok(true, "Whether showing Dialog");
|
||||
resolve();
|
||||
registrar.unregisterFactory(MOCK_HELPERAPP_DIALOG_CID,
|
||||
mockHelperAppService);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog])
|
||||
};
|
||||
|
||||
mockHelperAppService = XPCOMUtils._getFactory(HelperAppLauncherDialog);
|
||||
registrar.registerFactory(MOCK_HELPERAPP_DIALOG_CID, "",
|
||||
HELPERAPP_DIALOG_CONTRACT_ID,
|
||||
mockHelperAppService);
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
let promise = new Promise(function(resolve) {
|
||||
let iframe = document.createElement("iframe");
|
||||
iframe.onload = function() {
|
||||
is(iframe.contentDocument.getElementById("edit").innerText, "abc",
|
||||
"load iframe source");
|
||||
resolve();
|
||||
};
|
||||
iframe.id = "testframe";
|
||||
iframe.src = "data:text/html,<div id=edit contenteditable=true>abc</div>";
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
|
||||
yield promise;
|
||||
|
||||
let iframe = document.getElementById("testframe");
|
||||
let docShell = SpecialPowers.wrap(iframe.contentWindow)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
||||
ok(docShell.hasEditingSession, "Should have editing session");
|
||||
|
||||
document.getElementById("testframe").src =
|
||||
"data:application/octet-stream,TESTCONTENT";
|
||||
|
||||
yield helperAppDlgPromise;
|
||||
|
||||
ok(docShell.hasEditingSession, "Should have editing session");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
61
editor/composer/test/test_bug338427.html
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=338427
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 338427</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=338427">Mozilla Bug 338427</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<textarea id="editor" lang="testing-XX" spellcheck="true"></textarea>
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 338427 **/
|
||||
function init() {
|
||||
var onSpellCheck =
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck;
|
||||
var textarea = document.getElementById("editor");
|
||||
var editor = SpecialPowers.wrap(textarea).editor;
|
||||
var spellchecker = editor.getInlineSpellChecker(true);
|
||||
spellchecker.enableRealTimeSpell = true;
|
||||
textarea.focus();
|
||||
|
||||
onSpellCheck(textarea, function () {
|
||||
var list = {}, count = {};
|
||||
spellchecker.spellChecker.GetDictionaryList(list, count);
|
||||
ok(count.value > 0, "At least one dictionary should be present");
|
||||
|
||||
var lang = list.value[0];
|
||||
spellchecker.spellChecker.SetCurrentDictionary(lang);
|
||||
|
||||
onSpellCheck(textarea, function () {
|
||||
try {
|
||||
var dictionary =
|
||||
spellchecker.spellChecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
is(dictionary, lang, "Unexpected spell check dictionary");
|
||||
|
||||
// This will clear the content preferences and reset "spellchecker.dictionary".
|
||||
spellchecker.spellChecker.SetCurrentDictionary("");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(init);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
36
editor/composer/test/test_bug348497.html
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=348497
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 348497</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=348497">Mozilla Bug 348497</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
This page should not crash Mozilla<br>
|
||||
<iframe id="testIframe"></iframe>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 348497 **/
|
||||
function doe() {
|
||||
document.getElementById('testIframe').style.display = 'block';
|
||||
document.getElementById('testIframe').contentDocument.designMode = 'on';
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(doe);
|
||||
addLoadEvent(function() { ok(true, "enabling designmode on an iframe onload does not crash Mozilla")});
|
||||
addLoadEvent(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
204
editor/composer/test/test_bug384147.html
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=384147
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 384147</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384147">Mozilla Bug 384147</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: block">
|
||||
<div contentEditable id="editor"></div>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
/** Test for Bug 384147 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var editor = document.getElementById("editor");
|
||||
|
||||
editor.innerHTML = "<ol><li>Item 1</li><li>Item 2</li><ol><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>";
|
||||
editor.focus();
|
||||
|
||||
// If executed directly, a race condition exists that will cause execCommand
|
||||
// to fail occasionally (but often). Defer test execution to page load.
|
||||
addLoadEvent(function() {
|
||||
|
||||
var sel = window.getSelection();
|
||||
|
||||
// Test the effect that the tab key has on list items. Each test is
|
||||
// documented with the initial state of the list on the left, and the
|
||||
// expected state of the list on the right. {\t} indicates the list item
|
||||
// that will be indented. {\st} indicates that a shift-tab will be simulated
|
||||
// on that list item, outdenting it.
|
||||
//
|
||||
// Note: any test failing will likely result in all following tests failing
|
||||
// as well, since each test depends on the document being in a given state.
|
||||
// Unfortunately, due to the problems getting document focus and key events
|
||||
// to fire consistently, it's difficult to reset state between tests.
|
||||
// If there are test failures here, only debug the first test failure.
|
||||
|
||||
// *** test 1 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 2. {\t}Item 2 1. Item 2
|
||||
// 1. Item 3 2. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
sel.selectAllChildren(editor.getElementsByTagName("li")[1]);
|
||||
document.execCommand("indent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 1");
|
||||
|
||||
// *** test 2 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. Item 2 1. Item 2
|
||||
// 2. {\t}Item 3 1. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
sel.selectAllChildren(editor.getElementsByTagName("li")[2]);
|
||||
document.execCommand("indent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><ol><li>Item 3</li></ol></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 2");
|
||||
|
||||
// *** test 3 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. Item 2 1. Item 2
|
||||
// 1. {\st}Item 3 2. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
document.execCommand("outdent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 3");
|
||||
|
||||
// *** test 4 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. Item 2 1. Item 2
|
||||
// 2. {\st}Item 3 2. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
document.execCommand("outdent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 4");
|
||||
|
||||
// *** test 5 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. {\st}Item 2 2. Item 2
|
||||
// 2. Item 3 3. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
sel.selectAllChildren(editor.getElementsByTagName("li")[1]);
|
||||
document.execCommand("outdent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 5");
|
||||
|
||||
// *** test 6 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 2. {\t}Item 2 1. Item 2
|
||||
// 3. Item 3 2. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
document.execCommand("indent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 6");
|
||||
|
||||
// *** test 7 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. Item 2 1. Item 2
|
||||
// 2. {\t}Item 3 2. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
sel.selectAllChildren(editor.getElementsByTagName("li")[2]);
|
||||
document.execCommand("indent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 7");
|
||||
|
||||
// That covers the basics of merging lists on indent and outdent.
|
||||
// We also want to check that ul / ol lists won't be merged together,
|
||||
// since they're different types of lists.
|
||||
// *** test 8 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. Item 2 1. Item 2
|
||||
// 2. Item 3 2. Item 3
|
||||
// * {\t}Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
sel.selectAllChildren(editor.getElementsByTagName("li")[3]);
|
||||
document.execCommand("indent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li></ul><li>Item 5</li></ul>",
|
||||
"html output doesn't match expected value in test 8");
|
||||
|
||||
// Better test merging with <ul> rather than <ol> too.
|
||||
// *** test 9 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. Item 2 1. Item 2
|
||||
// 2. Item 3 2. Item 3
|
||||
// * Item 4 * Item 4
|
||||
// * {\t}Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
sel.selectAllChildren(editor.getElementsByTagName("li")[4]);
|
||||
document.execCommand("indent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>",
|
||||
"html output doesn't match expected value in test 9");
|
||||
|
||||
// Same test as test 8, but with outdent rather than indent.
|
||||
// *** test 10 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. Item 2 1. Item 2
|
||||
// 2. Item 3 2. Item 3
|
||||
// * {\st}Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
sel.selectAllChildren(editor.getElementsByTagName("li")[3]);
|
||||
document.execCommand("outdent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>",
|
||||
"html output doesn't match expected value in test 10");
|
||||
|
||||
// Test indenting multiple items at once. Hold down "shift" and select
|
||||
// upwards to get all the <ol> items and the first <ul> item.
|
||||
// *** test 11 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. {\t}Item 2 1. Item 2
|
||||
// 2. {\t}Item 3 2. Item 3
|
||||
// * {\t}Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
sel.removeAllRanges();
|
||||
var range = document.createRange();
|
||||
range.setStart(editor.getElementsByTagName("li")[1], 0);
|
||||
range.setEnd(editor.getElementsByTagName("li")[3], editor.getElementsByTagName("li")[3].childNodes.length);
|
||||
sel.addRange(range);
|
||||
document.execCommand("indent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><ol><li>Item 2</li><li>Item 3</li></ol></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>",
|
||||
"html output doesn't match expected value in test 11");
|
||||
|
||||
// Test outdenting multiple items at once. Selection is already ready...
|
||||
// *** test 12 ***
|
||||
// 1. Item 1 1. Item 1
|
||||
// 1. {\st}Item 2 1. Item 2
|
||||
// 2. {\st}Item 3 2. Item 3
|
||||
// * {\st}Item 4 * Item 4
|
||||
// * Item 5 * Item 5
|
||||
document.execCommand("outdent", false, null);
|
||||
ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>",
|
||||
"html output doesn't match expected value in test 12");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
33
editor/composer/test/test_bug389350.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=389350
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 389350</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function runTest() {
|
||||
var e = document.getElementById("edit");
|
||||
e.contentDocument.designMode='on';
|
||||
e.style.display='block';
|
||||
e.focus();
|
||||
sendString('abc');
|
||||
var expected = "<head></head><body>abc</body>";
|
||||
var result = e.contentDocument.documentElement.innerHTML;
|
||||
is(result, expected, "iframe with designmode on had incorrect content");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(runTest);
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body id="body">
|
||||
<iframe id="edit" width="200" height="100" style="display: none;" src="">
|
||||
</body>
|
||||
</html>
|
||||
109
editor/composer/test/test_bug434998.xul
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin"
|
||||
type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=434998
|
||||
-->
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Mozilla Bug 434998" onload="runTest();">
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
|
||||
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=434998"
|
||||
target="_blank">Mozilla Bug 434998</a>
|
||||
<p/>
|
||||
<editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
id="editor"
|
||||
type="content-primary"
|
||||
editortype="html"
|
||||
style="width: 400px; height: 100px; border: thin solid black"/>
|
||||
<p/>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
<script class="testbody" type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function EditorContentListener(aEditor)
|
||||
{
|
||||
this.init(aEditor);
|
||||
}
|
||||
|
||||
EditorContentListener.prototype = {
|
||||
init : function(aEditor)
|
||||
{
|
||||
this.mEditor = aEditor;
|
||||
},
|
||||
|
||||
QueryInterface : function(aIID)
|
||||
{
|
||||
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
|
||||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
|
||||
aIID.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_NOINTERFACE;
|
||||
},
|
||||
|
||||
onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
|
||||
{
|
||||
if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
|
||||
{
|
||||
var editor = this.mEditor.getEditor(this.mEditor.contentWindow);
|
||||
if (editor) {
|
||||
// Should not throw
|
||||
var threw = false;
|
||||
try {
|
||||
this.mEditor.contentDocument.execCommand("bold", false, null);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
ok(!threw, "The execCommand API should work on <xul:editor>");
|
||||
progress.removeProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onProgressChange : function(aWebProgress, aRequest,
|
||||
aCurSelfProgress, aMaxSelfProgress,
|
||||
aCurTotalProgress, aMaxTotalProgress)
|
||||
{
|
||||
},
|
||||
|
||||
onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
|
||||
{
|
||||
},
|
||||
|
||||
onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
|
||||
{
|
||||
},
|
||||
|
||||
onSecurityChange : function(aWebProgress, aRequest, aState)
|
||||
{
|
||||
},
|
||||
|
||||
mEditor: null
|
||||
};
|
||||
|
||||
var progress, progressListener;
|
||||
|
||||
function runTest() {
|
||||
var newEditorElement = document.getElementById("editor");
|
||||
newEditorElement.makeEditable("html", true);
|
||||
var docShell = newEditorElement.boxObject.docShell;
|
||||
progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);
|
||||
progressListener = new EditorContentListener(newEditorElement);
|
||||
progress.addProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||||
newEditorElement.setAttribute("src", "data:text/html,");
|
||||
}
|
||||
]]>
|
||||
</script>
|
||||
</window>
|
||||
123
editor/composer/test/test_bug519928.html
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=519928
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 519928</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=519928">Mozilla Bug 519928</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<iframe id="load-frame"></iframe>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var iframe = document.getElementById("load-frame");
|
||||
|
||||
function enableJS() { allowJS(true, iframe); }
|
||||
function disableJS() { allowJS(false, iframe); }
|
||||
function allowJS(allow, frame) {
|
||||
SpecialPowers.wrap(frame.contentWindow)
|
||||
.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
|
||||
.getInterface(SpecialPowers.Ci.nsIWebNavigation)
|
||||
.QueryInterface(SpecialPowers.Ci.nsIDocShell)
|
||||
.allowJavascript = allow;
|
||||
}
|
||||
|
||||
function expectJSAllowed(allowed, testCondition, callback) {
|
||||
window.ICanRunMyJS = false;
|
||||
var self_ = window;
|
||||
testCondition();
|
||||
|
||||
var doc = iframe.contentDocument;
|
||||
doc.body.innerHTML = "<iframe></iframe>";
|
||||
var innerFrame = doc.querySelector("iframe");
|
||||
innerFrame.addEventListener("load", function() {
|
||||
innerFrame.removeEventListener("load", arguments.callee, false);
|
||||
|
||||
var msg = "The inner iframe should" + (allowed ? "" : " not") + " be able to run Javascript";
|
||||
is(self_.ICanRunMyJS, allowed, msg);
|
||||
callback();
|
||||
}, false);
|
||||
var iframeSrc = "data:text/html,<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>";
|
||||
innerFrame.src = iframeSrc;
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
var enterDesignMode = function() { document.designMode = "on"; };
|
||||
var leaveDesignMode = function() { document.designMode = "off"; };
|
||||
expectJSAllowed(false, disableJS, function() {
|
||||
expectJSAllowed(true, enableJS, function() {
|
||||
expectJSAllowed(true, enterDesignMode, function() {
|
||||
expectJSAllowed(true, leaveDesignMode, function() {
|
||||
expectJSAllowed(false, disableJS, function() {
|
||||
expectJSAllowed(false, enterDesignMode, function() {
|
||||
expectJSAllowed(false, leaveDesignMode, function() {
|
||||
expectJSAllowed(true, enableJS, function() {
|
||||
enterDesignMode = function() { iframe.contentDocument.designMode = "on"; };
|
||||
leaveDesignMode = function() { iframe.contentDocument.designMode = "off"; };
|
||||
expectJSAllowed(false, disableJS, function() {
|
||||
expectJSAllowed(true, enableJS, function() {
|
||||
expectJSAllowed(true, enterDesignMode, function() {
|
||||
expectJSAllowed(true, leaveDesignMode, function() {
|
||||
expectJSAllowed(false, disableJS, function() {
|
||||
expectJSAllowed(false, enterDesignMode, function() {
|
||||
expectJSAllowed(false, leaveDesignMode, function() {
|
||||
expectJSAllowed(true, enableJS, function() {
|
||||
testDocumentDisabledJS();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testDocumentDisabledJS() {
|
||||
window.ICanRunMyJS = false;
|
||||
var self_ = window;
|
||||
// Ensure design modes are disabled
|
||||
document.designMode = "off";
|
||||
iframe.contentDocument.designMode = "off";
|
||||
|
||||
// Javascript enabled on the main iframe
|
||||
enableJS();
|
||||
|
||||
var doc = iframe.contentDocument;
|
||||
doc.body.innerHTML = "<iframe></iframe>";
|
||||
var innerFrame = doc.querySelector("iframe");
|
||||
|
||||
// Javascript disabled on the innerFrame.
|
||||
allowJS(false, innerFrame);
|
||||
|
||||
innerFrame.addEventListener("load", function() {
|
||||
innerFrame.removeEventListener("load", arguments.callee, false);
|
||||
|
||||
var msg = "The inner iframe should not be able to run Javascript";
|
||||
is(self_.ICanRunMyJS, false, msg);
|
||||
SimpleTest.finish();
|
||||
}, false);
|
||||
var iframeSrc = "data:text/html,<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>";
|
||||
innerFrame.src = iframeSrc;
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
105
editor/composer/test/test_bug678842.html
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=678842
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 678842</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=678842">Mozilla Bug 678842</a>
|
||||
<p id="display"></p>
|
||||
<iframe id="content"></iframe>
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 678842 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var content = document.getElementById('content');
|
||||
// load a subframe containing an editor with a defined unknown lang. At first
|
||||
// load, it will set dictionary to en-US. At second load, it will return current
|
||||
// dictionary. So, we can check, dictionary is correctly remembered between
|
||||
// loads.
|
||||
|
||||
var firstLoad = true;
|
||||
var script;
|
||||
|
||||
var loadListener = function(evt) {
|
||||
if (firstLoad) {
|
||||
script = SpecialPowers.loadChromeScript(function() {
|
||||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties)
|
||||
.get("CurWorkD", Components.interfaces.nsIFile);
|
||||
dir.append("tests");
|
||||
dir.append("editor");
|
||||
dir.append("composer");
|
||||
dir.append("test");
|
||||
|
||||
var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Components.interfaces.mozISpellCheckingEngine);
|
||||
|
||||
// Install en-GB dictionary.
|
||||
en_GB = dir.clone();
|
||||
en_GB.append("en-GB");
|
||||
hunspell.addDirectory(en_GB);
|
||||
|
||||
addMessageListener("en_GB-exists", () => en_GB.exists());
|
||||
addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
|
||||
});
|
||||
is(script.sendSyncMessage("en_GB-exists")[0][0], true,
|
||||
"true expected (en-GB directory should exist)");
|
||||
}
|
||||
|
||||
var doc = evt.target.contentDocument;
|
||||
var elem = doc.getElementById('textarea');
|
||||
var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
|
||||
editor.setSpellcheckUserOverride(true);
|
||||
var inlineSpellChecker = editor.getInlineSpellChecker(true);
|
||||
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck(elem, function () {
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
var currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
if (!currentDictonary) {
|
||||
spellchecker.SetCurrentDictionary('en-US');
|
||||
}
|
||||
|
||||
if (firstLoad) {
|
||||
firstLoad = false;
|
||||
|
||||
// First time around, the dictionary defaults to the locale.
|
||||
is (currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US");
|
||||
|
||||
// Select en-GB.
|
||||
spellchecker.SetCurrentDictionary("en-GB");
|
||||
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=false';
|
||||
} else {
|
||||
is (currentDictonary, "en-GB", "unexpected lang " + currentDictonary + " instead of en-GB");
|
||||
content.removeEventListener('load', loadListener, false);
|
||||
|
||||
// Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
|
||||
script.sendSyncMessage("destroy");
|
||||
|
||||
// This will clear the content preferences and reset "spellchecker.dictionary".
|
||||
spellchecker.SetCurrentDictionary("");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
content.addEventListener('load', loadListener, false);
|
||||
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=true';
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
133
editor/composer/test/test_bug697981.html
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=697981
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 697981</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=697981">Mozilla Bug 697981</a>
|
||||
<p id="display"></p>
|
||||
</div>
|
||||
|
||||
<textarea id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</textarea>
|
||||
<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
function getMisspelledWords(editor) {
|
||||
return editor.selectionController.getSelection(SpecialPowers.Ci.nsISelectionController.SELECTION_SPELLCHECK).toString();
|
||||
}
|
||||
|
||||
var elem_de;
|
||||
var editor_de;
|
||||
var script;
|
||||
|
||||
var onSpellCheck =
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck;
|
||||
|
||||
/** Test for Bug 697981 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
script = SpecialPowers.loadChromeScript(function() {
|
||||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties)
|
||||
.get("CurWorkD", Components.interfaces.nsIFile);
|
||||
dir.append("tests");
|
||||
dir.append("editor");
|
||||
dir.append("composer");
|
||||
dir.append("test");
|
||||
|
||||
var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Components.interfaces.mozISpellCheckingEngine);
|
||||
|
||||
// Install de-DE dictionary.
|
||||
var de_DE = dir.clone();
|
||||
de_DE.append("de-DE");
|
||||
hunspell.addDirectory(de_DE);
|
||||
|
||||
addMessageListener("de_DE-exists", () => de_DE.exists());
|
||||
addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
|
||||
});
|
||||
is(script.sendSyncMessage("de_DE-exists")[0][0], true,
|
||||
"true expected (de_DE directory should exist)");
|
||||
|
||||
document.getElementById('de-DE').focus();
|
||||
});
|
||||
|
||||
function deFocus() {
|
||||
elem_de = document.getElementById('de-DE');
|
||||
editor_de = SpecialPowers.wrap(elem_de).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
|
||||
editor_de.setSpellcheckUserOverride(true);
|
||||
var inlineSpellChecker = editor_de.getInlineSpellChecker(true);
|
||||
|
||||
onSpellCheck(elem_de, function () {
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
var currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
// Check that the German dictionary is loaded and that the spell check has worked.
|
||||
is(currentDictonary, "de-DE", "expected de-DE");
|
||||
is(getMisspelledWords(editor_de), "German", "one misspelled word expected: German");
|
||||
|
||||
// Now focus the other textarea, which requires English spelling.
|
||||
document.getElementById('en-US').focus();
|
||||
});
|
||||
}
|
||||
|
||||
function enFocus() {
|
||||
var elem_en = document.getElementById('en-US');
|
||||
var editor_en = SpecialPowers.wrap(elem_en).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
|
||||
editor_en.setSpellcheckUserOverride(true);
|
||||
var inlineSpellChecker = editor_en.getInlineSpellChecker(true);
|
||||
|
||||
onSpellCheck(elem_en, function () {
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
// Check that the English dictionary is loaded and that the spell check has worked.
|
||||
is(currentDictonary, "en-US", "expected en-US");
|
||||
is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
|
||||
|
||||
// So far all was boring. The important thing is whether the spell check result
|
||||
// in the de-DE editor is still the same. After losing focus, no spell check
|
||||
// updates should take place there.
|
||||
is(getMisspelledWords(editor_de), "German", "one misspelled word expected: German");
|
||||
|
||||
// Remove the fake de_DE dictionary again.
|
||||
script.sendSyncMessage("destroy");
|
||||
|
||||
// Focus again, so the spelling gets updated, but before we need to kill the focus handler.
|
||||
elem_de.onfocus = null;
|
||||
elem_de.blur();
|
||||
elem_de.focus();
|
||||
|
||||
// After removal, the de_DE editor should refresh the spelling with en-US.
|
||||
onSpellCheck(elem_de, function () {
|
||||
spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
// Check that the default English dictionary is loaded and that the spell check has worked.
|
||||
is(currentDictonary, "en-US", "expected en-US");
|
||||
is(getMisspelledWords(editor_de), "heute" + "ist" + "ein" + "guter",
|
||||
"some misspelled words expected: heute ist ein guter");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
107
editor/composer/test/test_bug717433.html
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=717433
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 717433</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717433">Mozilla Bug 717433</a>
|
||||
<p id="display"></p>
|
||||
<iframe id="content"></iframe>
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 717433 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var content = document.getElementById('content');
|
||||
// Load a subframe containing an editor with language "en". At first
|
||||
// load, it will set the dictionary to en-GB or en-US. We set the other one.
|
||||
// At second load, it will return the current dictionary. We can check that the
|
||||
// dictionary is correctly remembered between loads.
|
||||
|
||||
var firstLoad = true;
|
||||
var expected = "";
|
||||
var script;
|
||||
|
||||
var loadListener = function(evt) {
|
||||
|
||||
if (firstLoad) {
|
||||
script = SpecialPowers.loadChromeScript(function() {
|
||||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties)
|
||||
.get("CurWorkD", Components.interfaces.nsIFile);
|
||||
dir.append("tests");
|
||||
dir.append("editor");
|
||||
dir.append("composer");
|
||||
dir.append("test");
|
||||
|
||||
var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Components.interfaces.mozISpellCheckingEngine);
|
||||
|
||||
// Install en-GB dictionary.
|
||||
var en_GB = dir.clone();
|
||||
en_GB.append("en-GB");
|
||||
hunspell.addDirectory(en_GB);
|
||||
|
||||
addMessageListener("en_GB-exists", () => en_GB.exists());
|
||||
addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
|
||||
});
|
||||
is(script.sendSyncMessage("en_GB-exists")[0][0], true,
|
||||
"true expected (en-GB directory should exist)");
|
||||
}
|
||||
|
||||
var doc = evt.target.contentDocument;
|
||||
var elem = doc.getElementById('textarea');
|
||||
var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
|
||||
editor.setSpellcheckUserOverride(true);
|
||||
var inlineSpellChecker = editor.getInlineSpellChecker(true);
|
||||
|
||||
SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
|
||||
.onSpellCheck(elem, function () {
|
||||
var spellchecker = inlineSpellChecker.spellChecker;
|
||||
try {
|
||||
var currentDictonary = spellchecker.GetCurrentDictionary();
|
||||
} catch(e) {}
|
||||
|
||||
if (firstLoad) {
|
||||
firstLoad = false;
|
||||
|
||||
// First time around, we get a random dictionary based on the language "en".
|
||||
if (currentDictonary == "en-GB") {
|
||||
spellchecker.SetCurrentDictionary("en-US");
|
||||
expected = "en-US";
|
||||
} else if (currentDictonary == "en-US") {
|
||||
spellchecker.SetCurrentDictionary("en-GB");
|
||||
expected = "en-GB";
|
||||
} else {
|
||||
is(true, false, "Neither en-US nor en-GB are current");
|
||||
}
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug717433_subframe.html?firstload=false';
|
||||
} else {
|
||||
is(currentDictonary, expected, expected + " expected");
|
||||
content.removeEventListener('load', loadListener, false);
|
||||
|
||||
// Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
|
||||
script.sendSyncMessage("destroy");
|
||||
|
||||
// This will clear the content preferences and reset "spellchecker.dictionary".
|
||||
spellchecker.SetCurrentDictionary("");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
content.addEventListener('load', loadListener, false);
|
||||
|
||||
content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug717433_subframe.html?firstload=true';
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
37
editor/composer/test/test_bug738440.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<!doctype html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=738440
|
||||
-->
|
||||
<title>Test for Bug 738440</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=738440">Mozilla Bug 738440</a>
|
||||
<div contenteditable></div>
|
||||
<script>
|
||||
|
||||
/** Test for Bug 738440 **/
|
||||
document.execCommand("stylewithcss", false, "true");
|
||||
is(document.queryCommandState("stylewithcss"), true,
|
||||
"setting stylewithcss to true should cause its state to be true");
|
||||
is(document.queryCommandState("usecss"), false,
|
||||
"usecss state should always be false");
|
||||
|
||||
document.execCommand("stylewithcss", false, "false");
|
||||
is(document.queryCommandState("stylewithcss"), false,
|
||||
"setting stylewithcss to false should cause its state to be false");
|
||||
is(document.queryCommandState("usecss"), false,
|
||||
"usecss state should always be false");
|
||||
|
||||
document.execCommand("usecss", false, "true");
|
||||
is(document.queryCommandState("stylewithcss"), false,
|
||||
"setting usecss to true should cause stylewithcss state to be false");
|
||||
is(document.queryCommandState("usecss"), false,
|
||||
"usecss state should always be false");
|
||||
|
||||
document.execCommand("usecss", false, "false");
|
||||
is(document.queryCommandState("stylewithcss"), true,
|
||||
"setting usecss to false should cause stylewithcss state to be true");
|
||||
is(document.queryCommandState("usecss"), false,
|
||||
"usecss state should always be false");
|
||||
|
||||
</script>
|
||||
1422
editor/libeditor/CSSEditUtils.cpp
Normal file
473
editor/libeditor/CSSEditUtils.h
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_CSSEditUtils_h
|
||||
#define mozilla_CSSEditUtils_h
|
||||
|
||||
#include "mozilla/ChangeStyleTransaction.h" // for ChangeStyleTransaction
|
||||
#include "nsCOMPtr.h" // for already_AddRefed
|
||||
#include "nsTArray.h" // for nsTArray
|
||||
#include "nscore.h" // for nsAString, nsresult, nullptr
|
||||
|
||||
class nsComputedDOMStyle;
|
||||
class nsIAtom;
|
||||
class nsIContent;
|
||||
class nsIDOMCSSStyleDeclaration;
|
||||
class nsIDOMElement;
|
||||
class nsIDOMNode;
|
||||
class nsINode;
|
||||
class nsString;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class HTMLEditor;
|
||||
namespace dom {
|
||||
class Element;
|
||||
} // namespace dom
|
||||
|
||||
typedef void (*nsProcessValueFunc)(const nsAString* aInputString,
|
||||
nsAString& aOutputString,
|
||||
const char* aDefaultValueString,
|
||||
const char* aPrependString,
|
||||
const char* aAppendString);
|
||||
|
||||
class CSSEditUtils final
|
||||
{
|
||||
public:
|
||||
explicit CSSEditUtils(HTMLEditor* aEditor);
|
||||
~CSSEditUtils();
|
||||
|
||||
enum nsCSSEditableProperty
|
||||
{
|
||||
eCSSEditableProperty_NONE=0,
|
||||
eCSSEditableProperty_background_color,
|
||||
eCSSEditableProperty_background_image,
|
||||
eCSSEditableProperty_border,
|
||||
eCSSEditableProperty_caption_side,
|
||||
eCSSEditableProperty_color,
|
||||
eCSSEditableProperty_float,
|
||||
eCSSEditableProperty_font_family,
|
||||
eCSSEditableProperty_font_size,
|
||||
eCSSEditableProperty_font_style,
|
||||
eCSSEditableProperty_font_weight,
|
||||
eCSSEditableProperty_height,
|
||||
eCSSEditableProperty_list_style_type,
|
||||
eCSSEditableProperty_margin_left,
|
||||
eCSSEditableProperty_margin_right,
|
||||
eCSSEditableProperty_text_align,
|
||||
eCSSEditableProperty_text_decoration,
|
||||
eCSSEditableProperty_vertical_align,
|
||||
eCSSEditableProperty_whitespace,
|
||||
eCSSEditableProperty_width
|
||||
};
|
||||
|
||||
enum StyleType { eSpecified, eComputed };
|
||||
|
||||
|
||||
struct CSSEquivTable
|
||||
{
|
||||
nsCSSEditableProperty cssProperty;
|
||||
nsProcessValueFunc processValueFunctor;
|
||||
const char* defaultValue;
|
||||
const char* prependValue;
|
||||
const char* appendValue;
|
||||
bool gettable;
|
||||
bool caseSensitiveValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Answers true if the given combination element_name/attribute_name
|
||||
* has a CSS equivalence in this implementation.
|
||||
*
|
||||
* @param aNode [IN] A DOM node.
|
||||
* @param aProperty [IN] An atom containing a HTML tag name.
|
||||
* @param aAttribute [IN] A string containing the name of a HTML
|
||||
* attribute carried by the element above.
|
||||
* @return A boolean saying if the tag/attribute has a CSS
|
||||
* equiv.
|
||||
*/
|
||||
bool IsCSSEditableProperty(nsINode* aNode, nsIAtom* aProperty,
|
||||
const nsAString* aAttribute);
|
||||
|
||||
/**
|
||||
* Adds/remove a CSS declaration to the STYLE atrribute carried by a given
|
||||
* element.
|
||||
*
|
||||
* @param aElement [IN] A DOM element.
|
||||
* @param aProperty [IN] An atom containing the CSS property to set.
|
||||
* @param aValue [IN] A string containing the value of the CSS
|
||||
* property.
|
||||
* @param aSuppressTransaction [IN] A boolean indicating, when true,
|
||||
* that no transaction should be recorded.
|
||||
*/
|
||||
nsresult SetCSSProperty(dom::Element& aElement, nsIAtom& aProperty,
|
||||
const nsAString& aValue, bool aSuppressTxn = false);
|
||||
nsresult SetCSSPropertyPixels(dom::Element& aElement,
|
||||
nsIAtom& aProperty, int32_t aIntValue);
|
||||
nsresult RemoveCSSProperty(dom::Element& aElement,
|
||||
nsIAtom& aProperty,
|
||||
const nsAString& aPropertyValue,
|
||||
bool aSuppressTxn = false);
|
||||
|
||||
/**
|
||||
* Directly adds/remove a CSS declaration to the STYLE atrribute carried by
|
||||
* a given element without going through the transaction manager.
|
||||
*
|
||||
* @param aElement [IN] A DOM element.
|
||||
* @param aProperty [IN] A string containing the CSS property to
|
||||
* set/remove.
|
||||
* @param aValue [IN] A string containing the new value of the CSS
|
||||
* property.
|
||||
*/
|
||||
nsresult SetCSSProperty(nsIDOMElement* aElement,
|
||||
const nsAString& aProperty,
|
||||
const nsAString& aValue);
|
||||
nsresult SetCSSPropertyPixels(nsIDOMElement* aElement,
|
||||
const nsAString& aProperty,
|
||||
int32_t aIntValue);
|
||||
|
||||
/**
|
||||
* Gets the specified/computed style value of a CSS property for a given
|
||||
* node (or its element ancestor if it is not an element).
|
||||
*
|
||||
* @param aNode [IN] A DOM node.
|
||||
* @param aProperty [IN] An atom containing the CSS property to get.
|
||||
* @param aPropertyValue [OUT] The retrieved value of the property.
|
||||
*/
|
||||
nsresult GetSpecifiedProperty(nsINode& aNode, nsIAtom& aProperty,
|
||||
nsAString& aValue);
|
||||
nsresult GetComputedProperty(nsINode& aNode, nsIAtom& aProperty,
|
||||
nsAString& aValue);
|
||||
|
||||
/**
|
||||
* Removes a CSS property from the specified declarations in STYLE attribute
|
||||
* and removes the node if it is an useless span.
|
||||
*
|
||||
* @param aNode [IN] The specific node we want to remove a style
|
||||
* from.
|
||||
* @param aProperty [IN] The CSS property atom to remove.
|
||||
* @param aPropertyValue [IN] The value of the property we have to remove
|
||||
* if the property accepts more than one value.
|
||||
*/
|
||||
nsresult RemoveCSSInlineStyle(nsIDOMNode* aNode, nsIAtom* aProperty,
|
||||
const nsAString& aPropertyValue);
|
||||
|
||||
/**
|
||||
* Answers true is the property can be removed by setting a "none" CSS value
|
||||
* on a node.
|
||||
*
|
||||
* @param aProperty [IN] An atom containing a CSS property.
|
||||
* @param aAttribute [IN] Pointer to an attribute name or null if this
|
||||
* information is irrelevant.
|
||||
* @return A boolean saying if the property can be remove by
|
||||
* setting a "none" value.
|
||||
*/
|
||||
bool IsCSSInvertible(nsIAtom& aProperty, const nsAString* aAttribute);
|
||||
|
||||
/**
|
||||
* Get the default browser background color if we need it for
|
||||
* GetCSSBackgroundColorState().
|
||||
*
|
||||
* @param aColor [OUT] The default color as it is defined in prefs.
|
||||
*/
|
||||
void GetDefaultBackgroundColor(nsAString& aColor);
|
||||
|
||||
/**
|
||||
* Get the default length unit used for CSS Indent/Outdent.
|
||||
*
|
||||
* @param aLengthUnit [OUT] The default length unit as it is defined in
|
||||
* prefs.
|
||||
*/
|
||||
void GetDefaultLengthUnit(nsAString & aLengthUnit);
|
||||
|
||||
/**
|
||||
* Returns the list of values for the CSS equivalences to
|
||||
* the passed HTML style for the passed node.
|
||||
*
|
||||
* @param aNode [IN] A DOM node.
|
||||
* @param aHTMLProperty [IN] An atom containing an HTML property.
|
||||
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
|
||||
* irrelevant.
|
||||
* @param aValueString [OUT] The list of CSS values.
|
||||
* @param aStyleType [IN] eSpecified or eComputed.
|
||||
*/
|
||||
nsresult GetCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode,
|
||||
nsIAtom* aHTMLProperty,
|
||||
const nsAString* aAttribute,
|
||||
nsAString& aValueString,
|
||||
StyleType aStyleType);
|
||||
|
||||
/**
|
||||
* Does the node aNode (or his parent if it is not an element node) carries
|
||||
* the CSS equivalent styles to the HTML style for this node ?
|
||||
*
|
||||
* @param aNode [IN] A DOM node.
|
||||
* @param aHTMLProperty [IN] An atom containing an HTML property.
|
||||
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
|
||||
* irrelevant.
|
||||
* @param aIsSet [OUT] A boolean being true if the css properties are
|
||||
* set.
|
||||
* @param aValueString [IN/OUT] The attribute value (in) the list of CSS
|
||||
* values (out).
|
||||
* @param aStyleType [IN] eSpecified or eComputed.
|
||||
*
|
||||
* The nsIContent variant returns aIsSet instead of using an out parameter.
|
||||
*/
|
||||
bool IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aContent,
|
||||
nsIAtom* aProperty,
|
||||
const nsAString* aAttribute,
|
||||
const nsAString& aValue,
|
||||
StyleType aStyleType);
|
||||
|
||||
bool IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aContent,
|
||||
nsIAtom* aProperty,
|
||||
const nsAString* aAttribute,
|
||||
nsAString& aValue,
|
||||
StyleType aStyleType);
|
||||
|
||||
nsresult IsCSSEquivalentToHTMLInlineStyleSet(nsIDOMNode* aNode,
|
||||
nsIAtom* aHTMLProperty,
|
||||
const nsAString* aAttribute,
|
||||
bool& aIsSet,
|
||||
nsAString& aValueString,
|
||||
StyleType aStyleType);
|
||||
|
||||
/**
|
||||
* Adds to the node the CSS inline styles equivalent to the HTML style
|
||||
* and return the number of CSS properties set by the call.
|
||||
*
|
||||
* @param aNode [IN] A DOM node.
|
||||
* @param aHTMLProperty [IN] An atom containing an HTML property.
|
||||
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
|
||||
* irrelevant.
|
||||
* @param aValue [IN] The attribute value.
|
||||
* @param aCount [OUT] The number of CSS properties set by the call.
|
||||
* @param aSuppressTransaction [IN] A boolean indicating, when true,
|
||||
* that no transaction should be recorded.
|
||||
*
|
||||
* aCount is returned by the dom::Element variant instead of being an out
|
||||
* parameter.
|
||||
*/
|
||||
int32_t SetCSSEquivalentToHTMLStyle(dom::Element* aElement,
|
||||
nsIAtom* aProperty,
|
||||
const nsAString* aAttribute,
|
||||
const nsAString* aValue,
|
||||
bool aSuppressTransaction);
|
||||
nsresult SetCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
|
||||
nsIAtom* aHTMLProperty,
|
||||
const nsAString* aAttribute,
|
||||
const nsAString* aValue,
|
||||
int32_t* aCount,
|
||||
bool aSuppressTransaction);
|
||||
|
||||
/**
|
||||
* Removes from the node the CSS inline styles equivalent to the HTML style.
|
||||
*
|
||||
* @param aNode [IN] A DOM node.
|
||||
* @param aHTMLProperty [IN] An atom containing an HTML property.
|
||||
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
|
||||
* irrelevant.
|
||||
* @param aValue [IN] The attribute value.
|
||||
* @param aSuppressTransaction [IN] A boolean indicating, when true,
|
||||
* that no transaction should be recorded.
|
||||
*/
|
||||
nsresult RemoveCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
|
||||
nsIAtom* aHTMLProperty,
|
||||
const nsAString* aAttribute,
|
||||
const nsAString* aValue,
|
||||
bool aSuppressTransaction);
|
||||
|
||||
/**
|
||||
* Removes from the node the CSS inline styles equivalent to the HTML style.
|
||||
*
|
||||
* @param aElement [IN] A DOM Element (must not be null).
|
||||
* @param aHTMLProperty [IN] An atom containing an HTML property.
|
||||
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
|
||||
* irrelevant.
|
||||
* @param aValue [IN] The attribute value.
|
||||
* @param aSuppressTransaction [IN] A boolean indicating, when true,
|
||||
* that no transaction should be recorded.
|
||||
*/
|
||||
nsresult RemoveCSSEquivalentToHTMLStyle(dom::Element* aElement,
|
||||
nsIAtom* aHTMLProperty,
|
||||
const nsAString* aAttribute,
|
||||
const nsAString* aValue,
|
||||
bool aSuppressTransaction);
|
||||
|
||||
/**
|
||||
* Parses a "xxxx.xxxxxuuu" string where x is a digit and u an alpha char
|
||||
* we need such a parser because
|
||||
* nsIDOMCSSStyleDeclaration::GetPropertyCSSValue() is not implemented.
|
||||
*
|
||||
* @param aString [IN] Input string to parse.
|
||||
* @param aValue [OUT] Numeric part.
|
||||
* @param aUnit [OUT] Unit part.
|
||||
*/
|
||||
void ParseLength(const nsAString& aString, float* aValue, nsIAtom** aUnit);
|
||||
|
||||
/**
|
||||
* Sets the mIsCSSPrefChecked private member; used as callback from observer
|
||||
* when the CSS pref state is changed.
|
||||
*
|
||||
* @param aIsCSSPrefChecked [IN] The new boolean state for the pref.
|
||||
*/
|
||||
void SetCSSEnabled(bool aIsCSSPrefChecked);
|
||||
|
||||
/**
|
||||
* Retrieves the mIsCSSPrefChecked private member, true if the CSS pref is
|
||||
* checked, false if it is not.
|
||||
*
|
||||
* @return the boolean value of the CSS pref.
|
||||
*/
|
||||
bool IsCSSPrefChecked();
|
||||
|
||||
/**
|
||||
* ElementsSameStyle compares two elements and checks if they have the same
|
||||
* specified CSS declarations in the STYLE attribute.
|
||||
* The answer is always false if at least one of them carries an ID or a
|
||||
* class.
|
||||
*
|
||||
* @param aFirstNode [IN] A DOM node.
|
||||
* @param aSecondNode [IN] A DOM node.
|
||||
* @return true if the two elements are considered to
|
||||
* have same styles.
|
||||
*/
|
||||
bool ElementsSameStyle(dom::Element* aFirstNode,
|
||||
dom::Element* aSecondNode);
|
||||
bool ElementsSameStyle(nsIDOMNode* aFirstNode, nsIDOMNode* aSecondNode);
|
||||
|
||||
/**
|
||||
* Get the specified inline styles (style attribute) for an element.
|
||||
*
|
||||
* @param aElement [IN] The element node.
|
||||
* @param aCssDecl [OUT] The CSS declaration corresponding to the
|
||||
* style attribute.
|
||||
* @param aLength [OUT] The number of declarations in aCssDecl.
|
||||
*/
|
||||
nsresult GetInlineStyles(dom::Element* aElement,
|
||||
nsIDOMCSSStyleDeclaration** aCssDecl,
|
||||
uint32_t* aLength);
|
||||
nsresult GetInlineStyles(nsIDOMElement* aElement,
|
||||
nsIDOMCSSStyleDeclaration** aCssDecl,
|
||||
uint32_t* aLength);
|
||||
private:
|
||||
nsresult GetInlineStyles(nsISupports* aElement,
|
||||
nsIDOMCSSStyleDeclaration** aCssDecl,
|
||||
uint32_t* aLength);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns aNode itself if it is an element node, or the first ancestors
|
||||
* being an element node if aNode is not one itself.
|
||||
*
|
||||
* @param aNode [IN] A node
|
||||
* @param aElement [OUT] The deepest element node containing aNode
|
||||
* (possibly aNode itself)
|
||||
*/
|
||||
dom::Element* GetElementContainerOrSelf(nsINode* aNode);
|
||||
already_AddRefed<nsIDOMElement> GetElementContainerOrSelf(nsIDOMNode* aNode);
|
||||
|
||||
/**
|
||||
* Gets the computed style for a given element. Can return null.
|
||||
*/
|
||||
already_AddRefed<nsComputedDOMStyle> GetComputedStyle(dom::Element* aElement);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Retrieves the CSS property atom from an enum.
|
||||
*
|
||||
* @param aProperty [IN] The enum value for the property.
|
||||
* @param aAtom [OUT] The corresponding atom.
|
||||
*/
|
||||
void GetCSSPropertyAtom(nsCSSEditableProperty aProperty, nsIAtom** aAtom);
|
||||
|
||||
/**
|
||||
* Retrieves the CSS declarations equivalent to a HTML style value for
|
||||
* a given equivalence table.
|
||||
*
|
||||
* @param aPropertyArray [OUT] The array of css properties.
|
||||
* @param aValueArray [OUT] The array of values for the CSS properties
|
||||
* above.
|
||||
* @param aEquivTable [IN] The equivalence table.
|
||||
* @param aValue [IN] The HTML style value.
|
||||
* @param aGetOrRemoveRequest [IN] A boolean value being true if the call to
|
||||
* the current method is made for
|
||||
* GetCSSEquivalentToHTMLInlineStyleSet() or
|
||||
* RemoveCSSEquivalentToHTMLInlineStyleSet().
|
||||
*/
|
||||
void BuildCSSDeclarations(nsTArray<nsIAtom*>& aPropertyArray,
|
||||
nsTArray<nsString>& cssValueArray,
|
||||
const CSSEquivTable* aEquivTable,
|
||||
const nsAString* aValue,
|
||||
bool aGetOrRemoveRequest);
|
||||
|
||||
/**
|
||||
* Retrieves the CSS declarations equivalent to the given HTML
|
||||
* property/attribute/value for a given node.
|
||||
*
|
||||
* @param aNode [IN] The DOM node.
|
||||
* @param aHTMLProperty [IN] An atom containing an HTML property.
|
||||
* @param aAttribute [IN] A pointer to an attribute name or nullptr
|
||||
* if irrelevant
|
||||
* @param aValue [IN] The attribute value.
|
||||
* @param aPropertyArray [OUT] The array of CSS properties.
|
||||
* @param aValueArray [OUT] The array of values for the CSS properties
|
||||
* above.
|
||||
* @param aGetOrRemoveRequest [IN] A boolean value being true if the call to
|
||||
* the current method is made for
|
||||
* GetCSSEquivalentToHTMLInlineStyleSet() or
|
||||
* RemoveCSSEquivalentToHTMLInlineStyleSet().
|
||||
*/
|
||||
void GenerateCSSDeclarationsFromHTMLStyle(dom::Element* aNode,
|
||||
nsIAtom* aHTMLProperty,
|
||||
const nsAString* aAttribute,
|
||||
const nsAString* aValue,
|
||||
nsTArray<nsIAtom*>& aPropertyArray,
|
||||
nsTArray<nsString>& aValueArray,
|
||||
bool aGetOrRemoveRequest);
|
||||
|
||||
/**
|
||||
* Creates a Transaction for setting or removing a CSS property. Never
|
||||
* returns null.
|
||||
*
|
||||
* @param aElement [IN] A DOM element.
|
||||
* @param aProperty [IN] A CSS property.
|
||||
* @param aValue [IN] The value to set for this CSS property.
|
||||
* @param aChangeType [IN] eSet to set, eRemove to remove.
|
||||
*/
|
||||
already_AddRefed<ChangeStyleTransaction>
|
||||
CreateCSSPropertyTxn(dom::Element& aElement,
|
||||
nsIAtom& aProperty, const nsAString& aValue,
|
||||
ChangeStyleTransaction::EChangeType aChangeType);
|
||||
|
||||
/**
|
||||
* Back-end for GetSpecifiedProperty and GetComputedProperty.
|
||||
*
|
||||
* @param aNode [IN] A DOM node.
|
||||
* @param aProperty [IN] A CSS property.
|
||||
* @param aValue [OUT] The retrieved value for this property.
|
||||
* @param aStyleType [IN] eSpecified or eComputed.
|
||||
*/
|
||||
nsresult GetCSSInlinePropertyBase(nsINode* aNode, nsIAtom* aProperty,
|
||||
nsAString& aValue, StyleType aStyleType);
|
||||
|
||||
private:
|
||||
HTMLEditor* mHTMLEditor;
|
||||
bool mIsCSSPrefChecked;
|
||||
};
|
||||
|
||||
#define NS_EDITOR_INDENT_INCREMENT_IN 0.4134f
|
||||
#define NS_EDITOR_INDENT_INCREMENT_CM 1.05f
|
||||
#define NS_EDITOR_INDENT_INCREMENT_MM 10.5f
|
||||
#define NS_EDITOR_INDENT_INCREMENT_PT 29.76f
|
||||
#define NS_EDITOR_INDENT_INCREMENT_PC 2.48f
|
||||
#define NS_EDITOR_INDENT_INCREMENT_EM 3
|
||||
#define NS_EDITOR_INDENT_INCREMENT_EX 6
|
||||
#define NS_EDITOR_INDENT_INCREMENT_PX 40
|
||||
#define NS_EDITOR_INDENT_INCREMENT_PERCENT 4
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef mozilla_CSSEditUtils_h
|
||||
98
editor/libeditor/ChangeAttributeTransaction.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "ChangeAttributeTransaction.h"
|
||||
|
||||
#include "mozilla/dom/Element.h" // for Element
|
||||
|
||||
#include "nsAString.h"
|
||||
#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc.
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
ChangeAttributeTransaction::ChangeAttributeTransaction(Element& aElement,
|
||||
nsIAtom& aAttribute,
|
||||
const nsAString* aValue)
|
||||
: EditTransactionBase()
|
||||
, mElement(&aElement)
|
||||
, mAttribute(&aAttribute)
|
||||
, mValue(aValue ? *aValue : EmptyString())
|
||||
, mRemoveAttribute(!aValue)
|
||||
, mAttributeWasSet(false)
|
||||
, mUndoValue()
|
||||
{
|
||||
}
|
||||
|
||||
ChangeAttributeTransaction::~ChangeAttributeTransaction()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeAttributeTransaction,
|
||||
EditTransactionBase,
|
||||
mElement)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(ChangeAttributeTransaction, EditTransactionBase)
|
||||
NS_IMPL_RELEASE_INHERITED(ChangeAttributeTransaction, EditTransactionBase)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeAttributeTransaction)
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeAttributeTransaction::DoTransaction()
|
||||
{
|
||||
// Need to get the current value of the attribute and save it, and set
|
||||
// mAttributeWasSet
|
||||
mAttributeWasSet = mElement->GetAttr(kNameSpaceID_None, mAttribute,
|
||||
mUndoValue);
|
||||
|
||||
// XXX: hack until attribute-was-set code is implemented
|
||||
if (!mUndoValue.IsEmpty()) {
|
||||
mAttributeWasSet = true;
|
||||
}
|
||||
// XXX: end hack
|
||||
|
||||
// Now set the attribute to the new value
|
||||
if (mRemoveAttribute) {
|
||||
return mElement->UnsetAttr(kNameSpaceID_None, mAttribute, true);
|
||||
}
|
||||
|
||||
return mElement->SetAttr(kNameSpaceID_None, mAttribute, mValue, true);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeAttributeTransaction::UndoTransaction()
|
||||
{
|
||||
if (mAttributeWasSet) {
|
||||
return mElement->SetAttr(kNameSpaceID_None, mAttribute, mUndoValue, true);
|
||||
}
|
||||
return mElement->UnsetAttr(kNameSpaceID_None, mAttribute, true);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeAttributeTransaction::RedoTransaction()
|
||||
{
|
||||
if (mRemoveAttribute) {
|
||||
return mElement->UnsetAttr(kNameSpaceID_None, mAttribute, true);
|
||||
}
|
||||
|
||||
return mElement->SetAttr(kNameSpaceID_None, mAttribute, mValue, true);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeAttributeTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("ChangeAttributeTransaction: [mRemoveAttribute == ");
|
||||
|
||||
if (mRemoveAttribute) {
|
||||
aString.AppendLiteral("true] ");
|
||||
} else {
|
||||
aString.AppendLiteral("false] ");
|
||||
}
|
||||
aString += nsDependentAtomString(mAttribute);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
72
editor/libeditor/ChangeAttributeTransaction.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef ChangeAttributeTransaction_h
|
||||
#define ChangeAttributeTransaction_h
|
||||
|
||||
#include "mozilla/Attributes.h" // override
|
||||
#include "mozilla/EditTransactionBase.h" // base class
|
||||
#include "nsCOMPtr.h" // nsCOMPtr members
|
||||
#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED
|
||||
#include "nsISupportsImpl.h" // NS_DECL_ISUPPORTS_INHERITED
|
||||
#include "nsString.h" // nsString members
|
||||
|
||||
class nsIAtom;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
||||
class Element;
|
||||
} // namespace dom
|
||||
|
||||
/**
|
||||
* A transaction that changes an attribute of a content node. This transaction
|
||||
* covers add, remove, and change attribute.
|
||||
*/
|
||||
class ChangeAttributeTransaction final : public EditTransactionBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param aElement the element whose attribute will be changed
|
||||
* @param aAttribute the name of the attribute to change
|
||||
* @param aValue the new value for aAttribute, or null to remove
|
||||
*/
|
||||
ChangeAttributeTransaction(dom::Element& aElement,
|
||||
nsIAtom& aAttribute,
|
||||
const nsAString* aValue);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ChangeAttributeTransaction,
|
||||
EditTransactionBase)
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
NS_IMETHOD RedoTransaction() override;
|
||||
|
||||
private:
|
||||
virtual ~ChangeAttributeTransaction();
|
||||
|
||||
// The element to operate upon
|
||||
nsCOMPtr<dom::Element> mElement;
|
||||
|
||||
// The attribute to change
|
||||
nsCOMPtr<nsIAtom> mAttribute;
|
||||
|
||||
// The value to set the attribute to (ignored if mRemoveAttribute==true)
|
||||
nsString mValue;
|
||||
|
||||
// True if the operation is to remove mAttribute from mElement
|
||||
bool mRemoveAttribute;
|
||||
|
||||
// True if the mAttribute was set on mElement at the time of execution
|
||||
bool mAttributeWasSet;
|
||||
|
||||
// The value to set the attribute to for undo
|
||||
nsString mUndoValue;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef ChangeAttributeTransaction_h
|
||||
287
editor/libeditor/ChangeStyleTransaction.cpp
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "mozilla/ChangeStyleTransaction.h"
|
||||
|
||||
#include "mozilla/dom/Element.h" // for Element
|
||||
#include "nsAString.h" // for nsAString_internal::Append, etc.
|
||||
#include "nsCRT.h" // for nsCRT::IsAsciiSpace
|
||||
#include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc.
|
||||
#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc.
|
||||
#include "nsGkAtoms.h" // for nsGkAtoms, etc.
|
||||
#include "nsICSSDeclaration.h" // for nsICSSDeclaration.
|
||||
#include "nsLiteralString.h" // for NS_LITERAL_STRING, etc.
|
||||
#include "nsReadableUtils.h" // for ToNewUnicode
|
||||
#include "nsString.h" // for nsAutoString, nsString, etc.
|
||||
#include "nsStyledElement.h" // for nsStyledElement.
|
||||
#include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
#define kNullCh (char16_t('\0'))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase,
|
||||
mElement)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction)
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(ChangeStyleTransaction, EditTransactionBase)
|
||||
NS_IMPL_RELEASE_INHERITED(ChangeStyleTransaction, EditTransactionBase)
|
||||
|
||||
ChangeStyleTransaction::~ChangeStyleTransaction()
|
||||
{
|
||||
}
|
||||
|
||||
// Answers true if aValue is in the string list of white-space separated values
|
||||
// aValueList.
|
||||
bool
|
||||
ChangeStyleTransaction::ValueIncludes(const nsAString& aValueList,
|
||||
const nsAString& aValue)
|
||||
{
|
||||
nsAutoString valueList(aValueList);
|
||||
bool result = false;
|
||||
|
||||
// put an extra null at the end
|
||||
valueList.Append(kNullCh);
|
||||
|
||||
char16_t* value = ToNewUnicode(aValue);
|
||||
char16_t* start = valueList.BeginWriting();
|
||||
char16_t* end = start;
|
||||
|
||||
while (kNullCh != *start) {
|
||||
while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
|
||||
// skip leading space
|
||||
start++;
|
||||
}
|
||||
end = start;
|
||||
|
||||
while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
|
||||
// look for space or end
|
||||
end++;
|
||||
}
|
||||
// end string here
|
||||
*end = kNullCh;
|
||||
|
||||
if (start < end) {
|
||||
if (nsDependentString(value).Equals(nsDependentString(start),
|
||||
nsCaseInsensitiveStringComparator())) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
start = ++end;
|
||||
}
|
||||
free(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Removes the value aRemoveValue from the string list of white-space separated
|
||||
// values aValueList
|
||||
void
|
||||
ChangeStyleTransaction::RemoveValueFromListOfValues(
|
||||
nsAString& aValues,
|
||||
const nsAString& aRemoveValue)
|
||||
{
|
||||
nsAutoString classStr(aValues);
|
||||
nsAutoString outString;
|
||||
// put an extra null at the end
|
||||
classStr.Append(kNullCh);
|
||||
|
||||
char16_t* start = classStr.BeginWriting();
|
||||
char16_t* end = start;
|
||||
|
||||
while (kNullCh != *start) {
|
||||
while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
|
||||
// skip leading space
|
||||
start++;
|
||||
}
|
||||
end = start;
|
||||
|
||||
while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
|
||||
// look for space or end
|
||||
end++;
|
||||
}
|
||||
// end string here
|
||||
*end = kNullCh;
|
||||
|
||||
if (start < end && !aRemoveValue.Equals(start)) {
|
||||
outString.Append(start);
|
||||
outString.Append(char16_t(' '));
|
||||
}
|
||||
|
||||
start = ++end;
|
||||
}
|
||||
aValues.Assign(outString);
|
||||
}
|
||||
|
||||
ChangeStyleTransaction::ChangeStyleTransaction(Element& aElement,
|
||||
nsIAtom& aProperty,
|
||||
const nsAString& aValue,
|
||||
EChangeType aChangeType)
|
||||
: EditTransactionBase()
|
||||
, mElement(&aElement)
|
||||
, mProperty(&aProperty)
|
||||
, mValue(aValue)
|
||||
, mRemoveProperty(aChangeType == eRemove)
|
||||
, mUndoValue()
|
||||
, mRedoValue()
|
||||
, mUndoAttributeWasSet(false)
|
||||
, mRedoAttributeWasSet(false)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeStyleTransaction::DoTransaction()
|
||||
{
|
||||
nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement);
|
||||
NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER);
|
||||
|
||||
nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style();
|
||||
|
||||
nsAutoString propertyNameString;
|
||||
mProperty->ToString(propertyNameString);
|
||||
|
||||
mUndoAttributeWasSet = mElement->HasAttr(kNameSpaceID_None,
|
||||
nsGkAtoms::style);
|
||||
|
||||
nsAutoString values;
|
||||
nsresult rv = cssDecl->GetPropertyValue(propertyNameString, values);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mUndoValue.Assign(values);
|
||||
|
||||
// Does this property accept more than one value? (bug 62682)
|
||||
bool multiple = AcceptsMoreThanOneValue(*mProperty);
|
||||
|
||||
if (mRemoveProperty) {
|
||||
nsAutoString returnString;
|
||||
if (multiple) {
|
||||
// Let's remove only the value we have to remove and not the others
|
||||
|
||||
// The two lines below are a workaround because
|
||||
// nsDOMCSSDeclaration::GetPropertyCSSValue is not yet implemented (bug
|
||||
// 62682)
|
||||
RemoveValueFromListOfValues(values, NS_LITERAL_STRING("none"));
|
||||
RemoveValueFromListOfValues(values, mValue);
|
||||
if (values.IsEmpty()) {
|
||||
rv = cssDecl->RemoveProperty(propertyNameString, returnString);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
nsAutoString priority;
|
||||
cssDecl->GetPropertyPriority(propertyNameString, priority);
|
||||
rv = cssDecl->SetProperty(propertyNameString, values, priority);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
} else {
|
||||
rv = cssDecl->RemoveProperty(propertyNameString, returnString);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
} else {
|
||||
nsAutoString priority;
|
||||
cssDecl->GetPropertyPriority(propertyNameString, priority);
|
||||
if (multiple) {
|
||||
// Let's add the value we have to add to the others
|
||||
|
||||
// The line below is a workaround because
|
||||
// nsDOMCSSDeclaration::GetPropertyCSSValue is not yet implemented (bug
|
||||
// 62682)
|
||||
AddValueToMultivalueProperty(values, mValue);
|
||||
} else {
|
||||
values.Assign(mValue);
|
||||
}
|
||||
rv = cssDecl->SetProperty(propertyNameString, values, priority);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Let's be sure we don't keep an empty style attribute
|
||||
uint32_t length;
|
||||
rv = cssDecl->GetLength(&length);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!length) {
|
||||
rv = mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
mRedoAttributeWasSet = true;
|
||||
}
|
||||
|
||||
return cssDecl->GetPropertyValue(propertyNameString, mRedoValue);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ChangeStyleTransaction::SetStyle(bool aAttributeWasSet,
|
||||
nsAString& aValue)
|
||||
{
|
||||
if (aAttributeWasSet) {
|
||||
// The style attribute was not empty, let's recreate the declaration
|
||||
nsAutoString propertyNameString;
|
||||
mProperty->ToString(propertyNameString);
|
||||
|
||||
nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement);
|
||||
NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER);
|
||||
nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style();
|
||||
|
||||
if (aValue.IsEmpty()) {
|
||||
// An empty value means we have to remove the property
|
||||
nsAutoString returnString;
|
||||
return cssDecl->RemoveProperty(propertyNameString, returnString);
|
||||
}
|
||||
// Let's recreate the declaration as it was
|
||||
nsAutoString priority;
|
||||
cssDecl->GetPropertyPriority(propertyNameString, priority);
|
||||
return cssDecl->SetProperty(propertyNameString, aValue, priority);
|
||||
}
|
||||
return mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeStyleTransaction::UndoTransaction()
|
||||
{
|
||||
return SetStyle(mUndoAttributeWasSet, mUndoValue);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeStyleTransaction::RedoTransaction()
|
||||
{
|
||||
return SetStyle(mRedoAttributeWasSet, mRedoValue);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ChangeStyleTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("ChangeStyleTransaction: [mRemoveProperty == ");
|
||||
|
||||
if (mRemoveProperty) {
|
||||
aString.AppendLiteral("true] ");
|
||||
} else {
|
||||
aString.AppendLiteral("false] ");
|
||||
}
|
||||
aString += nsDependentAtomString(mProperty);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// True if the CSS property accepts more than one value
|
||||
bool
|
||||
ChangeStyleTransaction::AcceptsMoreThanOneValue(nsIAtom& aCSSProperty)
|
||||
{
|
||||
return &aCSSProperty == nsGkAtoms::text_decoration;
|
||||
}
|
||||
|
||||
// Adds the value aNewValue to the list of white-space separated values aValues
|
||||
void
|
||||
ChangeStyleTransaction::AddValueToMultivalueProperty(nsAString& aValues,
|
||||
const nsAString& aNewValue)
|
||||
{
|
||||
if (aValues.IsEmpty() || aValues.LowerCaseEqualsLiteral("none")) {
|
||||
aValues.Assign(aNewValue);
|
||||
} else if (!ValueIncludes(aValues, aNewValue)) {
|
||||
// We already have another value but not this one; add it
|
||||
aValues.Append(char16_t(' '));
|
||||
aValues.Append(aNewValue);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
123
editor/libeditor/ChangeStyleTransaction.h
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_ChangeStyleTransaction_h
|
||||
#define mozilla_ChangeStyleTransaction_h
|
||||
|
||||
#include "mozilla/EditTransactionBase.h" // base class
|
||||
#include "nsCOMPtr.h" // nsCOMPtr members
|
||||
#include "nsCycleCollectionParticipant.h" // various macros
|
||||
#include "nsString.h" // nsString members
|
||||
|
||||
class nsAString;
|
||||
class nsIAtom;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
||||
class Element;
|
||||
} // namespace dom
|
||||
|
||||
/**
|
||||
* A transaction that changes the value of a CSS inline style of a content
|
||||
* node. This transaction covers add, remove, and change a property's value.
|
||||
*/
|
||||
class ChangeStyleTransaction final : public EditTransactionBase
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ChangeStyleTransaction,
|
||||
EditTransactionBase)
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
NS_IMETHOD RedoTransaction() override;
|
||||
|
||||
enum EChangeType { eSet, eRemove };
|
||||
|
||||
/**
|
||||
* @param aNode [IN] the node whose style attribute will be changed
|
||||
* @param aProperty [IN] the name of the property to change
|
||||
* @param aValue [IN] new value for aProperty, or value to remove
|
||||
* @param aChangeType [IN] whether to set or remove
|
||||
*/
|
||||
ChangeStyleTransaction(dom::Element& aElement,
|
||||
nsIAtom& aProperty,
|
||||
const nsAString& aValue,
|
||||
EChangeType aChangeType);
|
||||
|
||||
/**
|
||||
* Returns true if the list of white-space separated values contains aValue
|
||||
*
|
||||
* @param aValueList [IN] a list of white-space separated values
|
||||
* @param aValue [IN] the value to look for in the list
|
||||
* @return true if the value is in the list of values
|
||||
*/
|
||||
static bool ValueIncludes(const nsAString& aValueList,
|
||||
const nsAString& aValue);
|
||||
|
||||
private:
|
||||
virtual ~ChangeStyleTransaction();
|
||||
|
||||
/*
|
||||
* Adds the value aNewValue to list of white-space separated values aValues.
|
||||
*
|
||||
* @param aValues [IN/OUT] a list of wite-space separated values
|
||||
* @param aNewValue [IN] a value this code adds to aValues if it is not
|
||||
* already in
|
||||
*/
|
||||
void AddValueToMultivalueProperty(nsAString& aValues,
|
||||
const nsAString& aNewValue);
|
||||
|
||||
/**
|
||||
* Returns true if the property accepts more than one value.
|
||||
*
|
||||
* @param aCSSProperty [IN] the CSS property
|
||||
* @return true if the property accepts more than one value
|
||||
*/
|
||||
bool AcceptsMoreThanOneValue(nsIAtom& aCSSProperty);
|
||||
|
||||
/**
|
||||
* Remove a value from a list of white-space separated values.
|
||||
* @param aValues [IN] a list of white-space separated values
|
||||
* @param aRemoveValue [IN] the value to remove from the list
|
||||
*/
|
||||
void RemoveValueFromListOfValues(nsAString& aValues,
|
||||
const nsAString& aRemoveValue);
|
||||
|
||||
/**
|
||||
* If the boolean is true and if the value is not the empty string,
|
||||
* set the property in the transaction to that value; if the value
|
||||
* is empty, remove the property from element's styles. If the boolean
|
||||
* is false, just remove the style attribute.
|
||||
*/
|
||||
nsresult SetStyle(bool aAttributeWasSet, nsAString& aValue);
|
||||
|
||||
// The element to operate upon.
|
||||
nsCOMPtr<dom::Element> mElement;
|
||||
|
||||
// The CSS property to change.
|
||||
nsCOMPtr<nsIAtom> mProperty;
|
||||
|
||||
// The value to set the property to (ignored if mRemoveProperty==true).
|
||||
nsString mValue;
|
||||
|
||||
// true if the operation is to remove mProperty from mElement.
|
||||
bool mRemoveProperty;
|
||||
|
||||
// The value to set the property to for undo.
|
||||
nsString mUndoValue;
|
||||
// The value to set the property to for redo.
|
||||
nsString mRedoValue;
|
||||
// True if the style attribute was present and not empty before DoTransaction.
|
||||
bool mUndoAttributeWasSet;
|
||||
// True if the style attribute is present and not empty after DoTransaction.
|
||||
bool mRedoAttributeWasSet;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef mozilla_ChangeStyleTransaction_h
|
||||
332
editor/libeditor/CompositionTransaction.cpp
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "CompositionTransaction.h"
|
||||
|
||||
#include "mozilla/EditorBase.h" // mEditorBase
|
||||
#include "mozilla/SelectionState.h" // RangeUpdater
|
||||
#include "mozilla/dom/Selection.h" // local var
|
||||
#include "mozilla/dom/Text.h" // mTextNode
|
||||
#include "nsAString.h" // params
|
||||
#include "nsDebug.h" // for NS_ASSERTION, etc
|
||||
#include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc
|
||||
#include "nsIPresShell.h" // nsISelectionController constants
|
||||
#include "nsRange.h" // local var
|
||||
#include "nsQueryObject.h" // for do_QueryObject
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
CompositionTransaction::CompositionTransaction(
|
||||
Text& aTextNode,
|
||||
uint32_t aOffset,
|
||||
uint32_t aReplaceLength,
|
||||
TextRangeArray* aTextRangeArray,
|
||||
const nsAString& aStringToInsert,
|
||||
EditorBase& aEditorBase,
|
||||
RangeUpdater* aRangeUpdater)
|
||||
: mTextNode(&aTextNode)
|
||||
, mOffset(aOffset)
|
||||
, mReplaceLength(aReplaceLength)
|
||||
, mRanges(aTextRangeArray)
|
||||
, mStringToInsert(aStringToInsert)
|
||||
, mEditorBase(aEditorBase)
|
||||
, mRangeUpdater(aRangeUpdater)
|
||||
, mFixed(false)
|
||||
{
|
||||
MOZ_ASSERT(mTextNode->TextLength() >= mOffset);
|
||||
}
|
||||
|
||||
CompositionTransaction::~CompositionTransaction()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
|
||||
mTextNode)
|
||||
// mRangeList can't lead to cycles
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositionTransaction)
|
||||
if (aIID.Equals(NS_GET_IID(CompositionTransaction))) {
|
||||
foundInterface = static_cast<nsITransaction*>(this);
|
||||
} else
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(CompositionTransaction, EditTransactionBase)
|
||||
NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
|
||||
|
||||
NS_IMETHODIMP
|
||||
CompositionTransaction::DoTransaction()
|
||||
{
|
||||
// Fail before making any changes if there's no selection controller
|
||||
nsCOMPtr<nsISelectionController> selCon;
|
||||
mEditorBase.GetSelectionController(getter_AddRefs(selCon));
|
||||
NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
// Advance caret: This requires the presentation shell to get the selection.
|
||||
if (mReplaceLength == 0) {
|
||||
nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
|
||||
} else {
|
||||
uint32_t replaceableLength = mTextNode->TextLength() - mOffset;
|
||||
nsresult rv =
|
||||
mTextNode->ReplaceData(mOffset, mReplaceLength, mStringToInsert);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
mRangeUpdater->SelAdjDeleteText(mTextNode, mOffset, mReplaceLength);
|
||||
mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
|
||||
|
||||
// If IME text node is multiple node, ReplaceData doesn't remove all IME
|
||||
// text. So we need remove remained text into other text node.
|
||||
if (replaceableLength < mReplaceLength) {
|
||||
int32_t remainLength = mReplaceLength - replaceableLength;
|
||||
nsCOMPtr<nsINode> node = mTextNode->GetNextSibling();
|
||||
while (node && node->IsNodeOfType(nsINode::eTEXT) &&
|
||||
remainLength > 0) {
|
||||
Text* text = static_cast<Text*>(node.get());
|
||||
uint32_t textLength = text->TextLength();
|
||||
text->DeleteData(0, remainLength);
|
||||
mRangeUpdater->SelAdjDeleteText(text, 0, remainLength);
|
||||
remainLength -= textLength;
|
||||
node = node->GetNextSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = SetSelectionForRanges();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CompositionTransaction::UndoTransaction()
|
||||
{
|
||||
// Get the selection first so we'll fail before making any changes if we
|
||||
// can't get it
|
||||
RefPtr<Selection> selection = mEditorBase.GetSelection();
|
||||
NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
nsresult rv = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// set the selection to the insertion point where the string was removed
|
||||
rv = selection->Collapse(mTextNode, mOffset);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Selection could not be collapsed after undo of IME insert.");
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CompositionTransaction::Merge(nsITransaction* aTransaction,
|
||||
bool* aDidMerge)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aTransaction && aDidMerge);
|
||||
|
||||
// Check to make sure we aren't fixed, if we are then nothing gets absorbed
|
||||
if (mFixed) {
|
||||
*aDidMerge = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If aTransaction is another CompositionTransaction then absorb it
|
||||
RefPtr<CompositionTransaction> otherTransaction =
|
||||
do_QueryObject(aTransaction);
|
||||
if (otherTransaction) {
|
||||
// We absorb the next IME transaction by adopting its insert string
|
||||
mStringToInsert = otherTransaction->mStringToInsert;
|
||||
mRanges = otherTransaction->mRanges;
|
||||
*aDidMerge = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
*aDidMerge = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
CompositionTransaction::MarkFixed()
|
||||
{
|
||||
mFixed = true;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CompositionTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("CompositionTransaction: ");
|
||||
aString += mStringToInsert;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* ============ private methods ================== */
|
||||
|
||||
nsresult
|
||||
CompositionTransaction::SetSelectionForRanges()
|
||||
{
|
||||
return SetIMESelection(mEditorBase, mTextNode, mOffset,
|
||||
mStringToInsert.Length(), mRanges);
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
CompositionTransaction::SetIMESelection(EditorBase& aEditorBase,
|
||||
Text* aTextNode,
|
||||
uint32_t aOffsetInNode,
|
||||
uint32_t aLengthOfCompositionString,
|
||||
const TextRangeArray* aRanges)
|
||||
{
|
||||
RefPtr<Selection> selection = aEditorBase.GetSelection();
|
||||
NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
nsresult rv = selection->StartBatchChanges();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// First, remove all selections of IME composition.
|
||||
static const RawSelectionType kIMESelections[] = {
|
||||
nsISelectionController::SELECTION_IME_RAWINPUT,
|
||||
nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
|
||||
nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
|
||||
nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
|
||||
};
|
||||
|
||||
nsCOMPtr<nsISelectionController> selCon;
|
||||
aEditorBase.GetSelectionController(getter_AddRefs(selCon));
|
||||
NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) {
|
||||
nsCOMPtr<nsISelection> selectionOfIME;
|
||||
if (NS_FAILED(selCon->GetSelection(kIMESelections[i],
|
||||
getter_AddRefs(selectionOfIME)))) {
|
||||
continue;
|
||||
}
|
||||
rv = selectionOfIME->RemoveAllRanges();
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Failed to remove all ranges of IME selection");
|
||||
}
|
||||
|
||||
// Set caret position and selection of IME composition with TextRangeArray.
|
||||
bool setCaret = false;
|
||||
uint32_t countOfRanges = aRanges ? aRanges->Length() : 0;
|
||||
|
||||
#ifdef DEBUG
|
||||
// Bounds-checking on debug builds
|
||||
uint32_t maxOffset = aTextNode->Length();
|
||||
#endif
|
||||
|
||||
// NOTE: composition string may be truncated when it's committed and
|
||||
// maxlength attribute value doesn't allow input of all text of this
|
||||
// composition.
|
||||
for (uint32_t i = 0; i < countOfRanges; ++i) {
|
||||
const TextRange& textRange = aRanges->ElementAt(i);
|
||||
|
||||
// Caret needs special handling since its length may be 0 and if it's not
|
||||
// specified explicitly, we need to handle it ourselves later.
|
||||
if (textRange.mRangeType == TextRangeType::eCaret) {
|
||||
NS_ASSERTION(!setCaret, "The ranges already has caret position");
|
||||
NS_ASSERTION(!textRange.Length(),
|
||||
"EditorBase doesn't support wide caret");
|
||||
int32_t caretOffset = static_cast<int32_t>(
|
||||
aOffsetInNode +
|
||||
std::min(textRange.mStartOffset, aLengthOfCompositionString));
|
||||
MOZ_ASSERT(caretOffset >= 0 &&
|
||||
static_cast<uint32_t>(caretOffset) <= maxOffset);
|
||||
rv = selection->Collapse(aTextNode, caretOffset);
|
||||
setCaret = setCaret || NS_SUCCEEDED(rv);
|
||||
if (NS_WARN_IF(!setCaret)) {
|
||||
continue;
|
||||
}
|
||||
// If caret range is specified explicitly, we should show the caret if
|
||||
// it should be so.
|
||||
aEditorBase.HideCaret(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the clause length is 0, it should be a bug.
|
||||
if (!textRange.Length()) {
|
||||
NS_WARNING("Any clauses must not be empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
RefPtr<nsRange> clauseRange;
|
||||
int32_t startOffset = static_cast<int32_t>(
|
||||
aOffsetInNode +
|
||||
std::min(textRange.mStartOffset, aLengthOfCompositionString));
|
||||
MOZ_ASSERT(startOffset >= 0 &&
|
||||
static_cast<uint32_t>(startOffset) <= maxOffset);
|
||||
int32_t endOffset = static_cast<int32_t>(
|
||||
aOffsetInNode +
|
||||
std::min(textRange.mEndOffset, aLengthOfCompositionString));
|
||||
MOZ_ASSERT(endOffset >= startOffset &&
|
||||
static_cast<uint32_t>(endOffset) <= maxOffset);
|
||||
rv = nsRange::CreateRange(aTextNode, startOffset,
|
||||
aTextNode, endOffset,
|
||||
getter_AddRefs(clauseRange));
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to create a DOM range for a clause of composition");
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the range of the clause to selection.
|
||||
nsCOMPtr<nsISelection> selectionOfIME;
|
||||
rv = selCon->GetSelection(ToRawSelectionType(textRange.mRangeType),
|
||||
getter_AddRefs(selectionOfIME));
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to get IME selection");
|
||||
break;
|
||||
}
|
||||
|
||||
rv = selectionOfIME->AddRange(clauseRange);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to add selection range for a clause of composition");
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the style of the clause.
|
||||
nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv =
|
||||
do_QueryInterface(selectionOfIME);
|
||||
if (!selectionOfIMEPriv) {
|
||||
NS_WARNING("Failed to get nsISelectionPrivate interface from selection");
|
||||
continue; // Since this is additional feature, we can continue this job.
|
||||
}
|
||||
rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange,
|
||||
textRange.mRangeStyle);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to set selection style");
|
||||
break; // but this is unexpected...
|
||||
}
|
||||
}
|
||||
|
||||
// If the ranges doesn't include explicit caret position, let's set the
|
||||
// caret to the end of composition string.
|
||||
if (!setCaret) {
|
||||
int32_t caretOffset =
|
||||
static_cast<int32_t>(aOffsetInNode + aLengthOfCompositionString);
|
||||
MOZ_ASSERT(caretOffset >= 0 &&
|
||||
static_cast<uint32_t>(caretOffset) <= maxOffset);
|
||||
rv = selection->Collapse(aTextNode, caretOffset);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Failed to set caret at the end of composition string");
|
||||
|
||||
// If caret range isn't specified explicitly, we should hide the caret.
|
||||
// Hiding the caret benefits a Windows build (see bug 555642 comment #6).
|
||||
// However, when there is no range, we should keep showing caret.
|
||||
if (countOfRanges) {
|
||||
aEditorBase.HideCaret(true);
|
||||
}
|
||||
}
|
||||
|
||||
rv = selection->EndBatchChangesInternal();
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
104
editor/libeditor/CompositionTransaction.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef CompositionTransaction_h
|
||||
#define CompositionTransaction_h
|
||||
|
||||
#include "mozilla/EditTransactionBase.h" // base class
|
||||
#include "nsCycleCollectionParticipant.h" // various macros
|
||||
#include "nsString.h" // mStringToInsert
|
||||
|
||||
#define NS_IMETEXTTXN_IID \
|
||||
{ 0xb391355d, 0x346c, 0x43d1, \
|
||||
{ 0x85, 0xed, 0x9e, 0x65, 0xbe, 0xe7, 0x7e, 0x48 } }
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class EditorBase;
|
||||
class RangeUpdater;
|
||||
class TextRangeArray;
|
||||
|
||||
namespace dom {
|
||||
class Text;
|
||||
} // namespace dom
|
||||
|
||||
/**
|
||||
* CompositionTransaction stores all edit for a composition, i.e.,
|
||||
* from compositionstart event to compositionend event. E.g., inserting a
|
||||
* composition string, modifying the composition string or its IME selection
|
||||
* ranges and commit or cancel the composition.
|
||||
*/
|
||||
class CompositionTransaction final : public EditTransactionBase
|
||||
{
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMETEXTTXN_IID)
|
||||
|
||||
/**
|
||||
* @param aTextNode The start node of text content.
|
||||
* @param aOffset The location in aTextNode to do the insertion.
|
||||
* @param aReplaceLength The length of text to replace. 0 means not
|
||||
* replacing existing text.
|
||||
* @param aTextRangeArray Clauses and/or caret information. This may be
|
||||
* null.
|
||||
* @param aString The new text to insert.
|
||||
* @param aEditorBase Used to get and set the selection.
|
||||
* @param aRangeUpdater The range updater
|
||||
*/
|
||||
CompositionTransaction(dom::Text& aTextNode,
|
||||
uint32_t aOffset, uint32_t aReplaceLength,
|
||||
TextRangeArray* aTextRangeArray,
|
||||
const nsAString& aString,
|
||||
EditorBase& aEditorBase,
|
||||
RangeUpdater* aRangeUpdater);
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CompositionTransaction,
|
||||
EditTransactionBase)
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
|
||||
|
||||
void MarkFixed();
|
||||
|
||||
static nsresult SetIMESelection(EditorBase& aEditorBase,
|
||||
dom::Text* aTextNode,
|
||||
uint32_t aOffsetInNode,
|
||||
uint32_t aLengthOfCompositionString,
|
||||
const TextRangeArray* aRanges);
|
||||
|
||||
private:
|
||||
~CompositionTransaction();
|
||||
|
||||
nsresult SetSelectionForRanges();
|
||||
|
||||
// The text element to operate upon.
|
||||
RefPtr<dom::Text> mTextNode;
|
||||
|
||||
// The offsets into mTextNode where the insertion should be placed.
|
||||
uint32_t mOffset;
|
||||
|
||||
uint32_t mReplaceLength;
|
||||
|
||||
// The range list.
|
||||
RefPtr<TextRangeArray> mRanges;
|
||||
|
||||
// The text to insert into mTextNode at mOffset.
|
||||
nsString mStringToInsert;
|
||||
|
||||
// The editor, which is used to get the selection controller.
|
||||
EditorBase& mEditorBase;
|
||||
|
||||
RangeUpdater* mRangeUpdater;
|
||||
|
||||
bool mFixed;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(CompositionTransaction, NS_IMETEXTTXN_IID)
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef CompositionTransaction_h
|
||||
147
editor/libeditor/CreateElementTransaction.cpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "CreateElementTransaction.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/Selection.h"
|
||||
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/EditorBase.h"
|
||||
|
||||
#include "nsAlgorithm.h"
|
||||
#include "nsAString.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsError.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIDOMCharacterData.h"
|
||||
#include "nsIEditor.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsISupportsUtils.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
CreateElementTransaction::CreateElementTransaction(EditorBase& aEditorBase,
|
||||
nsIAtom& aTag,
|
||||
nsINode& aParent,
|
||||
int32_t aOffsetInParent)
|
||||
: EditTransactionBase()
|
||||
, mEditorBase(&aEditorBase)
|
||||
, mTag(&aTag)
|
||||
, mParent(&aParent)
|
||||
, mOffsetInParent(aOffsetInParent)
|
||||
{
|
||||
}
|
||||
|
||||
CreateElementTransaction::~CreateElementTransaction()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTransaction,
|
||||
EditTransactionBase,
|
||||
mParent,
|
||||
mNewNode,
|
||||
mRefNode)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(CreateElementTransaction, EditTransactionBase)
|
||||
NS_IMPL_RELEASE_INHERITED(CreateElementTransaction, EditTransactionBase)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTransaction)
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
CreateElementTransaction::DoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mEditorBase && mTag && mParent);
|
||||
|
||||
mNewNode = mEditorBase->CreateHTMLContent(mTag);
|
||||
NS_ENSURE_STATE(mNewNode);
|
||||
|
||||
// Try to insert formatting whitespace for the new node:
|
||||
mEditorBase->MarkNodeDirty(GetAsDOMNode(mNewNode));
|
||||
|
||||
// Insert the new node
|
||||
ErrorResult rv;
|
||||
if (mOffsetInParent == -1) {
|
||||
mParent->AppendChild(*mNewNode, rv);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
mOffsetInParent = std::min(mOffsetInParent,
|
||||
static_cast<int32_t>(mParent->GetChildCount()));
|
||||
|
||||
// Note, it's ok for mRefNode to be null. That means append
|
||||
mRefNode = mParent->GetChildAt(mOffsetInParent);
|
||||
|
||||
nsCOMPtr<nsIContent> refNode = mRefNode;
|
||||
mParent->InsertBefore(*mNewNode, refNode, rv);
|
||||
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
|
||||
|
||||
// Only set selection to insertion point if editor gives permission
|
||||
if (!mEditorBase->GetShouldTxnSetSelection()) {
|
||||
// Do nothing - DOM range gravity will adjust selection
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<Selection> selection = mEditorBase->GetSelection();
|
||||
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
|
||||
|
||||
rv = selection->CollapseNative(mParent, mParent->IndexOf(mNewNode) + 1);
|
||||
NS_ASSERTION(!rv.Failed(),
|
||||
"selection could not be collapsed after insert");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CreateElementTransaction::UndoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mEditorBase && mParent);
|
||||
|
||||
ErrorResult rv;
|
||||
mParent->RemoveChild(*mNewNode, rv);
|
||||
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CreateElementTransaction::RedoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mEditorBase && mParent);
|
||||
|
||||
// First, reset mNewNode so it has no attributes or content
|
||||
// XXX We never actually did this, we only cleared mNewNode's contents if it
|
||||
// was a CharacterData node (which it's not, it's an Element)
|
||||
|
||||
// Now, reinsert mNewNode
|
||||
ErrorResult rv;
|
||||
nsCOMPtr<nsIContent> refNode = mRefNode;
|
||||
mParent->InsertBefore(*mNewNode, refNode, rv);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CreateElementTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("CreateElementTransaction: ");
|
||||
aString += nsDependentAtomString(mTag);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<Element>
|
||||
CreateElementTransaction::GetNewNode()
|
||||
{
|
||||
return nsCOMPtr<Element>(mNewNode).forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
80
editor/libeditor/CreateElementTransaction.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef CreateElementTransaction_h
|
||||
#define CreateElementTransaction_h
|
||||
|
||||
#include "mozilla/EditTransactionBase.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
class nsIAtom;
|
||||
class nsIContent;
|
||||
class nsINode;
|
||||
|
||||
/**
|
||||
* A transaction that creates a new node in the content tree.
|
||||
*/
|
||||
namespace mozilla {
|
||||
|
||||
class EditorBase;
|
||||
namespace dom {
|
||||
class Element;
|
||||
} // namespace dom
|
||||
|
||||
class CreateElementTransaction final : public EditTransactionBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initialize the transaction.
|
||||
* @param aEditorBase The provider of basic editing functionality.
|
||||
* @param aTag The tag (P, HR, TABLE, etc.) for the new element.
|
||||
* @param aParent The node into which the new element will be
|
||||
* inserted.
|
||||
* @param aOffsetInParent The location in aParent to insert the new element.
|
||||
* If eAppend, the new element is appended as the last
|
||||
* child.
|
||||
*/
|
||||
CreateElementTransaction(EditorBase& aEditorBase,
|
||||
nsIAtom& aTag,
|
||||
nsINode& aParent,
|
||||
int32_t aOffsetInParent);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CreateElementTransaction,
|
||||
EditTransactionBase)
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
NS_IMETHOD RedoTransaction() override;
|
||||
|
||||
already_AddRefed<dom::Element> GetNewNode();
|
||||
|
||||
protected:
|
||||
virtual ~CreateElementTransaction();
|
||||
|
||||
// The document into which the new node will be inserted.
|
||||
EditorBase* mEditorBase;
|
||||
|
||||
// The tag (mapping to object type) for the new element.
|
||||
nsCOMPtr<nsIAtom> mTag;
|
||||
|
||||
// The node into which the new node will be inserted.
|
||||
nsCOMPtr<nsINode> mParent;
|
||||
|
||||
// The index in mParent for the new node.
|
||||
int32_t mOffsetInParent;
|
||||
|
||||
// The new node to insert.
|
||||
nsCOMPtr<dom::Element> mNewNode;
|
||||
|
||||
// The node we will insert mNewNode before. We compute this ourselves.
|
||||
nsCOMPtr<nsIContent> mRefNode;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef CreateElementTransaction_h
|
||||
123
editor/libeditor/DeleteNodeTransaction.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "DeleteNodeTransaction.h"
|
||||
#include "mozilla/EditorBase.h"
|
||||
#include "mozilla/SelectionState.h" // RangeUpdater
|
||||
#include "nsDebug.h"
|
||||
#include "nsError.h"
|
||||
#include "nsAString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
DeleteNodeTransaction::DeleteNodeTransaction()
|
||||
: mEditorBase(nullptr)
|
||||
, mRangeUpdater(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
DeleteNodeTransaction::~DeleteNodeTransaction()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteNodeTransaction, EditTransactionBase,
|
||||
mNode,
|
||||
mParent,
|
||||
mRefNode)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(DeleteNodeTransaction, EditTransactionBase)
|
||||
NS_IMPL_RELEASE_INHERITED(DeleteNodeTransaction, EditTransactionBase)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteNodeTransaction)
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
||||
|
||||
nsresult
|
||||
DeleteNodeTransaction::Init(EditorBase* aEditorBase,
|
||||
nsINode* aNode,
|
||||
RangeUpdater* aRangeUpdater)
|
||||
{
|
||||
NS_ENSURE_TRUE(aEditorBase && aNode, NS_ERROR_NULL_POINTER);
|
||||
mEditorBase = aEditorBase;
|
||||
mNode = aNode;
|
||||
mParent = aNode->GetParentNode();
|
||||
|
||||
// do nothing if the node has a parent and it's read-only
|
||||
NS_ENSURE_TRUE(!mParent || mEditorBase->IsModifiableNode(mParent),
|
||||
NS_ERROR_FAILURE);
|
||||
|
||||
mRangeUpdater = aRangeUpdater;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteNodeTransaction::DoTransaction()
|
||||
{
|
||||
NS_ENSURE_TRUE(mNode, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
if (!mParent) {
|
||||
// this is a no-op, there's no parent to delete mNode from
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// remember which child mNode was (by remembering which child was next);
|
||||
// mRefNode can be null
|
||||
mRefNode = mNode->GetNextSibling();
|
||||
|
||||
// give range updater a chance. SelAdjDeleteNode() needs to be called
|
||||
// *before* we do the action, unlike some of the other RangeItem update
|
||||
// methods.
|
||||
if (mRangeUpdater) {
|
||||
mRangeUpdater->SelAdjDeleteNode(mNode->AsDOMNode());
|
||||
}
|
||||
|
||||
ErrorResult error;
|
||||
mParent->RemoveChild(*mNode, error);
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteNodeTransaction::UndoTransaction()
|
||||
{
|
||||
if (!mParent) {
|
||||
// this is a legal state, the txn is a no-op
|
||||
return NS_OK;
|
||||
}
|
||||
if (!mNode) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
ErrorResult error;
|
||||
nsCOMPtr<nsIContent> refNode = mRefNode;
|
||||
mParent->InsertBefore(*mNode, refNode, error);
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteNodeTransaction::RedoTransaction()
|
||||
{
|
||||
if (!mParent) {
|
||||
// this is a legal state, the txn is a no-op
|
||||
return NS_OK;
|
||||
}
|
||||
if (!mNode) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
if (mRangeUpdater) {
|
||||
mRangeUpdater->SelAdjDeleteNode(mNode->AsDOMNode());
|
||||
}
|
||||
|
||||
ErrorResult error;
|
||||
mParent->RemoveChild(*mNode, error);
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteNodeTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("DeleteNodeTransaction");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
66
editor/libeditor/DeleteNodeTransaction.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef DeleteNodeTransaction_h
|
||||
#define DeleteNodeTransaction_h
|
||||
|
||||
#include "mozilla/EditTransactionBase.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nscore.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class EditorBase;
|
||||
class RangeUpdater;
|
||||
|
||||
/**
|
||||
* A transaction that deletes a single element
|
||||
*/
|
||||
class DeleteNodeTransaction final : public EditTransactionBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initialize the transaction.
|
||||
* @param aElement The node to delete.
|
||||
*/
|
||||
nsresult Init(EditorBase* aEditorBase, nsINode* aNode,
|
||||
RangeUpdater* aRangeUpdater);
|
||||
|
||||
DeleteNodeTransaction();
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteNodeTransaction,
|
||||
EditTransactionBase)
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
NS_IMETHOD RedoTransaction() override;
|
||||
|
||||
protected:
|
||||
virtual ~DeleteNodeTransaction();
|
||||
|
||||
// The element to delete.
|
||||
nsCOMPtr<nsINode> mNode;
|
||||
|
||||
// Parent of node to delete.
|
||||
nsCOMPtr<nsINode> mParent;
|
||||
|
||||
// Next sibling to remember for undo/redo purposes.
|
||||
nsCOMPtr<nsIContent> mRefNode;
|
||||
|
||||
// The editor for this transaction.
|
||||
EditorBase* mEditorBase;
|
||||
|
||||
// Range updater object.
|
||||
RangeUpdater* mRangeUpdater;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef DeleteNodeTransaction_h
|
||||
239
editor/libeditor/DeleteRangeTransaction.cpp
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "DeleteRangeTransaction.h"
|
||||
|
||||
#include "DeleteNodeTransaction.h"
|
||||
#include "DeleteTextTransaction.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/EditorBase.h"
|
||||
#include "mozilla/dom/Selection.h"
|
||||
#include "mozilla/mozalloc.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsError.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIContentIterator.h"
|
||||
#include "nsIDOMCharacterData.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsAString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
// note that aEditorBase is not refcounted
|
||||
DeleteRangeTransaction::DeleteRangeTransaction()
|
||||
: mEditorBase(nullptr)
|
||||
, mRangeUpdater(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
|
||||
EditAggregateTransaction,
|
||||
mRange)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
|
||||
|
||||
nsresult
|
||||
DeleteRangeTransaction::Init(EditorBase* aEditorBase,
|
||||
nsRange* aRange,
|
||||
RangeUpdater* aRangeUpdater)
|
||||
{
|
||||
MOZ_ASSERT(aEditorBase && aRange);
|
||||
|
||||
mEditorBase = aEditorBase;
|
||||
mRange = aRange->CloneRange();
|
||||
mRangeUpdater = aRangeUpdater;
|
||||
|
||||
NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetStartParent()),
|
||||
NS_ERROR_FAILURE);
|
||||
NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetEndParent()),
|
||||
NS_ERROR_FAILURE);
|
||||
NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetCommonAncestor()),
|
||||
NS_ERROR_FAILURE);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteRangeTransaction::DoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mRange && mEditorBase);
|
||||
|
||||
// build the child transactions
|
||||
nsCOMPtr<nsINode> startParent = mRange->GetStartParent();
|
||||
int32_t startOffset = mRange->StartOffset();
|
||||
nsCOMPtr<nsINode> endParent = mRange->GetEndParent();
|
||||
int32_t endOffset = mRange->EndOffset();
|
||||
MOZ_ASSERT(startParent && endParent);
|
||||
|
||||
if (startParent == endParent) {
|
||||
// the selection begins and ends in the same node
|
||||
nsresult rv =
|
||||
CreateTxnsToDeleteBetween(startParent, startOffset, endOffset);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
// the selection ends in a different node from where it started. delete
|
||||
// the relevant content in the start node
|
||||
nsresult rv =
|
||||
CreateTxnsToDeleteContent(startParent, startOffset, nsIEditor::eNext);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// delete the intervening nodes
|
||||
rv = CreateTxnsToDeleteNodesBetween();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// delete the relevant content in the end node
|
||||
rv = CreateTxnsToDeleteContent(endParent, endOffset, nsIEditor::ePrevious);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// if we've successfully built this aggregate transaction, then do it.
|
||||
nsresult rv = EditAggregateTransaction::DoTransaction();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// only set selection to deletion point if editor gives permission
|
||||
bool bAdjustSelection;
|
||||
mEditorBase->ShouldTxnSetSelection(&bAdjustSelection);
|
||||
if (bAdjustSelection) {
|
||||
RefPtr<Selection> selection = mEditorBase->GetSelection();
|
||||
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
|
||||
rv = selection->Collapse(startParent, startOffset);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
// else do nothing - dom range gravity will adjust selection
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteRangeTransaction::UndoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mRange && mEditorBase);
|
||||
|
||||
return EditAggregateTransaction::UndoTransaction();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteRangeTransaction::RedoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mRange && mEditorBase);
|
||||
|
||||
return EditAggregateTransaction::RedoTransaction();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteRangeTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("DeleteRangeTransaction");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DeleteRangeTransaction::CreateTxnsToDeleteBetween(nsINode* aNode,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset)
|
||||
{
|
||||
// see what kind of node we have
|
||||
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
|
||||
// if the node is a chardata node, then delete chardata content
|
||||
int32_t numToDel;
|
||||
if (aStartOffset == aEndOffset) {
|
||||
numToDel = 1;
|
||||
} else {
|
||||
numToDel = aEndOffset - aStartOffset;
|
||||
}
|
||||
|
||||
RefPtr<nsGenericDOMDataNode> charDataNode =
|
||||
static_cast<nsGenericDOMDataNode*>(aNode);
|
||||
|
||||
RefPtr<DeleteTextTransaction> transaction =
|
||||
new DeleteTextTransaction(*mEditorBase, *charDataNode, aStartOffset,
|
||||
numToDel, mRangeUpdater);
|
||||
|
||||
nsresult rv = transaction->Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
AppendChild(transaction);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContent> child = aNode->GetChildAt(aStartOffset);
|
||||
NS_ENSURE_STATE(child);
|
||||
|
||||
// XXX This looks odd. Only when the last transaction causes error at
|
||||
// calling Init(), the result becomes error. Otherwise, always NS_OK.
|
||||
nsresult rv = NS_OK;
|
||||
for (int32_t i = aStartOffset; i < aEndOffset; ++i) {
|
||||
RefPtr<DeleteNodeTransaction> transaction = new DeleteNodeTransaction();
|
||||
rv = transaction->Init(mEditorBase, child, mRangeUpdater);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
AppendChild(transaction);
|
||||
}
|
||||
|
||||
child = child->GetNextSibling();
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DeleteRangeTransaction::CreateTxnsToDeleteContent(nsINode* aNode,
|
||||
int32_t aOffset,
|
||||
nsIEditor::EDirection aAction)
|
||||
{
|
||||
// see what kind of node we have
|
||||
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
|
||||
// if the node is a chardata node, then delete chardata content
|
||||
uint32_t start, numToDelete;
|
||||
if (nsIEditor::eNext == aAction) {
|
||||
start = aOffset;
|
||||
numToDelete = aNode->Length() - aOffset;
|
||||
} else {
|
||||
start = 0;
|
||||
numToDelete = aOffset;
|
||||
}
|
||||
|
||||
if (numToDelete) {
|
||||
RefPtr<nsGenericDOMDataNode> dataNode =
|
||||
static_cast<nsGenericDOMDataNode*>(aNode);
|
||||
RefPtr<DeleteTextTransaction> transaction =
|
||||
new DeleteTextTransaction(*mEditorBase, *dataNode, start, numToDelete,
|
||||
mRangeUpdater);
|
||||
|
||||
nsresult rv = transaction->Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
AppendChild(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween()
|
||||
{
|
||||
nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
|
||||
|
||||
nsresult rv = iter->Init(mRange);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
while (!iter->IsDone()) {
|
||||
nsCOMPtr<nsINode> node = iter->GetCurrentNode();
|
||||
NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
|
||||
|
||||
RefPtr<DeleteNodeTransaction> transaction = new DeleteNodeTransaction();
|
||||
rv = transaction->Init(mEditorBase, node, mRangeUpdater);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
AppendChild(transaction);
|
||||
|
||||
iter->Next();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
78
editor/libeditor/DeleteRangeTransaction.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef DeleteRangeTransaction_h
|
||||
#define DeleteRangeTransaction_h
|
||||
|
||||
#include "EditAggregateTransaction.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsID.h"
|
||||
#include "nsIEditor.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsRange.h"
|
||||
#include "nscore.h"
|
||||
|
||||
class nsINode;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class EditorBase;
|
||||
class RangeUpdater;
|
||||
|
||||
/**
|
||||
* A transaction that deletes an entire range in the content tree
|
||||
*/
|
||||
class DeleteRangeTransaction final : public EditAggregateTransaction
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initialize the transaction.
|
||||
* @param aEditorBase The object providing basic editing operations.
|
||||
* @param aRange The range to delete.
|
||||
*/
|
||||
nsresult Init(EditorBase* aEditorBase,
|
||||
nsRange* aRange,
|
||||
RangeUpdater* aRangeUpdater);
|
||||
|
||||
DeleteRangeTransaction();
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteRangeTransaction,
|
||||
EditAggregateTransaction)
|
||||
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
NS_IMETHOD RedoTransaction() override;
|
||||
|
||||
virtual void LastRelease() override
|
||||
{
|
||||
mRange = nullptr;
|
||||
EditAggregateTransaction::LastRelease();
|
||||
}
|
||||
|
||||
protected:
|
||||
nsresult CreateTxnsToDeleteBetween(nsINode* aNode,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset);
|
||||
|
||||
nsresult CreateTxnsToDeleteNodesBetween();
|
||||
|
||||
nsresult CreateTxnsToDeleteContent(nsINode* aParent,
|
||||
int32_t aOffset,
|
||||
nsIEditor::EDirection aAction);
|
||||
|
||||
// P1 in the range.
|
||||
RefPtr<nsRange> mRange;
|
||||
|
||||
// The editor for this transaction.
|
||||
EditorBase* mEditorBase;
|
||||
|
||||
// Range updater object.
|
||||
RangeUpdater* mRangeUpdater;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef DeleteRangeTransaction_h
|
||||
102
editor/libeditor/DeleteTextTransaction.cpp
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "DeleteTextTransaction.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/EditorBase.h"
|
||||
#include "mozilla/SelectionState.h"
|
||||
#include "mozilla/dom/Selection.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsError.h"
|
||||
#include "nsIEditor.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsAString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
DeleteTextTransaction::DeleteTextTransaction(
|
||||
EditorBase& aEditorBase,
|
||||
nsGenericDOMDataNode& aCharData,
|
||||
uint32_t aOffset,
|
||||
uint32_t aNumCharsToDelete,
|
||||
RangeUpdater* aRangeUpdater)
|
||||
: mEditorBase(aEditorBase)
|
||||
, mCharData(&aCharData)
|
||||
, mOffset(aOffset)
|
||||
, mNumCharsToDelete(aNumCharsToDelete)
|
||||
, mRangeUpdater(aRangeUpdater)
|
||||
{
|
||||
NS_ASSERTION(mCharData->Length() >= aOffset + aNumCharsToDelete,
|
||||
"Trying to delete more characters than in node");
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTransaction, EditTransactionBase,
|
||||
mCharData)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteTextTransaction)
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
||||
|
||||
nsresult
|
||||
DeleteTextTransaction::Init()
|
||||
{
|
||||
// Do nothing if the node is read-only
|
||||
if (!mEditorBase.IsModifiableNode(mCharData)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteTextTransaction::DoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mCharData);
|
||||
|
||||
// Get the text that we're about to delete
|
||||
nsresult rv = mCharData->SubstringData(mOffset, mNumCharsToDelete,
|
||||
mDeletedText);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
rv = mCharData->DeleteData(mOffset, mNumCharsToDelete);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mRangeUpdater) {
|
||||
mRangeUpdater->SelAdjDeleteText(mCharData, mOffset, mNumCharsToDelete);
|
||||
}
|
||||
|
||||
// Only set selection to deletion point if editor gives permission
|
||||
if (mEditorBase.GetShouldTxnSetSelection()) {
|
||||
RefPtr<Selection> selection = mEditorBase.GetSelection();
|
||||
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
|
||||
rv = selection->Collapse(mCharData, mOffset);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Selection could not be collapsed after undo of deletetext");
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
// Else do nothing - DOM Range gravity will adjust selection
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//XXX: We may want to store the selection state and restore it properly. Was
|
||||
// it an insertion point or an extended selection?
|
||||
NS_IMETHODIMP
|
||||
DeleteTextTransaction::UndoTransaction()
|
||||
{
|
||||
MOZ_ASSERT(mCharData);
|
||||
|
||||
return mCharData->InsertData(mOffset, mDeletedText);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DeleteTextTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("DeleteTextTransaction: ");
|
||||
aString += mDeletedText;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
76
editor/libeditor/DeleteTextTransaction.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef DeleteTextTransaction_h
|
||||
#define DeleteTextTransaction_h
|
||||
|
||||
#include "mozilla/EditTransactionBase.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsGenericDOMDataNode.h"
|
||||
#include "nsID.h"
|
||||
#include "nsString.h"
|
||||
#include "nscore.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class EditorBase;
|
||||
class RangeUpdater;
|
||||
|
||||
/**
|
||||
* A transaction that removes text from a content node.
|
||||
*/
|
||||
class DeleteTextTransaction final : public EditTransactionBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initialize the transaction.
|
||||
* @param aEditorBase The provider of basic editing operations.
|
||||
* @param aElement The content node to remove text from.
|
||||
* @param aOffset The location in aElement to begin the deletion.
|
||||
* @param aNumCharsToDelete The number of characters to delete. Not the
|
||||
* number of bytes!
|
||||
*/
|
||||
DeleteTextTransaction(EditorBase& aEditorBase,
|
||||
nsGenericDOMDataNode& aCharData,
|
||||
uint32_t aOffset,
|
||||
uint32_t aNumCharsToDelete,
|
||||
RangeUpdater* aRangeUpdater);
|
||||
|
||||
nsresult Init();
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteTextTransaction,
|
||||
EditTransactionBase)
|
||||
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
uint32_t GetOffset() { return mOffset; }
|
||||
|
||||
uint32_t GetNumCharsToDelete() { return mNumCharsToDelete; }
|
||||
|
||||
protected:
|
||||
// The provider of basic editing operations.
|
||||
EditorBase& mEditorBase;
|
||||
|
||||
// The CharacterData node to operate upon.
|
||||
RefPtr<nsGenericDOMDataNode> mCharData;
|
||||
|
||||
// The offset into mCharData where the deletion is to take place.
|
||||
uint32_t mOffset;
|
||||
|
||||
// The number of characters to delete.
|
||||
uint32_t mNumCharsToDelete;
|
||||
|
||||
// The text that was deleted.
|
||||
nsString mDeletedText;
|
||||
|
||||
// Range updater object.
|
||||
RangeUpdater* mRangeUpdater;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef DeleteTextTransaction_h
|
||||
17
editor/libeditor/EditActionListener.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef __editActionListener_h__
|
||||
#define __editActionListener_h__
|
||||
|
||||
class EditActionListener
|
||||
{
|
||||
public:
|
||||
|
||||
virtual void EditAction() = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif /* __editActionListener_h__ */
|
||||
145
editor/libeditor/EditAggregateTransaction.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "EditAggregateTransaction.h"
|
||||
#include "nsAString.h"
|
||||
#include "nsCOMPtr.h" // for nsCOMPtr
|
||||
#include "nsError.h" // for NS_OK, etc.
|
||||
#include "nsISupportsUtils.h" // for NS_ADDREF
|
||||
#include "nsITransaction.h" // for nsITransaction
|
||||
#include "nsString.h" // for nsAutoString
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
EditAggregateTransaction::EditAggregateTransaction()
|
||||
{
|
||||
}
|
||||
|
||||
EditAggregateTransaction::~EditAggregateTransaction()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(EditAggregateTransaction,
|
||||
EditTransactionBase,
|
||||
mChildren)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(EditAggregateTransaction, EditTransactionBase)
|
||||
NS_IMPL_RELEASE_INHERITED(EditAggregateTransaction, EditTransactionBase)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditAggregateTransaction)
|
||||
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditAggregateTransaction::DoTransaction()
|
||||
{
|
||||
// FYI: It's legal (but not very useful) to have an empty child list.
|
||||
for (uint32_t i = 0, length = mChildren.Length(); i < length; ++i) {
|
||||
nsITransaction *txn = mChildren[i];
|
||||
if (!txn) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
nsresult rv = txn->DoTransaction();
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditAggregateTransaction::UndoTransaction()
|
||||
{
|
||||
// FYI: It's legal (but not very useful) to have an empty child list.
|
||||
// Undo goes through children backwards.
|
||||
for (uint32_t i = mChildren.Length(); i--; ) {
|
||||
nsITransaction *txn = mChildren[i];
|
||||
if (!txn) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
nsresult rv = txn->UndoTransaction();
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditAggregateTransaction::RedoTransaction()
|
||||
{
|
||||
// It's legal (but not very useful) to have an empty child list.
|
||||
for (uint32_t i = 0, length = mChildren.Length(); i < length; ++i) {
|
||||
nsITransaction *txn = mChildren[i];
|
||||
if (!txn) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
nsresult rv = txn->RedoTransaction();
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditAggregateTransaction::Merge(nsITransaction* aTransaction,
|
||||
bool* aDidMerge)
|
||||
{
|
||||
if (aDidMerge) {
|
||||
*aDidMerge = false;
|
||||
}
|
||||
if (mChildren.IsEmpty()) {
|
||||
return NS_OK;
|
||||
}
|
||||
// FIXME: Is this really intended not to loop? It looks like the code
|
||||
// that used to be here sort of intended to loop, but didn't.
|
||||
nsITransaction *txn = mChildren[0];
|
||||
if (!txn) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
return txn->Merge(aTransaction, aDidMerge);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditAggregateTransaction::GetTxnDescription(nsAString& aString)
|
||||
{
|
||||
aString.AssignLiteral("EditAggregateTransaction: ");
|
||||
|
||||
if (mName) {
|
||||
nsAutoString name;
|
||||
mName->ToString(name);
|
||||
aString += name;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditAggregateTransaction::AppendChild(EditTransactionBase* aTransaction)
|
||||
{
|
||||
if (!aTransaction) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
RefPtr<EditTransactionBase>* slot = mChildren.AppendElement();
|
||||
if (!slot) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
*slot = aTransaction;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditAggregateTransaction::GetName(nsIAtom** aName)
|
||||
{
|
||||
if (aName && mName) {
|
||||
*aName = mName;
|
||||
NS_ADDREF(*aName);
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
58
editor/libeditor/EditAggregateTransaction.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef EditAggregateTransaction_h
|
||||
#define EditAggregateTransaction_h
|
||||
|
||||
#include "mozilla/EditTransactionBase.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIAtom.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nscore.h"
|
||||
|
||||
class nsITransaction;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* base class for all document editing transactions that require aggregation.
|
||||
* provides a list of child transactions.
|
||||
*/
|
||||
class EditAggregateTransaction : public EditTransactionBase
|
||||
{
|
||||
public:
|
||||
EditAggregateTransaction();
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(EditAggregateTransaction,
|
||||
EditTransactionBase)
|
||||
|
||||
NS_DECL_EDITTRANSACTIONBASE
|
||||
|
||||
NS_IMETHOD RedoTransaction() override;
|
||||
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
|
||||
|
||||
/**
|
||||
* Append a transaction to this aggregate.
|
||||
*/
|
||||
NS_IMETHOD AppendChild(EditTransactionBase* aTransaction);
|
||||
|
||||
/**
|
||||
* Get the name assigned to this transaction.
|
||||
*/
|
||||
NS_IMETHOD GetName(nsIAtom** aName);
|
||||
|
||||
protected:
|
||||
virtual ~EditAggregateTransaction();
|
||||
|
||||
nsTArray<RefPtr<EditTransactionBase>> mChildren;
|
||||
nsCOMPtr<nsIAtom> mName;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef EditAggregateTransaction_h
|
||||
55
editor/libeditor/EditTransactionBase.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "mozilla/EditTransactionBase.h"
|
||||
#include "nsError.h"
|
||||
#include "nsISupportsBase.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(EditTransactionBase)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_0(EditTransactionBase)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EditTransactionBase)
|
||||
// We don't have anything to traverse, but some of our subclasses do.
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditTransactionBase)
|
||||
NS_INTERFACE_MAP_ENTRY(nsITransaction)
|
||||
NS_INTERFACE_MAP_ENTRY(nsPIEditorTransaction)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransaction)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(EditTransactionBase)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(EditTransactionBase,
|
||||
LastRelease())
|
||||
|
||||
EditTransactionBase::~EditTransactionBase()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditTransactionBase::RedoTransaction()
|
||||
{
|
||||
return DoTransaction();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditTransactionBase::GetIsTransient(bool* aIsTransient)
|
||||
{
|
||||
*aIsTransient = false;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditTransactionBase::Merge(nsITransaction* aTransaction, bool* aDidMerge)
|
||||
{
|
||||
*aDidMerge = false;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
44
editor/libeditor/EditTransactionBase.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_EditTransactionBase_h
|
||||
#define mozilla_EditTransactionBase_h
|
||||
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsITransaction.h"
|
||||
#include "nsPIEditorTransaction.h"
|
||||
#include "nscore.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* Base class for all document editing transactions.
|
||||
*/
|
||||
class EditTransactionBase : public nsITransaction
|
||||
, public nsPIEditorTransaction
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EditTransactionBase, nsITransaction)
|
||||
|
||||
virtual void LastRelease() {}
|
||||
|
||||
NS_IMETHOD RedoTransaction(void) override;
|
||||
NS_IMETHOD GetIsTransient(bool* aIsTransient) override;
|
||||
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
|
||||
|
||||
protected:
|
||||
virtual ~EditTransactionBase();
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#define NS_DECL_EDITTRANSACTIONBASE \
|
||||
NS_IMETHOD DoTransaction() override; \
|
||||
NS_IMETHOD UndoTransaction() override; \
|
||||
NS_IMETHOD GetTxnDescription(nsAString& aTransactionDescription) override;
|
||||
|
||||
#endif // #ifndef mozilla_EditTransactionBase_h
|
||||