import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

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

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

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

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

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

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

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

View 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

View file

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

View 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
View 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']

View 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;
}

View 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

File diff suppressed because it is too large Load diff

View 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_

View 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;
}

View 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__

View 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;
}

View 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__ */

View 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;
}

View 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;

File diff suppressed because it is too large Load diff

View 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__

File diff suppressed because it is too large Load diff

View 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___

View 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;
};

View 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");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

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

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

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

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<textarea id="textarea" lang="testing-XXX"></textarea>
</body>
</html>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<textarea id="textarea" lang="en"></textarea>
</body>
</html>

View file

@ -0,0 +1,5 @@
[DEFAULT]
skip-if = os == 'android'
[test_bug434998.xul]
[test_bug1266815.html]

View file

@ -0,0 +1,2 @@
# Affix file for German English dictionary
# Fake file, nothing here.

View file

@ -0,0 +1,6 @@
5
ein
guter
heute
ist
Tag

View file

@ -0,0 +1,2 @@
# Affix file for British English dictionary
# Fake file, nothing here.

View file

@ -0,0 +1,4 @@
3
Mary
Paul
Peter

View file

@ -0,0 +1,2 @@
# Affix file for British English dictionary
# Fake file, nothing here.

View file

@ -0,0 +1,4 @@
3
Mary
Paul
Peter

View 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'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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__ */

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more