summaryrefslogtreecommitdiffstats
path: root/editor/composer
diff options
context:
space:
mode:
Diffstat (limited to 'editor/composer')
-rw-r--r--editor/composer/crashtests/351236-1.html37
-rw-r--r--editor/composer/crashtests/407062-1.html20
-rw-r--r--editor/composer/crashtests/419563-1.xhtml20
-rw-r--r--editor/composer/crashtests/428844-1-inner.xhtml4
-rw-r--r--editor/composer/crashtests/428844-1.html17
-rw-r--r--editor/composer/crashtests/461049-1.html25
-rw-r--r--editor/composer/crashtests/crashtests.list6
-rw-r--r--editor/composer/crashtests/removing-editable-xslt-inner.xhtml4
-rw-r--r--editor/composer/crashtests/removing-editable-xslt.html17
-rw-r--r--editor/composer/moz.build53
-rw-r--r--editor/composer/nsComposeTxtSrvFilter.cpp62
-rw-r--r--editor/composer/nsComposeTxtSrvFilter.h55
-rw-r--r--editor/composer/nsComposerCommands.cpp1525
-rw-r--r--editor/composer/nsComposerCommands.h281
-rw-r--r--editor/composer/nsComposerCommandsUpdater.cpp381
-rw-r--r--editor/composer/nsComposerCommandsUpdater.h102
-rw-r--r--editor/composer/nsComposerController.cpp140
-rw-r--r--editor/composer/nsComposerController.h34
-rw-r--r--editor/composer/nsComposerDocumentCommands.cpp480
-rw-r--r--editor/composer/nsComposerRegistration.cpp226
-rw-r--r--editor/composer/nsEditingSession.cpp1392
-rw-r--r--editor/composer/nsEditingSession.h147
-rw-r--r--editor/composer/nsEditorSpellCheck.cpp1005
-rw-r--r--editor/composer/nsEditorSpellCheck.h85
-rw-r--r--editor/composer/nsIEditingSession.idl104
-rw-r--r--editor/composer/res/EditorOverride.css323
-rw-r--r--editor/composer/res/grabber.gifbin0 -> 858 bytes
-rw-r--r--editor/composer/res/table-add-column-after-active.gifbin0 -> 58 bytes
-rw-r--r--editor/composer/res/table-add-column-after-hover.gifbin0 -> 826 bytes
-rw-r--r--editor/composer/res/table-add-column-after.gifbin0 -> 826 bytes
-rw-r--r--editor/composer/res/table-add-column-before-active.gifbin0 -> 57 bytes
-rw-r--r--editor/composer/res/table-add-column-before-hover.gifbin0 -> 825 bytes
-rw-r--r--editor/composer/res/table-add-column-before.gifbin0 -> 825 bytes
-rw-r--r--editor/composer/res/table-add-row-after-active.gifbin0 -> 57 bytes
-rw-r--r--editor/composer/res/table-add-row-after-hover.gifbin0 -> 826 bytes
-rw-r--r--editor/composer/res/table-add-row-after.gifbin0 -> 826 bytes
-rw-r--r--editor/composer/res/table-add-row-before-active.gifbin0 -> 57 bytes
-rw-r--r--editor/composer/res/table-add-row-before-hover.gifbin0 -> 825 bytes
-rw-r--r--editor/composer/res/table-add-row-before.gifbin0 -> 825 bytes
-rw-r--r--editor/composer/res/table-remove-column-active.gifbin0 -> 835 bytes
-rw-r--r--editor/composer/res/table-remove-column-hover.gifbin0 -> 841 bytes
-rw-r--r--editor/composer/res/table-remove-column.gifbin0 -> 841 bytes
-rw-r--r--editor/composer/res/table-remove-row-active.gifbin0 -> 835 bytes
-rw-r--r--editor/composer/res/table-remove-row-hover.gifbin0 -> 841 bytes
-rw-r--r--editor/composer/res/table-remove-row.gifbin0 -> 841 bytes
-rw-r--r--editor/composer/test/bug1200533_subframe.html14
-rw-r--r--editor/composer/test/bug1204147_subframe.html11
-rw-r--r--editor/composer/test/bug1204147_subframe2.html9
-rw-r--r--editor/composer/test/bug678842_subframe.html8
-rw-r--r--editor/composer/test/bug717433_subframe.html8
-rw-r--r--editor/composer/test/chrome.ini5
-rw-r--r--editor/composer/test/de-DE/de_DE.aff2
-rw-r--r--editor/composer/test/de-DE/de_DE.dic6
-rw-r--r--editor/composer/test/en-AU/en_AU.aff2
-rw-r--r--editor/composer/test/en-AU/en_AU.dic4
-rw-r--r--editor/composer/test/en-GB/en_GB.aff2
-rw-r--r--editor/composer/test/en-GB/en_GB.dic4
-rw-r--r--editor/composer/test/mochitest.ini40
-rw-r--r--editor/composer/test/test_async_UpdateCurrentDictionary.html75
-rw-r--r--editor/composer/test/test_bug1200533.html139
-rw-r--r--editor/composer/test/test_bug1204147.html112
-rw-r--r--editor/composer/test/test_bug1205983.html137
-rw-r--r--editor/composer/test/test_bug1209414.html150
-rw-r--r--editor/composer/test/test_bug1219928.html76
-rw-r--r--editor/composer/test/test_bug1266815.html82
-rw-r--r--editor/composer/test/test_bug338427.html61
-rw-r--r--editor/composer/test/test_bug348497.html36
-rw-r--r--editor/composer/test/test_bug384147.html204
-rw-r--r--editor/composer/test/test_bug389350.html33
-rw-r--r--editor/composer/test/test_bug434998.xul109
-rw-r--r--editor/composer/test/test_bug519928.html123
-rw-r--r--editor/composer/test/test_bug678842.html105
-rw-r--r--editor/composer/test/test_bug697981.html133
-rw-r--r--editor/composer/test/test_bug717433.html107
-rw-r--r--editor/composer/test/test_bug738440.html37
75 files changed, 8379 insertions, 0 deletions
diff --git a/editor/composer/crashtests/351236-1.html b/editor/composer/crashtests/351236-1.html
new file mode 100644
index 000000000..22133a215
--- /dev/null
+++ b/editor/composer/crashtests/351236-1.html
@@ -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> \ No newline at end of file
diff --git a/editor/composer/crashtests/407062-1.html b/editor/composer/crashtests/407062-1.html
new file mode 100644
index 000000000..81083c235
--- /dev/null
+++ b/editor/composer/crashtests/407062-1.html
@@ -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>
diff --git a/editor/composer/crashtests/419563-1.xhtml b/editor/composer/crashtests/419563-1.xhtml
new file mode 100644
index 000000000..417530c13
--- /dev/null
+++ b/editor/composer/crashtests/419563-1.xhtml
@@ -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>
diff --git a/editor/composer/crashtests/428844-1-inner.xhtml b/editor/composer/crashtests/428844-1-inner.xhtml
new file mode 100644
index 000000000..1cc72d085
--- /dev/null
+++ b/editor/composer/crashtests/428844-1-inner.xhtml
@@ -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>
diff --git a/editor/composer/crashtests/428844-1.html b/editor/composer/crashtests/428844-1.html
new file mode 100644
index 000000000..8bebdbf4b
--- /dev/null
+++ b/editor/composer/crashtests/428844-1.html
@@ -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>
diff --git a/editor/composer/crashtests/461049-1.html b/editor/composer/crashtests/461049-1.html
new file mode 100644
index 000000000..c25ed991f
--- /dev/null
+++ b/editor/composer/crashtests/461049-1.html
@@ -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>
diff --git a/editor/composer/crashtests/crashtests.list b/editor/composer/crashtests/crashtests.list
new file mode 100644
index 000000000..db84e0e5b
--- /dev/null
+++ b/editor/composer/crashtests/crashtests.list
@@ -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
diff --git a/editor/composer/crashtests/removing-editable-xslt-inner.xhtml b/editor/composer/crashtests/removing-editable-xslt-inner.xhtml
new file mode 100644
index 000000000..cbf206d7e
--- /dev/null
+++ b/editor/composer/crashtests/removing-editable-xslt-inner.xhtml
@@ -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>
diff --git a/editor/composer/crashtests/removing-editable-xslt.html b/editor/composer/crashtests/removing-editable-xslt.html
new file mode 100644
index 000000000..cbf104ac9
--- /dev/null
+++ b/editor/composer/crashtests/removing-editable-xslt.html
@@ -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>
diff --git a/editor/composer/moz.build b/editor/composer/moz.build
new file mode 100644
index 000000000..4db8c9130
--- /dev/null
+++ b/editor/composer/moz.build
@@ -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']
diff --git a/editor/composer/nsComposeTxtSrvFilter.cpp b/editor/composer/nsComposeTxtSrvFilter.cpp
new file mode 100644
index 000000000..ba66bca95
--- /dev/null
+++ b/editor/composer/nsComposeTxtSrvFilter.cpp
@@ -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;
+}
diff --git a/editor/composer/nsComposeTxtSrvFilter.h b/editor/composer/nsComposeTxtSrvFilter.h
new file mode 100644
index 000000000..0e5bba433
--- /dev/null
+++ b/editor/composer/nsComposeTxtSrvFilter.h
@@ -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
diff --git a/editor/composer/nsComposerCommands.cpp b/editor/composer/nsComposerCommands.cpp
new file mode 100644
index 000000000..3853604e4
--- /dev/null
+++ b/editor/composer/nsComposerCommands.cpp
@@ -0,0 +1,1525 @@
+/* -*- 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 <stdio.h> // for printf
+
+#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
+#include "nsAString.h"
+#include "nsCOMPtr.h" // for nsCOMPtr, do_QueryInterface, etc
+#include "nsComponentManagerUtils.h" // for do_CreateInstance
+#include "nsComposerCommands.h"
+#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
+#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc
+#include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::font, etc
+#include "nsIAtom.h" // for nsIAtom, etc
+#include "nsIClipboard.h" // for nsIClipboard, etc
+#include "nsICommandParams.h" // for nsICommandParams, etc
+#include "nsID.h"
+#include "nsIDOMElement.h" // for nsIDOMElement
+#include "nsIEditor.h" // for nsIEditor
+#include "nsIHTMLAbsPosEditor.h" // for nsIHTMLAbsPosEditor
+#include "nsIHTMLEditor.h" // for nsIHTMLEditor, etc
+#include "nsLiteralString.h" // for NS_LITERAL_STRING
+#include "nsReadableUtils.h" // for EmptyString
+#include "nsString.h" // for nsAutoString, nsString, etc
+#include "nsStringFwd.h" // for nsAFlatString
+
+class nsISupports;
+
+//prototype
+nsresult GetListState(nsIHTMLEditor* aEditor, bool* aMixed,
+ nsAString& aLocalName);
+nsresult RemoveOneProperty(nsIHTMLEditor* aEditor, const nsAString& aProp);
+nsresult RemoveTextProperty(nsIHTMLEditor* aEditor, const nsAString& aProp);
+nsresult SetTextProperty(nsIHTMLEditor *aEditor, const nsAString& aProp);
+
+
+//defines
+#define STATE_ENABLED "state_enabled"
+#define STATE_ALL "state_all"
+#define STATE_ANY "state_any"
+#define STATE_MIXED "state_mixed"
+#define STATE_BEGIN "state_begin"
+#define STATE_END "state_end"
+#define STATE_ATTRIBUTE "state_attribute"
+#define STATE_DATA "state_data"
+
+
+nsBaseComposerCommand::nsBaseComposerCommand()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsBaseComposerCommand, nsIControllerCommand)
+
+
+nsBaseStateUpdatingCommand::nsBaseStateUpdatingCommand(nsIAtom* aTagName)
+: nsBaseComposerCommand()
+, mTagName(aTagName)
+{
+ MOZ_ASSERT(mTagName);
+}
+
+nsBaseStateUpdatingCommand::~nsBaseStateUpdatingCommand()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsBaseStateUpdatingCommand, nsBaseComposerCommand)
+
+NS_IMETHODIMP
+nsBaseStateUpdatingCommand::IsCommandEnabled(const char *aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ if (editor)
+ return editor->GetIsSelectionEditable(outCmdEnabled);
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsBaseStateUpdatingCommand::DoCommand(const char *aCommandName,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(editor, NS_ERROR_NOT_INITIALIZED);
+
+ return ToggleState(editor);
+}
+
+NS_IMETHODIMP
+nsBaseStateUpdatingCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsBaseStateUpdatingCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ if (editor)
+ return GetCurrentState(editor, aParams);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPasteNoFormattingCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ NS_ENSURE_ARG_POINTER(outCmdEnabled);
+ *outCmdEnabled = false;
+
+ // This command is only implemented by nsIHTMLEditor, since
+ // pasting in a plaintext editor automatically only supplies
+ // "unformatted" text
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED);
+
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(htmlEditor);
+ NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG);
+
+ return editor->CanPaste(nsIClipboard::kGlobalClipboard, outCmdEnabled);
+}
+
+
+NS_IMETHODIMP
+nsPasteNoFormattingCommand::DoCommand(const char *aCommandName,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED);
+
+ return htmlEditor->PasteNoFormatting(nsIClipboard::kGlobalClipboard);
+}
+
+NS_IMETHODIMP
+nsPasteNoFormattingCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsPasteNoFormattingCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+
+ bool enabled = false;
+ nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aParams->SetBooleanValue(STATE_ENABLED, enabled);
+}
+
+nsStyleUpdatingCommand::nsStyleUpdatingCommand(nsIAtom* aTagName)
+: nsBaseStateUpdatingCommand(aTagName)
+{
+}
+
+nsresult
+nsStyleUpdatingCommand::GetCurrentState(nsIEditor *aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_INITIALIZED);
+
+ bool firstOfSelectionHasProp = false;
+ bool anyOfSelectionHasProp = false;
+ bool allOfSelectionHasProp = false;
+
+ nsresult rv = htmlEditor->GetInlineProperty(mTagName, EmptyString(),
+ EmptyString(),
+ &firstOfSelectionHasProp,
+ &anyOfSelectionHasProp,
+ &allOfSelectionHasProp);
+
+ aParams->SetBooleanValue(STATE_ENABLED, NS_SUCCEEDED(rv));
+ aParams->SetBooleanValue(STATE_ALL, allOfSelectionHasProp);
+ aParams->SetBooleanValue(STATE_ANY, anyOfSelectionHasProp);
+ aParams->SetBooleanValue(STATE_MIXED, anyOfSelectionHasProp
+ && !allOfSelectionHasProp);
+ aParams->SetBooleanValue(STATE_BEGIN, firstOfSelectionHasProp);
+ aParams->SetBooleanValue(STATE_END, allOfSelectionHasProp);//not completely accurate
+ return NS_OK;
+}
+
+nsresult
+nsStyleUpdatingCommand::ToggleState(nsIEditor *aEditor)
+{
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NO_INTERFACE);
+
+ //create some params now...
+ nsresult rv;
+ nsCOMPtr<nsICommandParams> params =
+ do_CreateInstance(NS_COMMAND_PARAMS_CONTRACTID,&rv);
+ if (NS_FAILED(rv) || !params)
+ return rv;
+
+ // tags "href" and "name" are special cases in the core editor
+ // they are used to remove named anchor/link and shouldn't be used for insertion
+ bool doTagRemoval;
+ if (mTagName == nsGkAtoms::href || mTagName == nsGkAtoms::name) {
+ doTagRemoval = true;
+ } else {
+ // check current selection; set doTagRemoval if formatting should be removed
+ rv = GetCurrentState(aEditor, params);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = params->GetBooleanValue(STATE_ALL, &doTagRemoval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (doTagRemoval) {
+ // Also remove equivalent properties (bug 317093)
+ if (mTagName == nsGkAtoms::b) {
+ rv = RemoveTextProperty(htmlEditor, NS_LITERAL_STRING("strong"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mTagName == nsGkAtoms::i) {
+ rv = RemoveTextProperty(htmlEditor, NS_LITERAL_STRING("em"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mTagName == nsGkAtoms::strike) {
+ rv = RemoveTextProperty(htmlEditor, NS_LITERAL_STRING("s"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = RemoveTextProperty(htmlEditor, nsDependentAtomString(mTagName));
+ } else {
+ // Superscript and Subscript styles are mutually exclusive
+ aEditor->BeginTransaction();
+
+ nsDependentAtomString tagName(mTagName);
+ if (mTagName == nsGkAtoms::sub || mTagName == nsGkAtoms::sup) {
+ rv = RemoveTextProperty(htmlEditor, tagName);
+ }
+ if (NS_SUCCEEDED(rv))
+ rv = SetTextProperty(htmlEditor, tagName);
+
+ aEditor->EndTransaction();
+ }
+
+ return rv;
+}
+
+nsListCommand::nsListCommand(nsIAtom* aTagName)
+: nsBaseStateUpdatingCommand(aTagName)
+{
+}
+
+nsresult
+nsListCommand::GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams)
+{
+ NS_ASSERTION(aEditor, "Need editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NO_INTERFACE);
+
+ bool bMixed;
+ nsAutoString localName;
+ nsresult rv = GetListState(htmlEditor, &bMixed, localName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool inList = mTagName->Equals(localName);
+ aParams->SetBooleanValue(STATE_ALL, !bMixed && inList);
+ aParams->SetBooleanValue(STATE_MIXED, bMixed);
+ aParams->SetBooleanValue(STATE_ENABLED, true);
+ return NS_OK;
+}
+
+nsresult
+nsListCommand::ToggleState(nsIEditor *aEditor)
+{
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(editor, NS_NOINTERFACE);
+
+ nsresult rv;
+ nsCOMPtr<nsICommandParams> params =
+ do_CreateInstance(NS_COMMAND_PARAMS_CONTRACTID,&rv);
+ if (NS_FAILED(rv) || !params)
+ return rv;
+
+ rv = GetCurrentState(aEditor, params);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool inList;
+ rv = params->GetBooleanValue(STATE_ALL,&inList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsDependentAtomString listType(mTagName);
+ if (inList) {
+ rv = editor->RemoveList(listType);
+ } else {
+ rv = editor->MakeOrChangeList(listType, false, EmptyString());
+ }
+
+ return rv;
+}
+
+nsListItemCommand::nsListItemCommand(nsIAtom* aTagName)
+: nsBaseStateUpdatingCommand(aTagName)
+{
+}
+
+nsresult
+nsListItemCommand::GetCurrentState(nsIEditor* aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need editor here");
+ // 39584
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_NOINTERFACE);
+
+ bool bMixed, bLI, bDT, bDD;
+ nsresult rv = htmlEditor->GetListItemState(&bMixed, &bLI, &bDT, &bDD);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool inList = false;
+ if (!bMixed) {
+ if (bLI) {
+ inList = mTagName == nsGkAtoms::li;
+ } else if (bDT) {
+ inList = mTagName == nsGkAtoms::dt;
+ } else if (bDD) {
+ inList = mTagName == nsGkAtoms::dd;
+ }
+ }
+
+ aParams->SetBooleanValue(STATE_ALL, !bMixed && inList);
+ aParams->SetBooleanValue(STATE_MIXED, bMixed);
+
+ return NS_OK;
+}
+
+nsresult
+nsListItemCommand::ToggleState(nsIEditor *aEditor)
+{
+ NS_ASSERTION(aEditor, "Need editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_INITIALIZED);
+
+ bool inList;
+ // Need to use mTagName????
+ nsresult rv;
+ nsCOMPtr<nsICommandParams> params =
+ do_CreateInstance(NS_COMMAND_PARAMS_CONTRACTID,&rv);
+ if (NS_FAILED(rv) || !params)
+ return rv;
+ rv = GetCurrentState(aEditor, params);
+ rv = params->GetBooleanValue(STATE_ALL,&inList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (inList) {
+ // To remove a list, first get what kind of list we're in
+ bool bMixed;
+ nsAutoString localName;
+ rv = GetListState(htmlEditor, &bMixed, localName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (localName.IsEmpty() || bMixed) {
+ return rv;
+ }
+ return htmlEditor->RemoveList(localName);
+ }
+
+ // Set to the requested paragraph type
+ //XXX Note: This actually doesn't work for "LI",
+ // but we currently don't use this for non DL lists anyway.
+ // Problem: won't this replace any current block paragraph style?
+ return htmlEditor->SetParagraphFormat(nsDependentAtomString(mTagName));
+}
+
+NS_IMETHODIMP
+nsRemoveListCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ *outCmdEnabled = false;
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(editor, NS_OK);
+
+ bool isEditable = false;
+ nsresult rv = editor->GetIsSelectionEditable(&isEditable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isEditable) {
+ return NS_OK;
+ }
+
+ // It is enabled if we are in any list type
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NO_INTERFACE);
+
+ bool bMixed;
+ nsAutoString localName;
+ rv = GetListState(htmlEditor, &bMixed, localName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *outCmdEnabled = bMixed || !localName.IsEmpty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsRemoveListCommand::DoCommand(const char *aCommandName, nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+
+ nsresult rv = NS_OK;
+ if (editor) {
+ // This removes any list type
+ rv = editor->RemoveList(EmptyString());
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsRemoveListCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsRemoveListCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled);
+}
+
+NS_IMETHODIMP
+nsIndentCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon, bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ if (editor)
+ return editor->GetIsSelectionEditable(outCmdEnabled);
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsIndentCommand::DoCommand(const char *aCommandName, nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+
+ nsresult rv = NS_OK;
+ if (editor) {
+ rv = editor->Indent(NS_LITERAL_STRING("indent"));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIndentCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsIndentCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled);
+}
+
+
+//OUTDENT
+
+NS_IMETHODIMP
+nsOutdentCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ *outCmdEnabled = false;
+
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ if (editor) {
+ nsresult rv = editor->GetIsSelectionEditable(outCmdEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsOutdentCommand::DoCommand(const char *aCommandName, nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon);
+ if (htmlEditor)
+ return htmlEditor->Indent(NS_LITERAL_STRING("outdent"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutdentCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsOutdentCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled);
+}
+
+nsMultiStateCommand::nsMultiStateCommand()
+: nsBaseComposerCommand()
+{
+}
+
+nsMultiStateCommand::~nsMultiStateCommand()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsMultiStateCommand, nsBaseComposerCommand)
+
+NS_IMETHODIMP
+nsMultiStateCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ // should be disabled sometimes, like if the current selection is an image
+ if (editor)
+ return editor->GetIsSelectionEditable(outCmdEnabled);
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMultiStateCommand::DoCommand(const char *aCommandName, nsISupports *refCon)
+{
+#ifdef DEBUG
+ printf("who is calling nsMultiStateCommand::DoCommand \
+ (no implementation)? %s\n", aCommandName);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiStateCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+
+ nsresult rv = NS_OK;
+ if (editor) {
+ nsAutoString tString;
+
+ if (aParams) {
+ nsXPIDLCString s;
+ rv = aParams->GetCStringValue(STATE_ATTRIBUTE, getter_Copies(s));
+ if (NS_SUCCEEDED(rv))
+ tString.AssignWithConversion(s);
+ else
+ rv = aParams->GetStringValue(STATE_ATTRIBUTE, tString);
+ }
+
+ rv = SetState(editor, tString);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMultiStateCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ nsresult rv = NS_OK;
+ if (editor) {
+ rv = GetCurrentState(editor, aParams);
+ }
+ return rv;
+}
+
+nsParagraphStateCommand::nsParagraphStateCommand()
+: nsMultiStateCommand()
+{
+}
+
+nsresult
+nsParagraphStateCommand::GetCurrentState(nsIEditor *aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ bool outMixed;
+ nsAutoString outStateString;
+ nsresult rv = htmlEditor->GetParagraphState(&outMixed, outStateString);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tOutStateString;
+ tOutStateString.AssignWithConversion(outStateString);
+ aParams->SetBooleanValue(STATE_MIXED,outMixed);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get());
+ }
+ return rv;
+}
+
+
+nsresult
+nsParagraphStateCommand::SetState(nsIEditor *aEditor, nsString& newState)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ return htmlEditor->SetParagraphFormat(newState);
+}
+
+nsFontFaceStateCommand::nsFontFaceStateCommand()
+: nsMultiStateCommand()
+{
+}
+
+nsresult
+nsFontFaceStateCommand::GetCurrentState(nsIEditor *aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ nsAutoString outStateString;
+ bool outMixed;
+ nsresult rv = htmlEditor->GetFontFaceState(&outMixed, outStateString);
+ if (NS_SUCCEEDED(rv)) {
+ aParams->SetBooleanValue(STATE_MIXED,outMixed);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, NS_ConvertUTF16toUTF8(outStateString).get());
+ }
+ return rv;
+}
+
+
+nsresult
+nsFontFaceStateCommand::SetState(nsIEditor *aEditor, nsString& newState)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ if (newState.EqualsLiteral("tt")) {
+ // The old "teletype" attribute
+ nsresult rv = htmlEditor->SetInlineProperty(nsGkAtoms::tt, EmptyString(),
+ EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Clear existing font face
+ return htmlEditor->RemoveInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("face"));
+ }
+
+ // Remove any existing TT nodes
+ nsresult rv = htmlEditor->RemoveInlineProperty(nsGkAtoms::tt, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newState.IsEmpty() || newState.EqualsLiteral("normal")) {
+ return htmlEditor->RemoveInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("face"));
+ }
+
+ return htmlEditor->SetInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("face"), newState);
+}
+
+nsFontSizeStateCommand::nsFontSizeStateCommand()
+ : nsMultiStateCommand()
+{
+}
+
+// nsAutoCString tOutStateString;
+// tOutStateString.AssignWithConversion(outStateString);
+nsresult
+nsFontSizeStateCommand::GetCurrentState(nsIEditor *aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_INVALID_ARG);
+
+ nsAutoString outStateString;
+ nsCOMPtr<nsIAtom> fontAtom = NS_Atomize("font");
+ bool firstHas, anyHas, allHas;
+ nsresult rv = htmlEditor->GetInlinePropertyWithAttrValue(fontAtom,
+ NS_LITERAL_STRING("size"),
+ EmptyString(),
+ &firstHas, &anyHas, &allHas,
+ outStateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tOutStateString;
+ tOutStateString.AssignWithConversion(outStateString);
+ aParams->SetBooleanValue(STATE_MIXED, anyHas && !allHas);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get());
+ aParams->SetBooleanValue(STATE_ENABLED, true);
+
+ return rv;
+}
+
+
+// acceptable values for "newState" are:
+// -2
+// -1
+// 0
+// +1
+// +2
+// +3
+// medium
+// normal
+nsresult
+nsFontSizeStateCommand::SetState(nsIEditor *aEditor, nsString& newState)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_INVALID_ARG);
+
+ if (!newState.IsEmpty() &&
+ !newState.EqualsLiteral("normal") &&
+ !newState.EqualsLiteral("medium")) {
+ return htmlEditor->SetInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("size"), newState);
+ }
+
+ // remove any existing font size, big or small
+ nsresult rv = htmlEditor->RemoveInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("size"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = htmlEditor->RemoveInlineProperty(nsGkAtoms::big, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return htmlEditor->RemoveInlineProperty(nsGkAtoms::small, EmptyString());
+}
+
+nsFontColorStateCommand::nsFontColorStateCommand()
+: nsMultiStateCommand()
+{
+}
+
+nsresult
+nsFontColorStateCommand::GetCurrentState(nsIEditor *aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ bool outMixed;
+ nsAutoString outStateString;
+ nsresult rv = htmlEditor->GetFontColorState(&outMixed, outStateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tOutStateString;
+ tOutStateString.AssignWithConversion(outStateString);
+ aParams->SetBooleanValue(STATE_MIXED, outMixed);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get());
+ return NS_OK;
+}
+
+nsresult
+nsFontColorStateCommand::SetState(nsIEditor *aEditor, nsString& newState)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ if (newState.IsEmpty() || newState.EqualsLiteral("normal")) {
+ return htmlEditor->RemoveInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("color"));
+ }
+
+ return htmlEditor->SetInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("color"), newState);
+}
+
+nsHighlightColorStateCommand::nsHighlightColorStateCommand()
+: nsMultiStateCommand()
+{
+}
+
+nsresult
+nsHighlightColorStateCommand::GetCurrentState(nsIEditor *aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ bool outMixed;
+ nsAutoString outStateString;
+ nsresult rv = htmlEditor->GetHighlightColorState(&outMixed, outStateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tOutStateString;
+ tOutStateString.AssignWithConversion(outStateString);
+ aParams->SetBooleanValue(STATE_MIXED, outMixed);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get());
+ return NS_OK;
+}
+
+nsresult
+nsHighlightColorStateCommand::SetState(nsIEditor *aEditor, nsString& newState)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ if (newState.IsEmpty() || newState.EqualsLiteral("normal")) {
+ return htmlEditor->RemoveInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("bgcolor"));
+ }
+
+ return htmlEditor->SetInlineProperty(nsGkAtoms::font,
+ NS_LITERAL_STRING("bgcolor"),
+ newState);
+}
+
+NS_IMETHODIMP
+nsHighlightColorStateCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ if (editor)
+ return editor->GetIsSelectionEditable(outCmdEnabled);
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+
+nsBackgroundColorStateCommand::nsBackgroundColorStateCommand()
+: nsMultiStateCommand()
+{
+}
+
+nsresult
+nsBackgroundColorStateCommand::GetCurrentState(nsIEditor *aEditor,
+ nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ bool outMixed;
+ nsAutoString outStateString;
+ nsresult rv = htmlEditor->GetBackgroundColorState(&outMixed, outStateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tOutStateString;
+ tOutStateString.AssignWithConversion(outStateString);
+ aParams->SetBooleanValue(STATE_MIXED, outMixed);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get());
+ return NS_OK;
+}
+
+nsresult
+nsBackgroundColorStateCommand::SetState(nsIEditor *aEditor, nsString& newState)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ return htmlEditor->SetBackgroundColor(newState);
+}
+
+nsAlignCommand::nsAlignCommand()
+: nsMultiStateCommand()
+{
+}
+
+nsresult
+nsAlignCommand::GetCurrentState(nsIEditor *aEditor, nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ nsIHTMLEditor::EAlignment firstAlign;
+ bool outMixed;
+ nsresult rv = htmlEditor->GetAlignment(&outMixed, &firstAlign);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString outStateString;
+ switch (firstAlign) {
+ default:
+ case nsIHTMLEditor::eLeft:
+ outStateString.AssignLiteral("left");
+ break;
+
+ case nsIHTMLEditor::eCenter:
+ outStateString.AssignLiteral("center");
+ break;
+
+ case nsIHTMLEditor::eRight:
+ outStateString.AssignLiteral("right");
+ break;
+
+ case nsIHTMLEditor::eJustify:
+ outStateString.AssignLiteral("justify");
+ break;
+ }
+ nsAutoCString tOutStateString;
+ tOutStateString.AssignWithConversion(outStateString);
+ aParams->SetBooleanValue(STATE_MIXED,outMixed);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get());
+ return NS_OK;
+}
+
+nsresult
+nsAlignCommand::SetState(nsIEditor *aEditor, nsString& newState)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ return htmlEditor->Align(newState);
+}
+
+nsAbsolutePositioningCommand::nsAbsolutePositioningCommand()
+: nsBaseStateUpdatingCommand(nsGkAtoms::_empty)
+{
+}
+
+NS_IMETHODIMP
+nsAbsolutePositioningCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *aCommandRefCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
+ nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(aCommandRefCon);
+ if (htmlEditor) {
+ bool isEditable = false;
+ nsresult rv = editor->GetIsSelectionEditable(&isEditable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEditable)
+ return htmlEditor->GetAbsolutePositioningEnabled(outCmdEnabled);
+ }
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+nsresult
+nsAbsolutePositioningCommand::GetCurrentState(nsIEditor *aEditor, nsICommandParams *aParams)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ bool isEnabled;
+ htmlEditor->GetAbsolutePositioningEnabled(&isEnabled);
+ if (!isEnabled) {
+ aParams->SetBooleanValue(STATE_MIXED,false);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, "");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMElement> elt;
+ nsresult rv = htmlEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString outStateString;
+ if (elt)
+ outStateString.AssignLiteral("absolute");
+
+ aParams->SetBooleanValue(STATE_MIXED,false);
+ aParams->SetCStringValue(STATE_ATTRIBUTE, NS_ConvertUTF16toUTF8(outStateString).get());
+ return NS_OK;
+}
+
+nsresult
+nsAbsolutePositioningCommand::ToggleState(nsIEditor *aEditor)
+{
+ NS_ASSERTION(aEditor, "Need an editor here");
+
+ nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(aEditor);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDOMElement> elt;
+ nsresult rv = htmlEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return htmlEditor->AbsolutePositionSelection(!elt);
+}
+
+
+NS_IMETHODIMP
+nsDecreaseZIndexCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ htmlEditor->GetAbsolutePositioningEnabled(outCmdEnabled);
+ if (!(*outCmdEnabled))
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> positionedElement;
+ htmlEditor->GetPositionedElement(getter_AddRefs(positionedElement));
+ *outCmdEnabled = false;
+ if (positionedElement) {
+ int32_t z;
+ nsresult rv = htmlEditor->GetElementZIndex(positionedElement, &z);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *outCmdEnabled = (z > 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDecreaseZIndexCommand::DoCommand(const char *aCommandName,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED);
+
+ return htmlEditor->RelativeChangeZIndex(-1);
+}
+
+NS_IMETHODIMP
+nsDecreaseZIndexCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsDecreaseZIndexCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+
+ bool enabled = false;
+ nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aParams->SetBooleanValue(STATE_ENABLED, enabled);
+}
+
+NS_IMETHODIMP
+nsIncreaseZIndexCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
+
+ htmlEditor->GetAbsolutePositioningEnabled(outCmdEnabled);
+ if (!(*outCmdEnabled))
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> positionedElement;
+ htmlEditor->GetPositionedElement(getter_AddRefs(positionedElement));
+ *outCmdEnabled = (nullptr != positionedElement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncreaseZIndexCommand::DoCommand(const char *aCommandName,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED);
+
+ return htmlEditor->RelativeChangeZIndex(1);
+}
+
+NS_IMETHODIMP
+nsIncreaseZIndexCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsIncreaseZIndexCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+
+ bool enabled = false;
+ nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aParams->SetBooleanValue(STATE_ENABLED, enabled);
+}
+
+
+NS_IMETHODIMP
+nsRemoveStylesCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ // test if we have any styles?
+ if (editor)
+ return editor->GetIsSelectionEditable(outCmdEnabled);
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsRemoveStylesCommand::DoCommand(const char *aCommandName,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+
+ nsresult rv = NS_OK;
+ if (editor) {
+ rv = editor->RemoveAllInlineProperties();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsRemoveStylesCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsRemoveStylesCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled);
+}
+
+NS_IMETHODIMP
+nsIncreaseFontSizeCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ // test if we are at max size?
+ if (editor)
+ return editor->GetIsSelectionEditable(outCmdEnabled);
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsIncreaseFontSizeCommand::DoCommand(const char *aCommandName,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+
+ nsresult rv = NS_OK;
+ if (editor) {
+ rv = editor->IncreaseFontSize();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIncreaseFontSizeCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsIncreaseFontSizeCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled);
+}
+
+NS_IMETHODIMP
+nsDecreaseFontSizeCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *refCon,
+ bool *outCmdEnabled)
+{
+ nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
+ // test if we are at min size?
+ if (editor)
+ return editor->GetIsSelectionEditable(outCmdEnabled);
+
+ *outCmdEnabled = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDecreaseFontSizeCommand::DoCommand(const char *aCommandName,
+ nsISupports *refCon)
+{
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+
+ nsresult rv = NS_OK;
+ if (editor) {
+ rv = editor->DecreaseFontSize();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDecreaseFontSizeCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ return DoCommand(aCommandName, refCon);
+}
+
+NS_IMETHODIMP
+nsDecreaseFontSizeCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled);
+}
+
+NS_IMETHODIMP
+nsInsertHTMLCommand::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
+nsInsertHTMLCommand::DoCommand(const char *aCommandName, nsISupports *refCon)
+{
+ // If nsInsertHTMLCommand is called with no parameters, it was probably called with
+ // an empty string parameter ''. In this case, it should act the same as the delete command
+ NS_ENSURE_ARG_POINTER(refCon);
+
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED);
+
+ nsString html = EmptyString();
+ return editor->InsertHTML(html);
+}
+
+NS_IMETHODIMP
+nsInsertHTMLCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+ NS_ENSURE_ARG_POINTER(refCon);
+
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED);
+
+ // Get HTML source string to insert from command params
+ nsAutoString html;
+ nsresult rv = aParams->GetStringValue(STATE_DATA, html);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return editor->InsertHTML(html);
+}
+
+NS_IMETHODIMP
+nsInsertHTMLCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+ NS_ENSURE_ARG_POINTER(refCon);
+
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsInsertTagCommand, nsBaseComposerCommand)
+
+nsInsertTagCommand::nsInsertTagCommand(nsIAtom* aTagName)
+: nsBaseComposerCommand()
+, mTagName(aTagName)
+{
+ MOZ_ASSERT(mTagName);
+}
+
+nsInsertTagCommand::~nsInsertTagCommand()
+{
+}
+
+NS_IMETHODIMP
+nsInsertTagCommand::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;
+}
+
+
+// corresponding STATE_ATTRIBUTE is: src (img) and href (a)
+NS_IMETHODIMP
+nsInsertTagCommand::DoCommand(const char *aCmdName, nsISupports *refCon)
+{
+ NS_ENSURE_TRUE(mTagName == nsGkAtoms::hr, NS_ERROR_NOT_IMPLEMENTED);
+
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED);
+
+ nsCOMPtr<nsIDOMElement> domElem;
+ nsresult rv = editor->CreateElementWithDefaults(
+ nsDependentAtomString(mTagName), getter_AddRefs(domElem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return editor->InsertElementAtSelection(domElem, true);
+}
+
+NS_IMETHODIMP
+nsInsertTagCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ NS_ENSURE_ARG_POINTER(refCon);
+
+ // inserting an hr shouldn't have an parameters, just call DoCommand for that
+ if (mTagName == nsGkAtoms::hr) {
+ return DoCommand(aCommandName, refCon);
+ }
+
+ NS_ENSURE_ARG_POINTER(aParams);
+
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon);
+ NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED);
+
+ // do we have an href to use for creating link?
+ nsXPIDLCString s;
+ nsresult rv = aParams->GetCStringValue(STATE_ATTRIBUTE, getter_Copies(s));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString attrib; attrib.AssignWithConversion(s);
+
+ if (attrib.IsEmpty())
+ return NS_ERROR_INVALID_ARG;
+
+ // filter out tags we don't know how to insert
+ nsAutoString attributeType;
+ if (mTagName == nsGkAtoms::a) {
+ attributeType.AssignLiteral("href");
+ } else if (mTagName == nsGkAtoms::img) {
+ attributeType.AssignLiteral("src");
+ } else {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIDOMElement> domElem;
+ rv = editor->CreateElementWithDefaults(nsDependentAtomString(mTagName),
+ getter_AddRefs(domElem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = domElem->SetAttribute(attributeType, attrib);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // do actual insertion
+ if (mTagName == nsGkAtoms::a)
+ return editor->InsertLinkAroundSelection(domElem);
+
+ return editor->InsertElementAtSelection(domElem, true);
+}
+
+NS_IMETHODIMP
+nsInsertTagCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *refCon)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+ NS_ENSURE_ARG_POINTER(refCon);
+
+ bool outCmdEnabled = false;
+ IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
+ return aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled);
+}
+
+
+/****************************/
+//HELPER METHODS
+/****************************/
+
+nsresult
+GetListState(nsIHTMLEditor* aEditor, bool* aMixed, nsAString& aLocalName)
+{
+ MOZ_ASSERT(aEditor);
+ MOZ_ASSERT(aMixed);
+
+ *aMixed = false;
+ aLocalName.Truncate();
+
+ bool bOL, bUL, bDL;
+ nsresult rv = aEditor->GetListState(aMixed, &bOL, &bUL, &bDL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*aMixed) {
+ return NS_OK;
+ }
+
+ if (bOL) {
+ aLocalName.AssignLiteral("ol");
+ } else if (bUL) {
+ aLocalName.AssignLiteral("ul");
+ } else if (bDL) {
+ aLocalName.AssignLiteral("dl");
+ }
+ return NS_OK;
+}
+
+nsresult
+RemoveOneProperty(nsIHTMLEditor* aEditor, const nsAString& aProp)
+{
+ MOZ_ASSERT(aEditor);
+
+ /// XXX Hack alert! Look in nsIEditProperty.h for this
+ nsCOMPtr<nsIAtom> styleAtom = NS_Atomize(aProp);
+ NS_ENSURE_TRUE(styleAtom, NS_ERROR_OUT_OF_MEMORY);
+
+ return aEditor->RemoveInlineProperty(styleAtom, EmptyString());
+}
+
+
+// the name of the attribute here should be the contents of the appropriate
+// tag, e.g. 'b' for bold, 'i' for italics.
+nsresult
+RemoveTextProperty(nsIHTMLEditor* aEditor, const nsAString& aProp)
+{
+ MOZ_ASSERT(aEditor);
+
+ if (aProp.LowerCaseEqualsLiteral("all")) {
+ return aEditor->RemoveAllInlineProperties();
+ }
+
+ return RemoveOneProperty(aEditor, aProp);
+}
+
+// the name of the attribute here should be the contents of the appropriate
+// tag, e.g. 'b' for bold, 'i' for italics.
+nsresult
+SetTextProperty(nsIHTMLEditor* aEditor, const nsAString& aProp)
+{
+ MOZ_ASSERT(aEditor);
+
+ /// XXX Hack alert! Look in nsIEditProperty.h for this
+ nsCOMPtr<nsIAtom> styleAtom = NS_Atomize(aProp);
+ NS_ENSURE_TRUE(styleAtom, NS_ERROR_OUT_OF_MEMORY);
+
+ return aEditor->SetInlineProperty(styleAtom, EmptyString(), EmptyString());
+}
diff --git a/editor/composer/nsComposerCommands.h b/editor/composer/nsComposerCommands.h
new file mode 100644
index 000000000..3d3855d40
--- /dev/null
+++ b/editor/composer/nsComposerCommands.h
@@ -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_
diff --git a/editor/composer/nsComposerCommandsUpdater.cpp b/editor/composer/nsComposerCommandsUpdater.cpp
new file mode 100644
index 000000000..4999f7b92
--- /dev/null
+++ b/editor/composer/nsComposerCommandsUpdater.cpp
@@ -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;
+}
diff --git a/editor/composer/nsComposerCommandsUpdater.h b/editor/composer/nsComposerCommandsUpdater.h
new file mode 100644
index 000000000..3b853edeb
--- /dev/null
+++ b/editor/composer/nsComposerCommandsUpdater.h
@@ -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__
diff --git a/editor/composer/nsComposerController.cpp b/editor/composer/nsComposerController.cpp
new file mode 100644
index 000000000..5d90fbff0
--- /dev/null
+++ b/editor/composer/nsComposerController.cpp
@@ -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;
+}
diff --git a/editor/composer/nsComposerController.h b/editor/composer/nsComposerController.h
new file mode 100644
index 000000000..768c67b80
--- /dev/null
+++ b/editor/composer/nsComposerController.h
@@ -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__ */
diff --git a/editor/composer/nsComposerDocumentCommands.cpp b/editor/composer/nsComposerDocumentCommands.cpp
new file mode 100644
index 000000000..d44e940f6
--- /dev/null
+++ b/editor/composer/nsComposerDocumentCommands.cpp
@@ -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;
+}
diff --git a/editor/composer/nsComposerRegistration.cpp b/editor/composer/nsComposerRegistration.cpp
new file mode 100644
index 000000000..7a0b3a440
--- /dev/null
+++ b/editor/composer/nsComposerRegistration.cpp
@@ -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;
diff --git a/editor/composer/nsEditingSession.cpp b/editor/composer/nsEditingSession.cpp
new file mode 100644
index 000000000..677b3b50f
--- /dev/null
+++ b/editor/composer/nsEditingSession.cpp
@@ -0,0 +1,1392 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* 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 <string.h> // for nullptr, strcmp
+
+#include "imgIContainer.h" // for imgIContainer, etc
+#include "mozFlushType.h" // for mozFlushType::Flush_Frames
+#include "mozilla/mozalloc.h" // for operator new
+#include "nsAString.h"
+#include "nsComponentManagerUtils.h" // for do_CreateInstance
+#include "nsComposerCommandsUpdater.h" // for nsComposerCommandsUpdater
+#include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc
+#include "nsEditingSession.h"
+#include "nsError.h" // for NS_ERROR_FAILURE, NS_OK, etc
+#include "nsIChannel.h" // for nsIChannel
+#include "nsICommandManager.h" // for nsICommandManager
+#include "nsIContentViewer.h" // for nsIContentViewer
+#include "nsIController.h" // for nsIController
+#include "nsIControllerContext.h" // for nsIControllerContext
+#include "nsIControllers.h" // for nsIControllers
+#include "nsID.h" // for NS_GET_IID, etc
+#include "nsIDOMDocument.h" // for nsIDOMDocument
+#include "nsIDOMHTMLDocument.h" // for nsIDOMHTMLDocument
+#include "nsIDOMWindow.h" // for nsIDOMWindow
+#include "nsIDocShell.h" // for nsIDocShell
+#include "nsIDocument.h" // for nsIDocument
+#include "nsIDocumentStateListener.h"
+#include "nsIEditor.h" // for nsIEditor
+#include "nsIHTMLDocument.h" // for nsIHTMLDocument, etc
+#include "nsIInterfaceRequestorUtils.h" // for do_GetInterface
+#include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc
+#include "nsIPresShell.h" // for nsIPresShell
+#include "nsIRefreshURI.h" // for nsIRefreshURI
+#include "nsIRequest.h" // for nsIRequest
+#include "nsISelection.h" // for nsISelection
+#include "nsISelectionPrivate.h" // for nsISelectionPrivate
+#include "nsITimer.h" // for nsITimer, etc
+#include "nsITransactionManager.h" // for nsITransactionManager
+#include "nsIWeakReference.h" // for nsISupportsWeakReference, etc
+#include "nsIWebNavigation.h" // for nsIWebNavigation
+#include "nsIWebProgress.h" // for nsIWebProgress, etc
+#include "nsLiteralString.h" // for NS_LITERAL_STRING
+#include "nsPICommandUpdater.h" // for nsPICommandUpdater
+#include "nsPIDOMWindow.h" // for nsPIDOMWindow
+#include "nsPresContext.h" // for nsPresContext
+#include "nsReadableUtils.h" // for AppendUTF16toUTF8
+#include "nsStringFwd.h" // for nsAFlatString
+#include "mozilla/dom/Selection.h" // for AutoHideSelectionChanges
+#include "nsFrameSelection.h" // for nsFrameSelection
+
+class nsISupports;
+class nsIURI;
+
+/*---------------------------------------------------------------------------
+
+ nsEditingSession
+
+----------------------------------------------------------------------------*/
+nsEditingSession::nsEditingSession()
+: mDoneSetup(false)
+, mCanCreateEditor(false)
+, mInteractive(false)
+, mMakeWholeDocumentEditable(true)
+, mDisabledJSAndPlugins(false)
+, mScriptsEnabled(true)
+, mPluginsEnabled(true)
+, mProgressListenerRegistered(false)
+, mImageAnimationMode(0)
+, mEditorFlags(0)
+, mEditorStatus(eEditorOK)
+, mBaseCommandControllerId(0)
+, mDocStateControllerId(0)
+, mHTMLCommandControllerId(0)
+{
+}
+
+/*---------------------------------------------------------------------------
+
+ ~nsEditingSession
+
+----------------------------------------------------------------------------*/
+nsEditingSession::~nsEditingSession()
+{
+ // Must cancel previous timer?
+ if (mLoadBlankDocTimer)
+ mLoadBlankDocTimer->Cancel();
+}
+
+NS_IMPL_ISUPPORTS(nsEditingSession, nsIEditingSession, nsIWebProgressListener,
+ nsISupportsWeakReference)
+
+/*---------------------------------------------------------------------------
+
+ MakeWindowEditable
+
+ aEditorType string, "html" "htmlsimple" "text" "textsimple"
+ void makeWindowEditable(in nsIDOMWindow aWindow, in string aEditorType,
+ in boolean aDoAfterUriLoad,
+ in boolean aMakeWholeDocumentEditable,
+ in boolean aInteractive);
+----------------------------------------------------------------------------*/
+#define DEFAULT_EDITOR_TYPE "html"
+
+NS_IMETHODIMP
+nsEditingSession::MakeWindowEditable(mozIDOMWindowProxy* aWindow,
+ const char *aEditorType,
+ bool aDoAfterUriLoad,
+ bool aMakeWholeDocumentEditable,
+ bool aInteractive)
+{
+ mEditorType.Truncate();
+ mEditorFlags = 0;
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
+ auto* window = nsPIDOMWindowOuter::From(aWindow);
+
+ // disable plugins
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ mDocShell = do_GetWeakReference(docShell);
+ mInteractive = aInteractive;
+ mMakeWholeDocumentEditable = aMakeWholeDocumentEditable;
+
+ nsresult rv;
+ if (!mInteractive) {
+ rv = DisableJSAndPlugins(aWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Always remove existing editor
+ TearDownEditorOnWindow(aWindow);
+
+ // Tells embedder that startup is in progress
+ mEditorStatus = eEditorCreationInProgress;
+
+ //temporary to set editor type here. we will need different classes soon.
+ if (!aEditorType)
+ aEditorType = DEFAULT_EDITOR_TYPE;
+ mEditorType = aEditorType;
+
+ // if all this does is setup listeners and I don't need listeners,
+ // can't this step be ignored?? (based on aDoAfterURILoad)
+ rv = PrepareForEditing(window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set the flag on the docShell to say that it's editable
+ rv = docShell->MakeEditable(aDoAfterUriLoad);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setup commands common to plaintext and html editors,
+ // including the document creation observers
+ // the first is an editing controller
+ rv = SetupEditorCommandController("@mozilla.org/editor/editingcontroller;1",
+ aWindow,
+ static_cast<nsIEditingSession*>(this),
+ &mBaseCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The second is a controller to monitor doc state,
+ // such as creation and "dirty flag"
+ rv = SetupEditorCommandController("@mozilla.org/editor/editordocstatecontroller;1",
+ aWindow,
+ static_cast<nsIEditingSession*>(this),
+ &mDocStateControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // aDoAfterUriLoad can be false only when making an existing window editable
+ if (!aDoAfterUriLoad) {
+ rv = SetupEditorOnWindow(aWindow);
+
+ // mEditorStatus is set to the error reason
+ // Since this is used only when editing an existing page,
+ // it IS ok to destroy current editor
+ if (NS_FAILED(rv)) {
+ TearDownEditorOnWindow(aWindow);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsEditingSession::DisableJSAndPlugins(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
+ nsIDocShell *docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ bool tmp;
+ nsresult rv = docShell->GetAllowJavascript(&tmp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mScriptsEnabled = tmp;
+
+ rv = docShell->SetAllowJavascript(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Disable plugins in this document:
+ mPluginsEnabled = docShell->PluginsAllowedInCurrentDoc();
+
+ rv = docShell->SetAllowPlugins(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDisabledJSAndPlugins = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEditingSession::RestoreJSAndPlugins(mozIDOMWindowProxy* aWindow)
+{
+ if (!mDisabledJSAndPlugins) {
+ return NS_OK;
+ }
+
+ mDisabledJSAndPlugins = false;
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
+ nsIDocShell *docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsresult rv = docShell->SetAllowJavascript(mScriptsEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Disable plugins in this document:
+ return docShell->SetAllowPlugins(mPluginsEnabled);
+}
+
+NS_IMETHODIMP
+nsEditingSession::GetJsAndPluginsDisabled(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mDisabledJSAndPlugins;
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ WindowIsEditable
+
+ boolean windowIsEditable (in nsIDOMWindow aWindow);
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::WindowIsEditable(mozIDOMWindowProxy* aWindow,
+ bool *outIsEditable)
+{
+ NS_ENSURE_STATE(aWindow);
+ nsCOMPtr<nsIDocShell> docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ return docShell->GetEditable(outIsEditable);
+}
+
+
+// These are MIME types that are automatically parsed as "text/plain"
+// and thus we can edit them as plaintext
+// Note: in older versions, we attempted to convert the mimetype of
+// the network channel for these and "text/xml" to "text/plain",
+// but further investigation reveals that strategy doesn't work
+const char* const gSupportedTextTypes[] = {
+ "text/plain",
+ "text/css",
+ "text/rdf",
+ "text/xsl",
+ "text/javascript", // obsolete type
+ "text/ecmascript", // obsolete type
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript", // obsolete type
+ "text/xul", // obsolete type
+ "application/vnd.mozilla.xul+xml",
+ nullptr // IMPORTANT! Null must be at end
+};
+
+bool
+IsSupportedTextType(const char* aMIMEType)
+{
+ NS_ENSURE_TRUE(aMIMEType, false);
+
+ for (size_t i = 0; gSupportedTextTypes[i]; ++i) {
+ if (!strcmp(gSupportedTextTypes[i], aMIMEType)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*---------------------------------------------------------------------------
+
+ SetupEditorOnWindow
+
+ nsIEditor setupEditorOnWindow (in nsIDOMWindow aWindow);
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::SetupEditorOnWindow(mozIDOMWindowProxy* aWindow)
+{
+ mDoneSetup = true;
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
+ auto* window = nsPIDOMWindowOuter::From(aWindow);
+
+ nsresult rv;
+
+ //MIME CHECKING
+ //must get the content type
+ // Note: the doc gets this from the network channel during StartPageLoad,
+ // so we don't have to get it from there ourselves
+ nsAutoCString mimeCType;
+
+ //then lets check the mime type
+ if (nsCOMPtr<nsIDocument> doc = window->GetDoc()) {
+ nsAutoString mimeType;
+ if (NS_SUCCEEDED(doc->GetContentType(mimeType)))
+ AppendUTF16toUTF8(mimeType, mimeCType);
+
+ if (IsSupportedTextType(mimeCType.get())) {
+ mEditorType.AssignLiteral("text");
+ mimeCType = "text/plain";
+ } else if (!mimeCType.EqualsLiteral("text/html") &&
+ !mimeCType.EqualsLiteral("application/xhtml+xml")) {
+ // Neither an acceptable text or html type.
+ mEditorStatus = eEditorErrorCantEditMimeType;
+
+ // Turn editor into HTML -- we will load blank page later
+ mEditorType.AssignLiteral("html");
+ mimeCType.AssignLiteral("text/html");
+ }
+
+ // Flush out frame construction to make sure that the subframe's
+ // presshell is set up if it needs to be.
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(doc);
+ if (document) {
+ document->FlushPendingNotifications(Flush_Frames);
+ if (mMakeWholeDocumentEditable) {
+ document->SetEditableFlag(true);
+ nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(document);
+ if (htmlDocument) {
+ // Enable usage of the execCommand API
+ htmlDocument->SetEditingState(nsIHTMLDocument::eDesignMode);
+ }
+ }
+ }
+ }
+ bool needHTMLController = false;
+
+ const char *classString = "@mozilla.org/editor/htmleditor;1";
+ if (mEditorType.EqualsLiteral("textmail")) {
+ mEditorFlags = nsIPlaintextEditor::eEditorPlaintextMask |
+ nsIPlaintextEditor::eEditorEnableWrapHackMask |
+ nsIPlaintextEditor::eEditorMailMask;
+ } else if (mEditorType.EqualsLiteral("text")) {
+ mEditorFlags = nsIPlaintextEditor::eEditorPlaintextMask |
+ nsIPlaintextEditor::eEditorEnableWrapHackMask;
+ } else if (mEditorType.EqualsLiteral("htmlmail")) {
+ if (mimeCType.EqualsLiteral("text/html")) {
+ needHTMLController = true;
+ mEditorFlags = nsIPlaintextEditor::eEditorMailMask;
+ } else {
+ // Set the flags back to textplain.
+ mEditorFlags = nsIPlaintextEditor::eEditorPlaintextMask |
+ nsIPlaintextEditor::eEditorEnableWrapHackMask;
+ }
+ } else {
+ // Defaulted to html
+ needHTMLController = true;
+ }
+
+ if (mInteractive) {
+ mEditorFlags |= nsIPlaintextEditor::eEditorAllowInteraction;
+ }
+
+ // make the UI state maintainer
+ mStateMaintainer = new nsComposerCommandsUpdater();
+
+ // now init the state maintainer
+ // This allows notification of error state
+ // even if we don't create an editor
+ rv = mStateMaintainer->Init(window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mEditorStatus != eEditorCreationInProgress) {
+ mStateMaintainer->NotifyDocumentCreated();
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create editor and do other things
+ // only if we haven't found some error above,
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ if (!mInteractive) {
+ // Disable animation of images in this document:
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ mImageAnimationMode = presContext->ImageAnimationMode();
+ presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
+ }
+
+ // Hide selection changes during initialization, in order to hide this
+ // from web pages.
+ RefPtr<nsFrameSelection> fs = presShell->FrameSelection();
+ NS_ENSURE_TRUE(fs, NS_ERROR_FAILURE);
+ mozilla::dom::AutoHideSelectionChanges hideSelectionChanges(fs);
+
+ // create and set editor
+ // Try to reuse an existing editor
+ nsCOMPtr<nsIEditor> editor = do_QueryReferent(mExistingEditor);
+ if (editor) {
+ editor->PreDestroy(false);
+ } else {
+ editor = do_CreateInstance(classString, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mExistingEditor = do_GetWeakReference(editor);
+ }
+ // set the editor on the docShell. The docShell now owns it.
+ rv = docShell->SetEditor(editor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // setup the HTML editor command controller
+ if (needHTMLController) {
+ // The third controller takes an nsIEditor as the context
+ rv = SetupEditorCommandController("@mozilla.org/editor/htmleditorcontroller;1",
+ aWindow, editor,
+ &mHTMLCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set mimetype on editor
+ rv = editor->SetContentsMIMEType(mimeCType.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ rv = docShell->GetContentViewer(getter_AddRefs(contentViewer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(contentViewer, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ rv = contentViewer->GetDOMDocument(getter_AddRefs(domDoc));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
+
+ // Set up as a doc state listener
+ // Important! We must have this to broadcast the "obs_documentCreated" message
+ rv = editor->AddDocumentStateListener(mStateMaintainer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = editor->Init(domDoc, nullptr /* root content */,
+ nullptr, mEditorFlags, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISelection> selection;
+ editor->GetSelection(getter_AddRefs(selection));
+ nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
+ NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE);
+
+ rv = selPriv->AddSelectionListener(mStateMaintainer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // and as a transaction listener
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ editor->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ txnMgr->AddListener(mStateMaintainer);
+ }
+
+ // Set context on all controllers to be the editor
+ rv = SetEditorOnControllers(aWindow, editor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Everything went fine!
+ mEditorStatus = eEditorOK;
+
+ // This will trigger documentCreation notification
+ return editor->PostCreate();
+}
+
+// Removes all listeners and controllers from aWindow and aEditor.
+void
+nsEditingSession::RemoveListenersAndControllers(nsPIDOMWindowOuter* aWindow,
+ nsIEditor *aEditor)
+{
+ if (!mStateMaintainer || !aEditor) {
+ return;
+ }
+
+ // Remove all the listeners
+ nsCOMPtr<nsISelection> selection;
+ aEditor->GetSelection(getter_AddRefs(selection));
+ nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
+ if (selPriv)
+ selPriv->RemoveSelectionListener(mStateMaintainer);
+
+ aEditor->RemoveDocumentStateListener(mStateMaintainer);
+
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ aEditor->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ txnMgr->RemoveListener(mStateMaintainer);
+ }
+
+ // Remove editor controllers from the window now that we're not
+ // editing in that window any more.
+ RemoveEditorControllers(aWindow);
+}
+
+/*---------------------------------------------------------------------------
+
+ TearDownEditorOnWindow
+
+ void tearDownEditorOnWindow (in nsIDOMWindow aWindow);
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::TearDownEditorOnWindow(mozIDOMWindowProxy *aWindow)
+{
+ if (!mDoneSetup) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+
+ // Kill any existing reload timer
+ if (mLoadBlankDocTimer) {
+ mLoadBlankDocTimer->Cancel();
+ mLoadBlankDocTimer = nullptr;
+ }
+
+ mDoneSetup = false;
+
+ // Check if we're turning off editing (from contentEditable or designMode).
+ auto* window = nsPIDOMWindowOuter::From(aWindow);
+
+ nsCOMPtr<nsIDocument> doc = window->GetDoc();
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
+ bool stopEditing = htmlDoc && htmlDoc->IsEditingOn();
+ if (stopEditing) {
+ RemoveWebProgressListener(window);
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ nsCOMPtr<nsIEditor> editor;
+ rv = docShell->GetEditor(getter_AddRefs(editor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (stopEditing) {
+ htmlDoc->TearingDownEditor(editor);
+ }
+
+ if (mStateMaintainer && editor) {
+ // Null out the editor on the controllers first to prevent their weak
+ // references from pointing to a destroyed editor.
+ SetEditorOnControllers(aWindow, nullptr);
+ }
+
+ // Null out the editor on the docShell to trigger PreDestroy which
+ // needs to happen before document state listeners are removed below.
+ docShell->SetEditor(nullptr);
+
+ RemoveListenersAndControllers(window, editor);
+
+ if (stopEditing) {
+ // Make things the way they were before we started editing.
+ RestoreJSAndPlugins(aWindow);
+ RestoreAnimationMode(window);
+
+ if (mMakeWholeDocumentEditable) {
+ doc->SetEditableFlag(false);
+ nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(doc);
+ if (htmlDocument) {
+ htmlDocument->SetEditingState(nsIHTMLDocument::eOff);
+ }
+ }
+ }
+
+ return rv;
+}
+
+/*---------------------------------------------------------------------------
+
+ GetEditorForFrame
+
+ nsIEditor getEditorForFrame (in nsIDOMWindow aWindow);
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::GetEditorForWindow(mozIDOMWindowProxy* aWindow,
+ nsIEditor **outEditor)
+{
+ NS_ENSURE_STATE(aWindow);
+ nsCOMPtr<nsIDocShell> docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ return docShell->GetEditor(outEditor);
+}
+
+/*---------------------------------------------------------------------------
+
+ OnStateChange
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnStateChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t aStateFlags, nsresult aStatus)
+{
+
+#ifdef NOISY_DOC_LOADING
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ if (channel) {
+ nsAutoCString contentType;
+ channel->GetContentType(contentType);
+ if (!contentType.IsEmpty()) {
+ printf(" ++++++ MIMETYPE = %s\n", contentType.get());
+ }
+ }
+#endif
+
+ //
+ // A Request has started...
+ //
+ if (aStateFlags & nsIWebProgressListener::STATE_START) {
+#ifdef NOISY_DOC_LOADING
+ {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ nsXPIDLCString spec;
+ uri->GetSpec(spec);
+ printf(" **** STATE_START: CHANNEL URI=%s, flags=%x\n",
+ spec.get(), aStateFlags);
+ }
+ } else {
+ printf(" STATE_START: NO CHANNEL flags=%x\n", aStateFlags);
+ }
+ }
+#endif
+ // Page level notification...
+ if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ StartPageLoad(channel);
+#ifdef NOISY_DOC_LOADING
+ printf("STATE_START & STATE_IS_NETWORK flags=%x\n", aStateFlags);
+#endif
+ }
+
+ // Document level notification...
+ if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT &&
+ !(aStateFlags & nsIWebProgressListener::STATE_RESTORING)) {
+#ifdef NOISY_DOC_LOADING
+ printf("STATE_START & STATE_IS_DOCUMENT flags=%x\n", aStateFlags);
+#endif
+
+ bool progressIsForTargetDocument =
+ IsProgressForTargetDocument(aWebProgress);
+
+ if (progressIsForTargetDocument) {
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ aWebProgress->GetDOMWindow(getter_AddRefs(window));
+
+ auto* piWindow = nsPIDOMWindowOuter::From(window);
+ nsCOMPtr<nsIDocument> doc = piWindow->GetDoc();
+
+ nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(doc));
+
+ if (htmlDoc && htmlDoc->IsWriting()) {
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDomDoc = do_QueryInterface(doc);
+ nsAutoString designMode;
+ htmlDomDoc->GetDesignMode(designMode);
+
+ if (designMode.EqualsLiteral("on")) {
+ // This notification is for data coming in through
+ // document.open/write/close(), ignore it.
+
+ return NS_OK;
+ }
+ }
+
+ mCanCreateEditor = true;
+ StartDocumentLoad(aWebProgress, progressIsForTargetDocument);
+ }
+ }
+ }
+ //
+ // A Request is being processed
+ //
+ else if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) {
+ if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
+ // document transfer started
+ }
+ }
+ //
+ // Got a redirection
+ //
+ else if (aStateFlags & nsIWebProgressListener::STATE_REDIRECTING) {
+ if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
+ // got a redirect
+ }
+ }
+ //
+ // A network or document Request has finished...
+ //
+ else if (aStateFlags & nsIWebProgressListener::STATE_STOP) {
+#ifdef NOISY_DOC_LOADING
+ {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ nsXPIDLCString spec;
+ uri->GetSpec(spec);
+ printf(" **** STATE_STOP: CHANNEL URI=%s, flags=%x\n",
+ spec.get(), aStateFlags);
+ }
+ } else {
+ printf(" STATE_STOP: NO CHANNEL flags=%x\n", aStateFlags);
+ }
+ }
+#endif
+
+ // Document level notification...
+ if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ EndDocumentLoad(aWebProgress, channel, aStatus,
+ IsProgressForTargetDocument(aWebProgress));
+#ifdef NOISY_DOC_LOADING
+ printf("STATE_STOP & STATE_IS_DOCUMENT flags=%x\n", aStateFlags);
+#endif
+ }
+
+ // Page level notification...
+ if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ (void)EndPageLoad(aWebProgress, channel, aStatus);
+#ifdef NOISY_DOC_LOADING
+ printf("STATE_STOP & STATE_IS_NETWORK flags=%x\n", aStateFlags);
+#endif
+ }
+ }
+
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ OnProgressChange
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ OnLocationChange
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsIURI *aURI,
+ uint32_t aFlags)
+{
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ nsresult rv = aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto* piWindow = nsPIDOMWindowOuter::From(domWindow);
+
+ nsCOMPtr<nsIDocument> doc = piWindow->GetDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ doc->SetDocumentURI(aURI);
+
+ // Notify the location-changed observer that
+ // the document URL has changed
+ nsIDocShell *docShell = piWindow->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
+ nsCOMPtr<nsPICommandUpdater> commandUpdater =
+ do_QueryInterface(commandManager);
+ NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE);
+
+ return commandUpdater->CommandStatusChanged("obs_documentLocationChanged");
+}
+
+/*---------------------------------------------------------------------------
+
+ OnStatusChange
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsresult aStatus,
+ const char16_t *aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ OnSecurityChange
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+
+/*---------------------------------------------------------------------------
+
+ IsProgressForTargetDocument
+
+ Check that this notification is for our document.
+----------------------------------------------------------------------------*/
+
+bool
+nsEditingSession::IsProgressForTargetDocument(nsIWebProgress *aWebProgress)
+{
+ nsCOMPtr<nsIWebProgress> editedWebProgress = do_QueryReferent(mDocShell);
+ return editedWebProgress == aWebProgress;
+}
+
+
+/*---------------------------------------------------------------------------
+
+ GetEditorStatus
+
+ Called during GetCommandStateParams("obs_documentCreated"...)
+ to determine if editor was created and document
+ was loaded successfully
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::GetEditorStatus(uint32_t *aStatus)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = mEditorStatus;
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ StartDocumentLoad
+
+ Called on start of load in a single frame
+----------------------------------------------------------------------------*/
+nsresult
+nsEditingSession::StartDocumentLoad(nsIWebProgress *aWebProgress,
+ bool aIsToBeMadeEditable)
+{
+#ifdef NOISY_DOC_LOADING
+ printf("======= StartDocumentLoad ========\n");
+#endif
+
+ NS_ENSURE_ARG_POINTER(aWebProgress);
+
+ if (aIsToBeMadeEditable) {
+ mEditorStatus = eEditorCreationInProgress;
+ }
+
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ EndDocumentLoad
+
+ Called on end of load in a single frame
+----------------------------------------------------------------------------*/
+nsresult
+nsEditingSession::EndDocumentLoad(nsIWebProgress *aWebProgress,
+ nsIChannel* aChannel, nsresult aStatus,
+ bool aIsToBeMadeEditable)
+{
+ NS_ENSURE_ARG_POINTER(aWebProgress);
+
+#ifdef NOISY_DOC_LOADING
+ printf("======= EndDocumentLoad ========\n");
+ printf("with status %d, ", aStatus);
+ nsCOMPtr<nsIURI> uri;
+ nsXPIDLCString spec;
+ if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) {
+ uri->GetSpec(spec);
+ printf(" uri %s\n", spec.get());
+ }
+#endif
+
+ // We want to call the base class EndDocumentLoad,
+ // but avoid some of the stuff
+ // that nsDocShell does (need to refactor).
+
+ // OK, time to make an editor on this document
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+
+ // Set the error state -- we will create an editor
+ // anyway and load empty doc later
+ if (aIsToBeMadeEditable && aStatus == NS_ERROR_FILE_NOT_FOUND) {
+ mEditorStatus = eEditorErrorFileNotFound;
+ }
+
+ nsIDocShell *docShell = nsPIDOMWindowOuter::From(domWindow)->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); // better error handling?
+
+ // cancel refresh from meta tags
+ // we need to make sure that all pages in editor (whether editable or not)
+ // can't refresh contents being edited
+ nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell);
+ if (refreshURI) {
+ refreshURI->CancelRefreshURITimers();
+ }
+
+ nsresult rv = NS_OK;
+
+ // did someone set the flag to make this shell editable?
+ if (aIsToBeMadeEditable && mCanCreateEditor) {
+ bool makeEditable;
+ docShell->GetEditable(&makeEditable);
+
+ if (makeEditable) {
+ // To keep pre Gecko 1.9 behavior, setup editor always when
+ // mMakeWholeDocumentEditable.
+ bool needsSetup = false;
+ if (mMakeWholeDocumentEditable) {
+ needsSetup = true;
+ } else {
+ // do we already have an editor here?
+ nsCOMPtr<nsIEditor> editor;
+ rv = docShell->GetEditor(getter_AddRefs(editor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ needsSetup = !editor;
+ }
+
+ if (needsSetup) {
+ mCanCreateEditor = false;
+ rv = SetupEditorOnWindow(domWindow);
+ if (NS_FAILED(rv)) {
+ // If we had an error, setup timer to load a blank page later
+ if (mLoadBlankDocTimer) {
+ // Must cancel previous timer?
+ mLoadBlankDocTimer->Cancel();
+ mLoadBlankDocTimer = nullptr;
+ }
+
+ mLoadBlankDocTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEditorStatus = eEditorCreationInProgress;
+ mLoadBlankDocTimer->InitWithFuncCallback(
+ nsEditingSession::TimerCallback,
+ static_cast<void*> (mDocShell.get()),
+ 10, nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+
+void
+nsEditingSession::TimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(static_cast<nsIWeakReference*> (aClosure));
+ if (docShell) {
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
+ if (webNav) {
+ webNav->LoadURI(u"about:blank", 0, nullptr, nullptr, nullptr);
+ }
+ }
+}
+
+/*---------------------------------------------------------------------------
+
+ StartPageLoad
+
+ Called on start load of the entire page (incl. subframes)
+----------------------------------------------------------------------------*/
+nsresult
+nsEditingSession::StartPageLoad(nsIChannel *aChannel)
+{
+#ifdef NOISY_DOC_LOADING
+ printf("======= StartPageLoad ========\n");
+#endif
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ EndPageLoad
+
+ Called on end load of the entire page (incl. subframes)
+----------------------------------------------------------------------------*/
+nsresult
+nsEditingSession::EndPageLoad(nsIWebProgress *aWebProgress,
+ nsIChannel* aChannel, nsresult aStatus)
+{
+#ifdef NOISY_DOC_LOADING
+ printf("======= EndPageLoad ========\n");
+ printf(" with status %d, ", aStatus);
+ nsCOMPtr<nsIURI> uri;
+ nsXPIDLCString spec;
+ if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) {
+ uri->GetSpec(spec);
+ printf("uri %s\n", spec.get());
+ }
+
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+ if (!contentType.IsEmpty()) {
+ printf(" flags = %d, status = %d, MIMETYPE = %s\n",
+ mEditorFlags, mEditorStatus, contentType.get());
+ }
+#endif
+
+ // Set the error state -- we will create an editor anyway
+ // and load empty doc later
+ if (aStatus == NS_ERROR_FILE_NOT_FOUND) {
+ mEditorStatus = eEditorErrorFileNotFound;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
+
+ nsIDocShell *docShell =
+ domWindow ? nsPIDOMWindowOuter::From(domWindow)->GetDocShell() : nullptr;
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ // cancel refresh from meta tags
+ // we need to make sure that all pages in editor (whether editable or not)
+ // can't refresh contents being edited
+ nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell);
+ if (refreshURI) {
+ refreshURI->CancelRefreshURITimers();
+ }
+
+#if 0
+ // Shouldn't we do this when we want to edit sub-frames?
+ return MakeWindowEditable(domWindow, "html", false, mInteractive);
+#else
+ return NS_OK;
+#endif
+}
+
+/*---------------------------------------------------------------------------
+
+ PrepareForEditing
+
+ Set up this editing session for one or more editors
+----------------------------------------------------------------------------*/
+nsresult
+nsEditingSession::PrepareForEditing(nsPIDOMWindowOuter* aWindow)
+{
+ if (mProgressListenerRegistered) {
+ return NS_OK;
+ }
+
+ nsIDocShell *docShell = aWindow ? aWindow->GetDocShell() : nullptr;
+
+ // register callback
+ nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+ NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE);
+
+ nsresult rv =
+ webProgress->AddProgressListener(this,
+ (nsIWebProgress::NOTIFY_STATE_NETWORK |
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT |
+ nsIWebProgress::NOTIFY_LOCATION));
+
+ mProgressListenerRegistered = NS_SUCCEEDED(rv);
+
+ return rv;
+}
+
+/*---------------------------------------------------------------------------
+
+ SetupEditorCommandController
+
+ Create a command controller, append to controllers,
+ get and return the controller ID, and set the context
+----------------------------------------------------------------------------*/
+nsresult
+nsEditingSession::SetupEditorCommandController(
+ const char *aControllerClassName,
+ mozIDOMWindowProxy *aWindow,
+ nsISupports *aContext,
+ uint32_t *aControllerId)
+{
+ NS_ENSURE_ARG_POINTER(aControllerClassName);
+ NS_ENSURE_ARG_POINTER(aWindow);
+ NS_ENSURE_ARG_POINTER(aContext);
+ NS_ENSURE_ARG_POINTER(aControllerId);
+
+ auto* piWindow = nsPIDOMWindowOuter::From(aWindow);
+ MOZ_ASSERT(piWindow);
+
+ nsCOMPtr<nsIControllers> controllers;
+ nsresult rv = piWindow->GetControllers(getter_AddRefs(controllers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We only have to create each singleton controller once
+ // We know this has happened once we have a controllerId value
+ if (!*aControllerId) {
+ nsCOMPtr<nsIController> controller;
+ controller = do_CreateInstance(aControllerClassName, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We must insert at head of the list to be sure our
+ // controller is found before other implementations
+ // (e.g., not-implemented versions by browser)
+ rv = controllers->InsertControllerAt(0, controller);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remember the ID for the controller
+ rv = controllers->GetControllerId(controller, aControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set the context
+ return SetContextOnControllerById(controllers, aContext, *aControllerId);
+}
+
+/*---------------------------------------------------------------------------
+
+ SetEditorOnControllers
+
+ Set the editor on the controller(s) for this window
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::SetEditorOnControllers(mozIDOMWindowProxy* aWindow,
+ nsIEditor* aEditor)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
+
+ auto* piWindow = nsPIDOMWindowOuter::From(aWindow);
+
+ nsCOMPtr<nsIControllers> controllers;
+ nsresult rv = piWindow->GetControllers(getter_AddRefs(controllers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> editorAsISupports = do_QueryInterface(aEditor);
+ if (mBaseCommandControllerId) {
+ rv = SetContextOnControllerById(controllers, editorAsISupports,
+ mBaseCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mDocStateControllerId) {
+ rv = SetContextOnControllerById(controllers, editorAsISupports,
+ mDocStateControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mHTMLCommandControllerId) {
+ rv = SetContextOnControllerById(controllers, editorAsISupports,
+ mHTMLCommandControllerId);
+ }
+
+ return rv;
+}
+
+nsresult
+nsEditingSession::SetContextOnControllerById(nsIControllers* aControllers,
+ nsISupports* aContext,
+ uint32_t aID)
+{
+ NS_ENSURE_ARG_POINTER(aControllers);
+
+ // aContext can be null (when destroying editor)
+ nsCOMPtr<nsIController> controller;
+ aControllers->GetControllerById(aID, getter_AddRefs(controller));
+
+ // ok with nil controller
+ nsCOMPtr<nsIControllerContext> editorController =
+ do_QueryInterface(controller);
+ NS_ENSURE_TRUE(editorController, NS_ERROR_FAILURE);
+
+ return editorController->SetCommandContext(aContext);
+}
+
+void
+nsEditingSession::RemoveEditorControllers(nsPIDOMWindowOuter* aWindow)
+{
+ // Remove editor controllers from the aWindow, call when we're
+ // tearing down/detaching editor.
+
+ nsCOMPtr<nsIControllers> controllers;
+ if (aWindow) {
+ aWindow->GetControllers(getter_AddRefs(controllers));
+ }
+
+ if (controllers) {
+ nsCOMPtr<nsIController> controller;
+ if (mBaseCommandControllerId) {
+ controllers->GetControllerById(mBaseCommandControllerId,
+ getter_AddRefs(controller));
+ if (controller) {
+ controllers->RemoveController(controller);
+ }
+ }
+
+ if (mDocStateControllerId) {
+ controllers->GetControllerById(mDocStateControllerId,
+ getter_AddRefs(controller));
+ if (controller) {
+ controllers->RemoveController(controller);
+ }
+ }
+
+ if (mHTMLCommandControllerId) {
+ controllers->GetControllerById(mHTMLCommandControllerId,
+ getter_AddRefs(controller));
+ if (controller) {
+ controllers->RemoveController(controller);
+ }
+ }
+ }
+
+ // Clear IDs to trigger creation of new controllers.
+ mBaseCommandControllerId = 0;
+ mDocStateControllerId = 0;
+ mHTMLCommandControllerId = 0;
+}
+
+void
+nsEditingSession::RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow)
+{
+ nsIDocShell *docShell = aWindow ? aWindow->GetDocShell() : nullptr;
+ nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+ if (webProgress) {
+ webProgress->RemoveProgressListener(this);
+ mProgressListenerRegistered = false;
+ }
+}
+
+void
+nsEditingSession::RestoreAnimationMode(nsPIDOMWindowOuter* aWindow)
+{
+ if (mInteractive) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow ? aWindow->GetDocShell() : nullptr;
+ NS_ENSURE_TRUE_VOID(docShell);
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE_VOID(presShell);
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE_VOID(presContext);
+
+ presContext->SetImageAnimationMode(mImageAnimationMode);
+}
+
+nsresult
+nsEditingSession::DetachFromWindow(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(mDoneSetup, NS_OK);
+
+ NS_ASSERTION(mStateMaintainer, "mStateMaintainer should exist.");
+
+ // Kill any existing reload timer
+ if (mLoadBlankDocTimer) {
+ mLoadBlankDocTimer->Cancel();
+ mLoadBlankDocTimer = nullptr;
+ }
+
+ auto* window = nsPIDOMWindowOuter::From(aWindow);
+
+ // Remove controllers, webprogress listener, and otherwise
+ // make things the way they were before we started editing.
+ RemoveEditorControllers(window);
+ RemoveWebProgressListener(window);
+ RestoreJSAndPlugins(aWindow);
+ RestoreAnimationMode(window);
+
+ // Kill our weak reference to our original window, in case
+ // it changes on restore, or otherwise dies.
+ mDocShell = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsEditingSession::ReattachToWindow(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(mDoneSetup, NS_OK);
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
+
+ NS_ASSERTION(mStateMaintainer, "mStateMaintainer should exist.");
+
+ // Imitate nsEditorDocShell::MakeEditable() to reattach the
+ // old editor ot the window.
+ nsresult rv;
+
+ auto* window = nsPIDOMWindowOuter::From(aWindow);
+ nsIDocShell *docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+ mDocShell = do_GetWeakReference(docShell);
+
+ // Disable plugins.
+ if (!mInteractive) {
+ rv = DisableJSAndPlugins(aWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Tells embedder that startup is in progress.
+ mEditorStatus = eEditorCreationInProgress;
+
+ // Adds back web progress listener.
+ rv = PrepareForEditing(window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setup the command controllers again.
+ rv = SetupEditorCommandController("@mozilla.org/editor/editingcontroller;1",
+ aWindow,
+ static_cast<nsIEditingSession*>(this),
+ &mBaseCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupEditorCommandController("@mozilla.org/editor/editordocstatecontroller;1",
+ aWindow,
+ static_cast<nsIEditingSession*>(this),
+ &mDocStateControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mStateMaintainer) {
+ mStateMaintainer->Init(window);
+ }
+
+ // Get editor
+ nsCOMPtr<nsIEditor> editor;
+ rv = GetEditorForWindow(aWindow, getter_AddRefs(editor));
+ NS_ENSURE_TRUE(editor, NS_ERROR_FAILURE);
+
+ if (!mInteractive) {
+ // Disable animation of images in this document:
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ mImageAnimationMode = presContext->ImageAnimationMode();
+ presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
+ }
+
+ // The third controller takes an nsIEditor as the context
+ rv = SetupEditorCommandController("@mozilla.org/editor/htmleditorcontroller;1",
+ aWindow, editor,
+ &mHTMLCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set context on all controllers to be the editor
+ rv = SetEditorOnControllers(aWindow, editor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DEBUG
+ {
+ bool isEditable;
+ rv = WindowIsEditable(aWindow, &isEditable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(isEditable, "Window is not editable after reattaching editor.");
+ }
+#endif // DEBUG
+
+ return NS_OK;
+}
diff --git a/editor/composer/nsEditingSession.h b/editor/composer/nsEditingSession.h
new file mode 100644
index 000000000..6772d3a96
--- /dev/null
+++ b/editor/composer/nsEditingSession.h
@@ -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__
diff --git a/editor/composer/nsEditorSpellCheck.cpp b/editor/composer/nsEditorSpellCheck.cpp
new file mode 100644
index 000000000..7cae48c1f
--- /dev/null
+++ b/editor/composer/nsEditorSpellCheck.cpp
@@ -0,0 +1,1005 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 sw=2 tw=80: */
+/* 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 <stdlib.h> // for getenv
+
+#include "mozilla/Attributes.h" // for final
+#include "mozilla/Preferences.h" // for Preferences
+#include "mozilla/Services.h" // for GetXULChromeRegistryService
+#include "mozilla/dom/Element.h" // for Element
+#include "mozilla/dom/Selection.h"
+#include "mozilla/mozalloc.h" // for operator delete, etc
+#include "nsAString.h" // for nsAString_internal::IsEmpty, etc
+#include "nsComponentManagerUtils.h" // for do_CreateInstance
+#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
+#include "nsDependentSubstring.h" // for Substring
+#include "nsEditorSpellCheck.h"
+#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc
+#include "nsIChromeRegistry.h" // for nsIXULChromeRegistry
+#include "nsIContent.h" // for nsIContent
+#include "nsIContentPrefService.h" // for nsIContentPrefService, etc
+#include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc
+#include "nsIDOMDocument.h" // for nsIDOMDocument
+#include "nsIDOMElement.h" // for nsIDOMElement
+#include "nsIDocument.h" // for nsIDocument
+#include "nsIEditor.h" // for nsIEditor
+#include "nsIHTMLEditor.h" // for nsIHTMLEditor
+#include "nsILoadContext.h"
+#include "nsISelection.h" // for nsISelection
+#include "nsISpellChecker.h" // for nsISpellChecker, etc
+#include "nsISupportsBase.h" // for nsISupports
+#include "nsISupportsUtils.h" // for NS_ADDREF
+#include "nsITextServicesDocument.h" // for nsITextServicesDocument
+#include "nsITextServicesFilter.h" // for nsITextServicesFilter
+#include "nsIURI.h" // for nsIURI
+#include "nsVariant.h" // for nsIWritableVariant, etc
+#include "nsLiteralString.h" // for NS_LITERAL_STRING, etc
+#include "nsMemory.h" // for nsMemory
+#include "nsRange.h"
+#include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc
+#include "nsServiceManagerUtils.h" // for do_GetService
+#include "nsString.h" // for nsAutoString, nsString, etc
+#include "nsStringFwd.h" // for nsAFlatString
+#include "nsStyleUtil.h" // for nsStyleUtil
+#include "nsXULAppAPI.h" // for XRE_GetProcessType
+#include "nsIPlaintextEditor.h" // for editor flags
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class UpdateDictionaryHolder {
+ private:
+ nsEditorSpellCheck* mSpellCheck;
+ public:
+ explicit UpdateDictionaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
+ if (mSpellCheck) {
+ mSpellCheck->BeginUpdateDictionary();
+ }
+ }
+ ~UpdateDictionaryHolder() {
+ if (mSpellCheck) {
+ mSpellCheck->EndUpdateDictionary();
+ }
+ }
+};
+
+#define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
+
+/**
+ * Gets the URI of aEditor's document.
+ */
+static nsresult
+GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
+{
+ NS_ENSURE_ARG_POINTER(aEditor);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ aEditor->GetDocument(getter_AddRefs(domDoc));
+ NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
+ NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
+
+ *aURI = docUri;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+static already_AddRefed<nsILoadContext>
+GetLoadContext(nsIEditor* aEditor)
+{
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ aEditor->GetDocument(getter_AddRefs(domDoc));
+ NS_ENSURE_TRUE(domDoc, nullptr);
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+ NS_ENSURE_TRUE(doc, nullptr);
+
+ nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
+ return loadContext.forget();
+}
+
+/**
+ * Fetches the dictionary stored in content prefs and maintains state during the
+ * fetch, which is asynchronous.
+ */
+class DictionaryFetcher final : public nsIContentPrefCallback2
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ DictionaryFetcher(nsEditorSpellCheck* aSpellCheck,
+ nsIEditorSpellCheckCallback* aCallback,
+ uint32_t aGroup)
+ : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {}
+
+ NS_IMETHOD Fetch(nsIEditor* aEditor);
+
+ NS_IMETHOD HandleResult(nsIContentPref* aPref) override
+ {
+ nsCOMPtr<nsIVariant> value;
+ nsresult rv = aPref->GetValue(getter_AddRefs(value));
+ NS_ENSURE_SUCCESS(rv, rv);
+ value->GetAsAString(mDictionary);
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleCompletion(uint16_t reason) override
+ {
+ mSpellCheck->DictionaryFetched(this);
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleError(nsresult error) override
+ {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
+ uint32_t mGroup;
+ nsString mRootContentLang;
+ nsString mRootDocContentLang;
+ nsString mDictionary;
+
+private:
+ ~DictionaryFetcher() {}
+
+ RefPtr<nsEditorSpellCheck> mSpellCheck;
+};
+NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2)
+
+NS_IMETHODIMP
+DictionaryFetcher::Fetch(nsIEditor* aEditor)
+{
+ NS_ENSURE_ARG_POINTER(aEditor);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> docUri;
+ rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString docUriSpec;
+ rv = docUri->GetSpec(docUriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
+ rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec),
+ CPS_PREF_NAME, loadContext,
+ this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * Stores the current dictionary for aEditor's document URL.
+ */
+static nsresult
+StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
+{
+ NS_ENSURE_ARG_POINTER(aEditor);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> docUri;
+ rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString docUriSpec;
+ rv = docUri->GetSpec(docUriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsVariant> prefValue = new nsVariant();
+ prefValue->SetAsAString(aDictionary);
+
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
+ return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
+ CPS_PREF_NAME, prefValue, loadContext,
+ nullptr);
+}
+
+/**
+ * Forgets the current dictionary stored for aEditor's document URL.
+ */
+static nsresult
+ClearCurrentDictionary(nsIEditor* aEditor)
+{
+ NS_ENSURE_ARG_POINTER(aEditor);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> docUri;
+ rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString docUriSpec;
+ rv = docUri->GetSpec(docUriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
+ return contentPrefService->RemoveByDomainAndName(
+ NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr);
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
+
+NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
+ NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck,
+ mEditor,
+ mSpellChecker,
+ mTxtSrvFilter)
+
+nsEditorSpellCheck::nsEditorSpellCheck()
+ : mSuggestedWordIndex(0)
+ , mDictionaryIndex(0)
+ , mEditor(nullptr)
+ , mDictionaryFetcherGroup(0)
+ , mUpdateDictionaryRunning(false)
+{
+}
+
+nsEditorSpellCheck::~nsEditorSpellCheck()
+{
+ // Make sure we blow the spellchecker away, just in
+ // case it hasn't been destroyed already.
+ mSpellChecker = nullptr;
+}
+
+// The problem is that if the spell checker does not exist, we can not tell
+// which dictionaries are installed. This function works around the problem,
+// allowing callers to ask if we can spell check without actually doing so (and
+// enabling or disabling UI as necessary). This just creates a spellcheck
+// object if needed and asks it for the dictionary list.
+NS_IMETHODIMP
+nsEditorSpellCheck::CanSpellCheck(bool* _retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsISpellChecker> spellChecker;
+ if (! mSpellChecker) {
+ spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ spellChecker = mSpellChecker;
+ }
+ nsTArray<nsString> dictList;
+ rv = spellChecker->GetDictionaryList(&dictList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = (dictList.Length() > 0);
+ return NS_OK;
+}
+
+// Instances of this class can be used as either runnables or RAII helpers.
+class CallbackCaller final : public Runnable
+{
+public:
+ explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
+ : mCallback(aCallback) {}
+
+ ~CallbackCaller()
+ {
+ Run();
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mCallback) {
+ mCallback->EditorSpellCheckDone();
+ mCallback = nullptr;
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
+};
+
+NS_IMETHODIMP
+nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback)
+{
+ NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
+ mEditor = aEditor;
+
+ nsresult rv;
+
+ // We can spell check with any editor type
+ nsCOMPtr<nsITextServicesDocument>tsDoc =
+ do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
+
+ tsDoc->SetFilter(mTxtSrvFilter);
+
+ // Pass the editor to the text services document
+ rv = tsDoc->InitWithEditor(aEditor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aEnableSelectionChecking) {
+ // Find out if the section is collapsed or not.
+ // If it isn't, we want to spellcheck just the selection.
+
+ nsCOMPtr<nsISelection> domSelection;
+ aEditor->GetSelection(getter_AddRefs(domSelection));
+ if (NS_WARN_IF(!domSelection)) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<Selection> selection = domSelection->AsSelection();
+
+ int32_t count = 0;
+
+ rv = selection->GetRangeCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count > 0) {
+ RefPtr<nsRange> range = selection->GetRangeAt(0);
+ NS_ENSURE_STATE(range);
+
+ bool collapsed = false;
+ rv = range->GetCollapsed(&collapsed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!collapsed) {
+ // We don't want to touch the range in the selection,
+ // so create a new copy of it.
+
+ RefPtr<nsRange> rangeBounds = range->CloneRange();
+
+ // Make sure the new range spans complete words.
+
+ rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now tell the text services that you only want
+ // to iterate over the text in this range.
+
+ rv = tsDoc->SetExtent(rangeBounds);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
+
+ rv = mSpellChecker->SetDocument(tsDoc, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // do not fail if UpdateCurrentDictionary fails because this method may
+ // succeed later.
+ rv = UpdateCurrentDictionary(aCallback);
+ if (NS_FAILED(rv) && aCallback) {
+ // However, if it does fail, we still need to call the callback since we
+ // discard the failure. Do it asynchronously so that the caller is always
+ // guaranteed async behavior.
+ RefPtr<CallbackCaller> caller = new CallbackCaller(aCallback);
+ NS_ENSURE_STATE(caller);
+ rv = NS_DispatchToMainThread(caller);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoString nextMisspelledWord;
+
+ DeleteSuggestedWordList();
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord,
+ &mSuggestedWordList);
+
+ *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord)
+{
+ nsAutoString word;
+ // XXX This is buggy if mSuggestedWordList.Length() is over INT32_MAX.
+ if (mSuggestedWordIndex < static_cast<int32_t>(mSuggestedWordList.Length())) {
+ *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]);
+ mSuggestedWordIndex++;
+ } else {
+ // A blank string signals that there are no more strings
+ *aSuggestedWord = ToNewUnicode(EmptyString());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord,
+ bool *aIsMisspelled)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ DeleteSuggestedWordList();
+ return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
+ aIsMisspelled, &mSuggestedWordList);
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord,
+ bool *aIsMisspelled)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
+ aIsMisspelled, nullptr);
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord,
+ const char16_t *aReplaceWord,
+ bool allOccurrences)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ return mSpellChecker->Replace(nsDependentString(aMisspelledWord),
+ nsDependentString(aReplaceWord), allOccurrences);
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ return mSpellChecker->IgnoreAll(nsDependentString(aWord));
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::GetPersonalDictionary()
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ // We can spell check with any editor type
+ mDictionaryList.Clear();
+ mDictionaryIndex = 0;
+ return mSpellChecker->GetPersonalDictionary(&mDictionaryList);
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord)
+{
+ // XXX This is buggy if mDictionaryList.Length() is over INT32_MAX.
+ if (mDictionaryIndex < static_cast<int32_t>(mDictionaryList.Length())) {
+ *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]);
+ mDictionaryIndex++;
+ } else {
+ // A blank string signals that there are no more strings
+ *aDictionaryWord = ToNewUnicode(EmptyString());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord));
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord));
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER);
+
+ *aDictionaryList = 0;
+ *aCount = 0;
+
+ nsTArray<nsString> dictList;
+
+ nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char16_t **tmpPtr = 0;
+
+ if (dictList.IsEmpty()) {
+ // If there are no dictionaries, return an array containing
+ // one element and a count of one.
+
+ tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *));
+
+ NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
+
+ *tmpPtr = 0;
+ *aDictionaryList = tmpPtr;
+ *aCount = 0;
+
+ return NS_OK;
+ }
+
+ tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *) * dictList.Length());
+
+ NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
+
+ *aDictionaryList = tmpPtr;
+ *aCount = dictList.Length();
+
+ for (uint32_t i = 0; i < *aCount; i++) {
+ tmpPtr[i] = ToNewUnicode(dictList[i]);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ return mSpellChecker->GetCurrentDictionary(aDictionary);
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
+
+ // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if
+ // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us,
+ // is on the stack. In other words: Only do this, if the user manually selected a
+ // dictionary to use.
+ if (!mUpdateDictionaryRunning) {
+
+ // Ignore pending dictionary fetchers by increasing this number.
+ mDictionaryFetcherGroup++;
+
+ uint32_t flags = 0;
+ mEditor->GetFlags(&flags);
+ if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
+ if (!aDictionary.IsEmpty() && (mPreferredLang.IsEmpty() ||
+ !mPreferredLang.Equals(aDictionary,
+ nsCaseInsensitiveStringComparator()))) {
+ // When user sets dictionary manually, we store this value associated
+ // with editor url, if it doesn't match the document language exactly.
+ // For example on "en" sites, we need to store "en-GB", otherwise
+ // the language might jump back to en-US although the user explicitly
+ // chose otherwise.
+ StoreCurrentDictionary(mEditor, aDictionary);
+#ifdef DEBUG_DICT
+ printf("***** Writing content preferences for |%s|\n",
+ NS_ConvertUTF16toUTF8(aDictionary).get());
+#endif
+ } else {
+ // If user sets a dictionary matching the language defined by
+ // document, we consider content pref has been canceled, and we clear it.
+ ClearCurrentDictionary(mEditor);
+#ifdef DEBUG_DICT
+ printf("***** Clearing content preferences for |%s|\n",
+ NS_ConvertUTF16toUTF8(aDictionary).get());
+#endif
+ }
+
+ // Also store it in as a preference, so we can use it as a fallback.
+ // We don't want this for mail composer because it uses
+ // "spellchecker.dictionary" as a preference.
+ Preferences::SetString("spellchecker.dictionary", aDictionary);
+#ifdef DEBUG_DICT
+ printf("***** Storing spellchecker.dictionary |%s|\n",
+ NS_ConvertUTF16toUTF8(aDictionary).get());
+#endif
+ }
+ }
+ return mSpellChecker->SetCurrentDictionary(aDictionary);
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::UninitSpellChecker()
+{
+ NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
+
+ // Cleanup - kill the spell checker
+ DeleteSuggestedWordList();
+ mDictionaryList.Clear();
+ mDictionaryIndex = 0;
+ mSpellChecker = nullptr;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter)
+{
+ mTxtSrvFilter = filter;
+ return NS_OK;
+}
+
+nsresult
+nsEditorSpellCheck::DeleteSuggestedWordList()
+{
+ mSuggestedWordList.Clear();
+ mSuggestedWordIndex = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback)
+{
+ if (NS_WARN_IF(!mSpellChecker)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+
+ RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
+
+ // Get language with html5 algorithm
+ nsCOMPtr<nsIContent> rootContent;
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
+ if (htmlEditor) {
+ rootContent = htmlEditor->GetActiveEditingHost();
+ } else {
+ nsCOMPtr<nsIDOMElement> rootElement;
+ rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rootContent = do_QueryInterface(rootElement);
+ }
+
+ // Try to get topmost document's document element for embedded mail editor.
+ uint32_t flags = 0;
+ mEditor->GetFlags(&flags);
+ if (flags & nsIPlaintextEditor::eEditorMailMask) {
+ nsCOMPtr<nsIDocument> ownerDoc = rootContent->OwnerDoc();
+ NS_ENSURE_TRUE(ownerDoc, NS_ERROR_FAILURE);
+ nsIDocument* parentDoc = ownerDoc->GetParentDocument();
+ if (parentDoc) {
+ rootContent = do_QueryInterface(parentDoc->GetDocumentElement());
+ }
+ }
+
+ if (!rootContent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DictionaryFetcher> fetcher =
+ new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup);
+ rootContent->GetLang(fetcher->mRootContentLang);
+ nsCOMPtr<nsIDocument> doc = rootContent->GetUncomposedDoc();
+ NS_ENSURE_STATE(doc);
+ doc->GetContentLanguage(fetcher->mRootDocContentLang);
+
+ rv = fetcher->Fetch(mEditor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// Helper function that iterates over the list of dictionaries and sets the one
+// that matches based on a given comparison type.
+nsresult
+nsEditorSpellCheck::TryDictionary(const nsAString& aDictName,
+ nsTArray<nsString>& aDictList,
+ enum dictCompare aCompareType)
+{
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ for (uint32_t i = 0; i < aDictList.Length(); i++) {
+ nsAutoString dictStr(aDictList.ElementAt(i));
+ bool equals = false;
+ switch (aCompareType) {
+ case DICT_NORMAL_COMPARE:
+ equals = aDictName.Equals(dictStr);
+ break;
+ case DICT_COMPARE_CASE_INSENSITIVE:
+ equals = aDictName.Equals(dictStr, nsCaseInsensitiveStringComparator());
+ break;
+ case DICT_COMPARE_DASHMATCH:
+ equals = nsStyleUtil::DashMatchCompare(dictStr, aDictName, nsCaseInsensitiveStringComparator());
+ break;
+ }
+ if (equals) {
+ rv = mSpellChecker->SetCurrentDictionary(dictStr);
+#ifdef DEBUG_DICT
+ if (NS_SUCCEEDED(rv))
+ printf("***** Set |%s|.\n", NS_ConvertUTF16toUTF8(dictStr).get());
+#endif
+ // We always break here. We tried to set the dictionary to an existing
+ // dictionary from the list. This must work, if it doesn't, there is
+ // no point trying another one.
+ break;
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
+{
+ MOZ_ASSERT(aFetcher);
+ RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
+
+ // Important: declare the holder after the callback caller so that the former
+ // is destructed first so that it's not active when the callback is called.
+ CallbackCaller callbackCaller(aFetcher->mCallback);
+ UpdateDictionaryHolder holder(this);
+
+ if (aFetcher->mGroup < mDictionaryFetcherGroup) {
+ // SetCurrentDictionary was called after the fetch started. Don't overwrite
+ // that dictionary with the fetched one.
+ return NS_OK;
+ }
+
+ /*
+ * We try to derive the dictionary to use based on the following priorities:
+ * 1) Content preference, so the language the user set for the site before.
+ * (Introduced in bug 678842 and corrected in bug 717433.)
+ * 2) Language set by the website, or any other dictionary that partly
+ * matches that. (Introduced in bug 338427.)
+ * Eg. if the website is "en-GB", a user who only has "en-US" will get
+ * that. If the website is generic "en", the user will get one of the
+ * "en-*" installed, (almost) at random.
+ * However, we prefer what is stored in "spellchecker.dictionary",
+ * so if the user chose "en-AU" before, they will get "en-AU" on a plain
+ * "en" site. (Introduced in bug 682564.)
+ * 3) The value of "spellchecker.dictionary" which reflects a previous
+ * language choice of the user (on another site).
+ * (This was the original behaviour before the aforementioned bugs
+ * landed).
+ * 4) The user's locale.
+ * 5) Use the current dictionary that is currently set.
+ * 6) The content of the "LANG" environment variable (if set).
+ * 7) The first spell check dictionary installed.
+ */
+
+ // Get the language from the element or its closest parent according to:
+ // https://html.spec.whatwg.org/#attr-lang
+ // This is used in SetCurrentDictionary.
+ mPreferredLang.Assign(aFetcher->mRootContentLang);
+#ifdef DEBUG_DICT
+ printf("***** mPreferredLang (element) |%s|\n",
+ NS_ConvertUTF16toUTF8(mPreferredLang).get());
+#endif
+
+ // If no luck, try the "Content-Language" header.
+ if (mPreferredLang.IsEmpty()) {
+ mPreferredLang.Assign(aFetcher->mRootDocContentLang);
+#ifdef DEBUG_DICT
+ printf("***** mPreferredLang (content-language) |%s|\n",
+ NS_ConvertUTF16toUTF8(mPreferredLang).get());
+#endif
+ }
+
+ // Auxiliary status.
+ nsresult rv2;
+
+ // We obtain a list of available dictionaries.
+ nsTArray<nsString> dictList;
+ rv2 = mSpellChecker->GetDictionaryList(&dictList);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+
+ // Priority 1:
+ // If we successfully fetched a dictionary from content prefs, do not go
+ // further. Use this exact dictionary.
+ // Don't use content preferences for editor with eEditorMailMask flag.
+ nsAutoString dictName;
+ uint32_t flags;
+ mEditor->GetFlags(&flags);
+ if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
+ dictName.Assign(aFetcher->mDictionary);
+ if (!dictName.IsEmpty()) {
+ if (NS_SUCCEEDED(TryDictionary(dictName, dictList, DICT_NORMAL_COMPARE))) {
+#ifdef DEBUG_DICT
+ printf("***** Assigned from content preferences |%s|\n",
+ NS_ConvertUTF16toUTF8(dictName).get());
+#endif
+
+ // We take an early exit here, so let's not forget to clear the word
+ // list.
+ DeleteSuggestedWordList();
+ return NS_OK;
+ }
+ // May be dictionary was uninstalled ?
+ // Clear the content preference and continue.
+ ClearCurrentDictionary(mEditor);
+ }
+ }
+
+ // Priority 2:
+ // After checking the content preferences, we use the language of the element
+ // or document.
+ dictName.Assign(mPreferredLang);
+#ifdef DEBUG_DICT
+ printf("***** Assigned from element/doc |%s|\n",
+ NS_ConvertUTF16toUTF8(dictName).get());
+#endif
+
+ // Get the preference value.
+ nsAutoString preferredDict;
+ preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary");
+
+ // The following will be driven by this status. Once we were able to set a
+ // dictionary successfully, we're done. So we start with a "failed" status.
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ if (!dictName.IsEmpty()) {
+ // RFC 5646 explicitly states that matches should be case-insensitive.
+ rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE);
+
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG_DICT
+ printf("***** Setting of |%s| failed (or it wasn't available)\n",
+ NS_ConvertUTF16toUTF8(dictName).get());
+#endif
+
+ // Required dictionary was not available. Try to get a dictionary
+ // matching at least language part of dictName.
+ nsAutoString langCode;
+ int32_t dashIdx = dictName.FindChar('-');
+ if (dashIdx != -1) {
+ langCode.Assign(Substring(dictName, 0, dashIdx));
+ } else {
+ langCode.Assign(dictName);
+ }
+
+ // Try dictionary.spellchecker preference, if it starts with langCode,
+ // so we don't just get any random dictionary matching the language.
+ if (!preferredDict.IsEmpty() &&
+ nsStyleUtil::DashMatchCompare(preferredDict, langCode, nsDefaultStringComparator())) {
+#ifdef DEBUG_DICT
+ printf("***** Trying preference value |%s| since it matches language code\n",
+ NS_ConvertUTF16toUTF8(preferredDict).get());
+#endif
+ rv = TryDictionary (preferredDict, dictList,
+ DICT_COMPARE_CASE_INSENSITIVE);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Use any dictionary with the required language.
+#ifdef DEBUG_DICT
+ printf("***** Trying to find match for language code |%s|\n",
+ NS_ConvertUTF16toUTF8(langCode).get());
+#endif
+ rv = TryDictionary (langCode, dictList, DICT_COMPARE_DASHMATCH);
+ }
+ }
+ }
+
+ // Priority 3:
+ // If the document didn't supply a dictionary or the setting failed,
+ // try the user preference next.
+ if (NS_FAILED(rv)) {
+ if (!preferredDict.IsEmpty()) {
+#ifdef DEBUG_DICT
+ printf("***** Trying preference value |%s|\n",
+ NS_ConvertUTF16toUTF8(preferredDict).get());
+#endif
+ rv = TryDictionary (preferredDict, dictList, DICT_NORMAL_COMPARE);
+ }
+ }
+
+ // Priority 4:
+ // As next fallback, try the current locale.
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
+ mozilla::services::GetXULChromeRegistryService();
+
+ if (packageRegistry) {
+ nsAutoCString utf8DictName;
+ rv2 = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"),
+ false, utf8DictName);
+ dictName.Assign(EmptyString());
+ AppendUTF8toUTF16(utf8DictName, dictName);
+#ifdef DEBUG_DICT
+ printf("***** Trying locale |%s|\n",
+ NS_ConvertUTF16toUTF8(dictName).get());
+#endif
+ rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ // Still no success.
+
+ // Priority 5:
+ // If we have a current dictionary, don't try anything else.
+ nsAutoString currentDictionary;
+ rv2 = GetCurrentDictionary(currentDictionary);
+#ifdef DEBUG_DICT
+ if (NS_SUCCEEDED(rv2)) {
+ printf("***** Retrieved current dict |%s|\n",
+ NS_ConvertUTF16toUTF8(currentDictionary).get());
+ }
+#endif
+
+ if (NS_FAILED(rv2) || currentDictionary.IsEmpty()) {
+ // Priority 6:
+ // Try to get current dictionary from environment variable LANG.
+ // LANG = language[_territory][.charset]
+ char* env_lang = getenv("LANG");
+ if (env_lang) {
+ nsString lang = NS_ConvertUTF8toUTF16(env_lang);
+ // Strip trailing charset, if there is any.
+ int32_t dot_pos = lang.FindChar('.');
+ if (dot_pos != -1) {
+ lang = Substring(lang, 0, dot_pos);
+ }
+
+ int32_t underScore = lang.FindChar('_');
+ if (underScore != -1) {
+ lang.Replace(underScore, 1, '-');
+#ifdef DEBUG_DICT
+ printf("***** Trying LANG from environment |%s|\n",
+ NS_ConvertUTF16toUTF8(lang).get());
+#endif
+ nsAutoString lang2;
+ lang2.Assign(lang);
+ rv = TryDictionary(lang2, dictList, DICT_COMPARE_CASE_INSENSITIVE);
+ }
+ }
+
+ // Priority 7:
+ // If it does not work, pick the first one.
+ if (NS_FAILED(rv) && !dictList.IsEmpty()) {
+ nsAutoString firstInList;
+ firstInList.Assign(dictList[0]);
+ rv = TryDictionary(firstInList, dictList, DICT_NORMAL_COMPARE);
+#ifdef DEBUG_DICT
+ printf("***** Trying first of list |%s|\n",
+ NS_ConvertUTF16toUTF8(dictList[0]).get());
+ if (NS_SUCCEEDED(rv)) {
+ printf ("***** Setting worked.\n");
+ }
+#endif
+ }
+ }
+ }
+
+ // If an error was thrown while setting the dictionary, just
+ // fail silently so that the spellchecker dialog is allowed to come
+ // up. The user can manually reset the language to their choice on
+ // the dialog if it is wrong.
+
+ DeleteSuggestedWordList();
+
+ return NS_OK;
+}
diff --git a/editor/composer/nsEditorSpellCheck.h b/editor/composer/nsEditorSpellCheck.h
new file mode 100644
index 000000000..e39a2c891
--- /dev/null
+++ b/editor/composer/nsEditorSpellCheck.h
@@ -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___
+
+
diff --git a/editor/composer/nsIEditingSession.idl b/editor/composer/nsIEditingSession.idl
new file mode 100644
index 000000000..6cbf00481
--- /dev/null
+++ b/editor/composer/nsIEditingSession.idl
@@ -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;
+};
+
diff --git a/editor/composer/res/EditorOverride.css b/editor/composer/res/EditorOverride.css
new file mode 100644
index 000000000..4940fbb30
--- /dev/null
+++ b/editor/composer/res/EditorOverride.css
@@ -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");
+}
diff --git a/editor/composer/res/grabber.gif b/editor/composer/res/grabber.gif
new file mode 100644
index 000000000..06749a64f
--- /dev/null
+++ b/editor/composer/res/grabber.gif
Binary files differ
diff --git a/editor/composer/res/table-add-column-after-active.gif b/editor/composer/res/table-add-column-after-active.gif
new file mode 100644
index 000000000..3ec50b82e
--- /dev/null
+++ b/editor/composer/res/table-add-column-after-active.gif
Binary files differ
diff --git a/editor/composer/res/table-add-column-after-hover.gif b/editor/composer/res/table-add-column-after-hover.gif
new file mode 100644
index 000000000..29679f981
--- /dev/null
+++ b/editor/composer/res/table-add-column-after-hover.gif
Binary files differ
diff --git a/editor/composer/res/table-add-column-after.gif b/editor/composer/res/table-add-column-after.gif
new file mode 100644
index 000000000..8891be969
--- /dev/null
+++ b/editor/composer/res/table-add-column-after.gif
Binary files differ
diff --git a/editor/composer/res/table-add-column-before-active.gif b/editor/composer/res/table-add-column-before-active.gif
new file mode 100644
index 000000000..1e205291e
--- /dev/null
+++ b/editor/composer/res/table-add-column-before-active.gif
Binary files differ
diff --git a/editor/composer/res/table-add-column-before-hover.gif b/editor/composer/res/table-add-column-before-hover.gif
new file mode 100644
index 000000000..7b54537e4
--- /dev/null
+++ b/editor/composer/res/table-add-column-before-hover.gif
Binary files differ
diff --git a/editor/composer/res/table-add-column-before.gif b/editor/composer/res/table-add-column-before.gif
new file mode 100644
index 000000000..d4a3ffe5e
--- /dev/null
+++ b/editor/composer/res/table-add-column-before.gif
Binary files differ
diff --git a/editor/composer/res/table-add-row-after-active.gif b/editor/composer/res/table-add-row-after-active.gif
new file mode 100644
index 000000000..cc01da2c9
--- /dev/null
+++ b/editor/composer/res/table-add-row-after-active.gif
Binary files differ
diff --git a/editor/composer/res/table-add-row-after-hover.gif b/editor/composer/res/table-add-row-after-hover.gif
new file mode 100644
index 000000000..a829351b6
--- /dev/null
+++ b/editor/composer/res/table-add-row-after-hover.gif
Binary files differ
diff --git a/editor/composer/res/table-add-row-after.gif b/editor/composer/res/table-add-row-after.gif
new file mode 100644
index 000000000..3f1a39d98
--- /dev/null
+++ b/editor/composer/res/table-add-row-after.gif
Binary files differ
diff --git a/editor/composer/res/table-add-row-before-active.gif b/editor/composer/res/table-add-row-before-active.gif
new file mode 100644
index 000000000..34f1e0ade
--- /dev/null
+++ b/editor/composer/res/table-add-row-before-active.gif
Binary files differ
diff --git a/editor/composer/res/table-add-row-before-hover.gif b/editor/composer/res/table-add-row-before-hover.gif
new file mode 100644
index 000000000..e8f1d10b0
--- /dev/null
+++ b/editor/composer/res/table-add-row-before-hover.gif
Binary files differ
diff --git a/editor/composer/res/table-add-row-before.gif b/editor/composer/res/table-add-row-before.gif
new file mode 100644
index 000000000..1682170cb
--- /dev/null
+++ b/editor/composer/res/table-add-row-before.gif
Binary files differ
diff --git a/editor/composer/res/table-remove-column-active.gif b/editor/composer/res/table-remove-column-active.gif
new file mode 100644
index 000000000..4dfbde4ce
--- /dev/null
+++ b/editor/composer/res/table-remove-column-active.gif
Binary files differ
diff --git a/editor/composer/res/table-remove-column-hover.gif b/editor/composer/res/table-remove-column-hover.gif
new file mode 100644
index 000000000..fd11bb52c
--- /dev/null
+++ b/editor/composer/res/table-remove-column-hover.gif
Binary files differ
diff --git a/editor/composer/res/table-remove-column.gif b/editor/composer/res/table-remove-column.gif
new file mode 100644
index 000000000..d8071da0a
--- /dev/null
+++ b/editor/composer/res/table-remove-column.gif
Binary files differ
diff --git a/editor/composer/res/table-remove-row-active.gif b/editor/composer/res/table-remove-row-active.gif
new file mode 100644
index 000000000..4dfbde4ce
--- /dev/null
+++ b/editor/composer/res/table-remove-row-active.gif
Binary files differ
diff --git a/editor/composer/res/table-remove-row-hover.gif b/editor/composer/res/table-remove-row-hover.gif
new file mode 100644
index 000000000..fd11bb52c
--- /dev/null
+++ b/editor/composer/res/table-remove-row-hover.gif
Binary files differ
diff --git a/editor/composer/res/table-remove-row.gif b/editor/composer/res/table-remove-row.gif
new file mode 100644
index 000000000..d8071da0a
--- /dev/null
+++ b/editor/composer/res/table-remove-row.gif
Binary files differ
diff --git a/editor/composer/test/bug1200533_subframe.html b/editor/composer/test/bug1200533_subframe.html
new file mode 100644
index 000000000..6eaefc266
--- /dev/null
+++ b/editor/composer/test/bug1200533_subframe.html
@@ -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>
diff --git a/editor/composer/test/bug1204147_subframe.html b/editor/composer/test/bug1204147_subframe.html
new file mode 100644
index 000000000..a9b1225cd
--- /dev/null
+++ b/editor/composer/test/bug1204147_subframe.html
@@ -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>
diff --git a/editor/composer/test/bug1204147_subframe2.html b/editor/composer/test/bug1204147_subframe2.html
new file mode 100644
index 000000000..935777bd9
--- /dev/null
+++ b/editor/composer/test/bug1204147_subframe2.html
@@ -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>
diff --git a/editor/composer/test/bug678842_subframe.html b/editor/composer/test/bug678842_subframe.html
new file mode 100644
index 000000000..39d578ee4
--- /dev/null
+++ b/editor/composer/test/bug678842_subframe.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<textarea id="textarea" lang="testing-XXX"></textarea>
+</body>
+</html>
diff --git a/editor/composer/test/bug717433_subframe.html b/editor/composer/test/bug717433_subframe.html
new file mode 100644
index 000000000..3c2927e88
--- /dev/null
+++ b/editor/composer/test/bug717433_subframe.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<textarea id="textarea" lang="en"></textarea>
+</body>
+</html>
diff --git a/editor/composer/test/chrome.ini b/editor/composer/test/chrome.ini
new file mode 100644
index 000000000..015cad3d5
--- /dev/null
+++ b/editor/composer/test/chrome.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+skip-if = os == 'android'
+
+[test_bug434998.xul]
+[test_bug1266815.html]
diff --git a/editor/composer/test/de-DE/de_DE.aff b/editor/composer/test/de-DE/de_DE.aff
new file mode 100644
index 000000000..5dc6896b6
--- /dev/null
+++ b/editor/composer/test/de-DE/de_DE.aff
@@ -0,0 +1,2 @@
+# Affix file for German English dictionary
+# Fake file, nothing here.
diff --git a/editor/composer/test/de-DE/de_DE.dic b/editor/composer/test/de-DE/de_DE.dic
new file mode 100644
index 000000000..415c21686
--- /dev/null
+++ b/editor/composer/test/de-DE/de_DE.dic
@@ -0,0 +1,6 @@
+5
+ein
+guter
+heute
+ist
+Tag
diff --git a/editor/composer/test/en-AU/en_AU.aff b/editor/composer/test/en-AU/en_AU.aff
new file mode 100644
index 000000000..e0c467248
--- /dev/null
+++ b/editor/composer/test/en-AU/en_AU.aff
@@ -0,0 +1,2 @@
+# Affix file for British English dictionary
+# Fake file, nothing here.
diff --git a/editor/composer/test/en-AU/en_AU.dic b/editor/composer/test/en-AU/en_AU.dic
new file mode 100644
index 000000000..0a1be725d
--- /dev/null
+++ b/editor/composer/test/en-AU/en_AU.dic
@@ -0,0 +1,4 @@
+3
+Mary
+Paul
+Peter
diff --git a/editor/composer/test/en-GB/en_GB.aff b/editor/composer/test/en-GB/en_GB.aff
new file mode 100644
index 000000000..e0c467248
--- /dev/null
+++ b/editor/composer/test/en-GB/en_GB.aff
@@ -0,0 +1,2 @@
+# Affix file for British English dictionary
+# Fake file, nothing here.
diff --git a/editor/composer/test/en-GB/en_GB.dic b/editor/composer/test/en-GB/en_GB.dic
new file mode 100644
index 000000000..0a1be725d
--- /dev/null
+++ b/editor/composer/test/en-GB/en_GB.dic
@@ -0,0 +1,4 @@
+3
+Mary
+Paul
+Peter
diff --git a/editor/composer/test/mochitest.ini b/editor/composer/test/mochitest.ini
new file mode 100644
index 000000000..832137d60
--- /dev/null
+++ b/editor/composer/test/mochitest.ini
@@ -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'
diff --git a/editor/composer/test/test_async_UpdateCurrentDictionary.html b/editor/composer/test/test_async_UpdateCurrentDictionary.html
new file mode 100644
index 000000000..53d9ff307
--- /dev/null
+++ b/editor/composer/test/test_async_UpdateCurrentDictionary.html
@@ -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>
+
diff --git a/editor/composer/test/test_bug1200533.html b/editor/composer/test/test_bug1200533.html
new file mode 100644
index 000000000..310b8d7cf
--- /dev/null
+++ b/editor/composer/test/test_bug1200533.html
@@ -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>
diff --git a/editor/composer/test/test_bug1204147.html b/editor/composer/test/test_bug1204147.html
new file mode 100644
index 000000000..d7dd90474
--- /dev/null
+++ b/editor/composer/test/test_bug1204147.html
@@ -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>
diff --git a/editor/composer/test/test_bug1205983.html b/editor/composer/test/test_bug1205983.html
new file mode 100644
index 000000000..139f6fc3c
--- /dev/null
+++ b/editor/composer/test/test_bug1205983.html
@@ -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>
diff --git a/editor/composer/test/test_bug1209414.html b/editor/composer/test/test_bug1209414.html
new file mode 100644
index 000000000..bce1bb313
--- /dev/null
+++ b/editor/composer/test/test_bug1209414.html
@@ -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>
diff --git a/editor/composer/test/test_bug1219928.html b/editor/composer/test/test_bug1219928.html
new file mode 100644
index 000000000..2f646f00f
--- /dev/null
+++ b/editor/composer/test/test_bug1219928.html
@@ -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>
diff --git a/editor/composer/test/test_bug1266815.html b/editor/composer/test/test_bug1266815.html
new file mode 100644
index 000000000..a2ab0b048
--- /dev/null
+++ b/editor/composer/test/test_bug1266815.html
@@ -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>
diff --git a/editor/composer/test/test_bug338427.html b/editor/composer/test/test_bug338427.html
new file mode 100644
index 000000000..f16194b3d
--- /dev/null
+++ b/editor/composer/test/test_bug338427.html
@@ -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>
+
diff --git a/editor/composer/test/test_bug348497.html b/editor/composer/test/test_bug348497.html
new file mode 100644
index 000000000..9483b727a
--- /dev/null
+++ b/editor/composer/test/test_bug348497.html
@@ -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>
+
diff --git a/editor/composer/test/test_bug384147.html b/editor/composer/test/test_bug384147.html
new file mode 100644
index 000000000..35f0e533e
--- /dev/null
+++ b/editor/composer/test/test_bug384147.html
@@ -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>
+
diff --git a/editor/composer/test/test_bug389350.html b/editor/composer/test/test_bug389350.html
new file mode 100644
index 000000000..bf1d514ad
--- /dev/null
+++ b/editor/composer/test/test_bug389350.html
@@ -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>
diff --git a/editor/composer/test/test_bug434998.xul b/editor/composer/test/test_bug434998.xul
new file mode 100644
index 000000000..4a384ac1c
--- /dev/null
+++ b/editor/composer/test/test_bug434998.xul
@@ -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>
diff --git a/editor/composer/test/test_bug519928.html b/editor/composer/test/test_bug519928.html
new file mode 100644
index 000000000..3b1a9ba17
--- /dev/null
+++ b/editor/composer/test/test_bug519928.html
@@ -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>
diff --git a/editor/composer/test/test_bug678842.html b/editor/composer/test/test_bug678842.html
new file mode 100644
index 000000000..226b25ee2
--- /dev/null
+++ b/editor/composer/test/test_bug678842.html
@@ -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>
diff --git a/editor/composer/test/test_bug697981.html b/editor/composer/test/test_bug697981.html
new file mode 100644
index 000000000..f9417acb0
--- /dev/null
+++ b/editor/composer/test/test_bug697981.html
@@ -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>
diff --git a/editor/composer/test/test_bug717433.html b/editor/composer/test/test_bug717433.html
new file mode 100644
index 000000000..59ad9b247
--- /dev/null
+++ b/editor/composer/test/test_bug717433.html
@@ -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>
diff --git a/editor/composer/test/test_bug738440.html b/editor/composer/test/test_bug738440.html
new file mode 100644
index 000000000..a021906cf
--- /dev/null
+++ b/editor/composer/test/test_bug738440.html
@@ -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>