summaryrefslogtreecommitdiffstats
path: root/editor/composer
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /editor/composer
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--editor/composer/ComposerCommandsUpdater.cpp270
-rw-r--r--editor/composer/ComposerCommandsUpdater.h124
-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.build63
-rw-r--r--editor/composer/nsEditingSession.cpp1303
-rw-r--r--editor/composer/nsEditingSession.h170
-rw-r--r--editor/composer/nsIEditingSession.idl85
-rw-r--r--editor/composer/res/EditorOverride.css322
-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/.eslintrc.js5
-rw-r--r--editor/composer/test/chrome.ini5
-rw-r--r--editor/composer/test/file_bug1453190.html12
-rw-r--r--editor/composer/test/mochitest.ini11
-rw-r--r--editor/composer/test/test_bug1266815.html80
-rw-r--r--editor/composer/test/test_bug1453190.html36
-rw-r--r--editor/composer/test/test_bug348497.html36
-rw-r--r--editor/composer/test/test_bug384147.html203
-rw-r--r--editor/composer/test/test_bug389350.html33
-rw-r--r--editor/composer/test/test_bug434998.xhtml106
-rw-r--r--editor/composer/test/test_bug519928.html119
-rw-r--r--editor/composer/test/test_bug738440.html37
47 files changed, 3170 insertions, 0 deletions
diff --git a/editor/composer/ComposerCommandsUpdater.cpp b/editor/composer/ComposerCommandsUpdater.cpp
new file mode 100644
index 0000000000..c7e1a376b1
--- /dev/null
+++ b/editor/composer/ComposerCommandsUpdater.cpp
@@ -0,0 +1,270 @@
+/* -*- 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/ComposerCommandsUpdater.h"
+
+#include "mozilla/mozalloc.h" // for operator new
+#include "mozilla/TransactionManager.h" // for TransactionManager
+#include "mozilla/dom/Selection.h"
+#include "nsCommandManager.h" // for nsCommandManager
+#include "nsComponentManagerUtils.h" // for do_CreateInstance
+#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
+#include "nsDocShell.h" // for nsIDocShell
+#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc
+#include "nsID.h" // for NS_GET_IID, etc
+#include "nsIInterfaceRequestorUtils.h" // for do_GetInterface
+#include "nsITransactionManager.h" // for nsITransactionManager
+#include "nsLiteralString.h" // for NS_LITERAL_STRING
+#include "nsPIDOMWindow.h" // for nsPIDOMWindow
+
+class nsITransaction;
+
+namespace mozilla {
+
+ComposerCommandsUpdater::ComposerCommandsUpdater()
+ : mDirtyState(eStateUninitialized),
+ mSelectionCollapsed(eStateUninitialized),
+ mFirstDoOfFirstUndo(true) {}
+
+ComposerCommandsUpdater::~ComposerCommandsUpdater() {
+ // cancel any outstanding update timer
+ if (mUpdateTimer) {
+ mUpdateTimer->Cancel();
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ComposerCommandsUpdater)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ComposerCommandsUpdater)
+
+NS_INTERFACE_MAP_BEGIN(ComposerCommandsUpdater)
+ NS_INTERFACE_MAP_ENTRY(nsITransactionListener)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransactionListener)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ComposerCommandsUpdater)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(ComposerCommandsUpdater, mUpdateTimer, mDOMWindow,
+ mDocShell)
+
+#if 0
+# pragma mark -
+#endif
+
+NS_IMETHODIMP ComposerCommandsUpdater::WillDo(nsITransactionManager* aManager,
+ nsITransaction* aTransaction,
+ bool* aInterrupt) {
+ *aInterrupt = false;
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+NS_IMETHODIMP ComposerCommandsUpdater::DidDo(nsITransactionManager* aManager,
+ nsITransaction* aTransaction,
+ nsresult aDoResult) {
+ // only need to update if the status of the Undo menu item changes.
+ size_t undoCount = aManager->AsTransactionManager()->NumberOfUndoItems();
+ if (undoCount == 1) {
+ if (mFirstDoOfFirstUndo) {
+ UpdateCommandGroup(CommandGroup::Undo);
+ }
+ mFirstDoOfFirstUndo = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::WillUndo(nsITransactionManager* aManager,
+ nsITransaction* aTransaction,
+ bool* aInterrupt) {
+ *aInterrupt = false;
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+NS_IMETHODIMP ComposerCommandsUpdater::DidUndo(nsITransactionManager* aManager,
+ nsITransaction* aTransaction,
+ nsresult aUndoResult) {
+ size_t undoCount = aManager->AsTransactionManager()->NumberOfUndoItems();
+ if (!undoCount) {
+ mFirstDoOfFirstUndo = true; // reset the state for the next do
+ }
+ UpdateCommandGroup(CommandGroup::Undo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::WillRedo(nsITransactionManager* aManager,
+ nsITransaction* aTransaction,
+ bool* aInterrupt) {
+ *aInterrupt = false;
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+NS_IMETHODIMP ComposerCommandsUpdater::DidRedo(nsITransactionManager* aManager,
+ nsITransaction* aTransaction,
+ nsresult aRedoResult) {
+ UpdateCommandGroup(CommandGroup::Undo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::WillBeginBatch(
+ nsITransactionManager* aManager, bool* aInterrupt) {
+ *aInterrupt = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::DidBeginBatch(
+ nsITransactionManager* aManager, nsresult aResult) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::WillEndBatch(
+ nsITransactionManager* aManager, bool* aInterrupt) {
+ *aInterrupt = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::DidEndBatch(
+ nsITransactionManager* aManager, nsresult aResult) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::WillMerge(
+ nsITransactionManager* aManager, nsITransaction* aTopTransaction,
+ nsITransaction* aTransactionToMerge, bool* aInterrupt) {
+ *aInterrupt = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::DidMerge(
+ nsITransactionManager* aManager, nsITransaction* aTopTransaction,
+ nsITransaction* aTransactionToMerge, bool aDidMerge,
+ nsresult aMergeResult) {
+ return NS_OK;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+void ComposerCommandsUpdater::Init(nsPIDOMWindowOuter& aDOMWindow) {
+ mDOMWindow = &aDOMWindow;
+ mDocShell = aDOMWindow.GetDocShell();
+}
+
+nsresult ComposerCommandsUpdater::PrimeUpdateTimer() {
+ if (!mUpdateTimer) {
+ mUpdateTimer = NS_NewTimer();
+ }
+ const uint32_t kUpdateTimerDelay = 150;
+ return mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this),
+ kUpdateTimerDelay,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+void ComposerCommandsUpdater::TimerCallback() {
+ mSelectionCollapsed = SelectionIsCollapsed();
+ UpdateCommandGroup(CommandGroup::Style);
+}
+
+void ComposerCommandsUpdater::UpdateCommandGroup(CommandGroup aCommandGroup) {
+ RefPtr<nsCommandManager> commandManager = GetCommandManager();
+ if (NS_WARN_IF(!commandManager)) {
+ return;
+ }
+
+ switch (aCommandGroup) {
+ case CommandGroup::Undo:
+ commandManager->CommandStatusChanged("cmd_undo");
+ commandManager->CommandStatusChanged("cmd_redo");
+ return;
+ case CommandGroup::Style:
+ commandManager->CommandStatusChanged("cmd_bold");
+ commandManager->CommandStatusChanged("cmd_italic");
+ commandManager->CommandStatusChanged("cmd_underline");
+ commandManager->CommandStatusChanged("cmd_tt");
+
+ commandManager->CommandStatusChanged("cmd_strikethrough");
+ commandManager->CommandStatusChanged("cmd_superscript");
+ commandManager->CommandStatusChanged("cmd_subscript");
+ commandManager->CommandStatusChanged("cmd_nobreak");
+
+ commandManager->CommandStatusChanged("cmd_em");
+ commandManager->CommandStatusChanged("cmd_strong");
+ commandManager->CommandStatusChanged("cmd_cite");
+ commandManager->CommandStatusChanged("cmd_abbr");
+ commandManager->CommandStatusChanged("cmd_acronym");
+ commandManager->CommandStatusChanged("cmd_code");
+ commandManager->CommandStatusChanged("cmd_samp");
+ commandManager->CommandStatusChanged("cmd_var");
+
+ commandManager->CommandStatusChanged("cmd_increaseFont");
+ commandManager->CommandStatusChanged("cmd_decreaseFont");
+
+ commandManager->CommandStatusChanged("cmd_paragraphState");
+ commandManager->CommandStatusChanged("cmd_fontFace");
+ commandManager->CommandStatusChanged("cmd_fontColor");
+ commandManager->CommandStatusChanged("cmd_backgroundColor");
+ commandManager->CommandStatusChanged("cmd_highlight");
+ return;
+ case CommandGroup::Save:
+ commandManager->CommandStatusChanged("cmd_setDocumentModified");
+ commandManager->CommandStatusChanged("cmd_save");
+ return;
+ default:
+ MOZ_ASSERT_UNREACHABLE("New command group hasn't been implemented yet");
+ }
+}
+
+nsresult ComposerCommandsUpdater::UpdateOneCommand(const char* aCommand) {
+ RefPtr<nsCommandManager> commandManager = GetCommandManager();
+ NS_ENSURE_TRUE(commandManager, NS_ERROR_FAILURE);
+ commandManager->CommandStatusChanged(aCommand);
+ return NS_OK;
+}
+
+bool ComposerCommandsUpdater::SelectionIsCollapsed() {
+ if (NS_WARN_IF(!mDOMWindow)) {
+ return true;
+ }
+
+ RefPtr<dom::Selection> domSelection = mDOMWindow->GetSelection();
+ if (NS_WARN_IF(!domSelection)) {
+ return false;
+ }
+
+ return domSelection->IsCollapsed();
+}
+
+nsCommandManager* ComposerCommandsUpdater::GetCommandManager() {
+ if (NS_WARN_IF(!mDocShell)) {
+ return nullptr;
+ }
+ return mDocShell->GetCommandManager();
+}
+
+NS_IMETHODIMP ComposerCommandsUpdater::GetName(nsACString& aName) {
+ aName.AssignLiteral("ComposerCommandsUpdater");
+ return NS_OK;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+nsresult ComposerCommandsUpdater::Notify(nsITimer* aTimer) {
+ NS_ASSERTION(aTimer == mUpdateTimer.get(), "Hey, this ain't my timer!");
+ TimerCallback();
+ return NS_OK;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+} // namespace mozilla
diff --git a/editor/composer/ComposerCommandsUpdater.h b/editor/composer/ComposerCommandsUpdater.h
new file mode 100644
index 0000000000..43982d6470
--- /dev/null
+++ b/editor/composer/ComposerCommandsUpdater.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ComposerCommandsUpdater_h
+#define mozilla_ComposerCommandsUpdater_h
+
+#include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr
+#include "nsCycleCollectionParticipant.h"
+#include "nsINamed.h"
+#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS
+#include "nsITimer.h" // for NS_DECL_NSITIMERCALLBACK, etc
+#include "nsITransactionListener.h" // for nsITransactionListener
+#include "nscore.h" // for NS_IMETHOD, nsresult, etc
+
+class nsCommandManager;
+class nsIDocShell;
+class nsITransaction;
+class nsITransactionManager;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+class ComposerCommandsUpdater final : public nsITransactionListener,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ ComposerCommandsUpdater();
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ComposerCommandsUpdater,
+ nsITransactionListener)
+
+ // nsITimerCallback
+ NS_DECL_NSITIMERCALLBACK
+
+ // nsINamed
+ NS_DECL_NSINAMED
+
+ // nsITransactionListener
+ NS_DECL_NSITRANSACTIONLISTENER
+
+ void Init(nsPIDOMWindowOuter& aDOMWindow);
+
+ /**
+ * OnSelectionChange() is called when selection is changed in the editor.
+ */
+ void OnSelectionChange() { PrimeUpdateTimer(); }
+
+ /**
+ * OnHTMLEditorCreated() is called when `HTMLEditor` is created and
+ * initialized.
+ */
+ MOZ_CAN_RUN_SCRIPT void OnHTMLEditorCreated() {
+ UpdateOneCommand("obs_documentCreated");
+ }
+
+ /**
+ * OnBeforeHTMLEditorDestroyed() is called when `HTMLEditor` is being
+ * destroyed.
+ */
+ MOZ_CAN_RUN_SCRIPT void OnBeforeHTMLEditorDestroyed() {
+ // cancel any outstanding update timer
+ if (mUpdateTimer) {
+ mUpdateTimer->Cancel();
+ mUpdateTimer = nullptr;
+ }
+
+ // We can't notify the command manager of this right now; it is too late in
+ // some cases and the window is already partially destructed (e.g. JS
+ // objects may be gone).
+ }
+
+ /**
+ * OnHTMLEditorDirtyStateChanged() is called when dirty state of `HTMLEditor`
+ * is changed form or to "dirty".
+ */
+ MOZ_CAN_RUN_SCRIPT void OnHTMLEditorDirtyStateChanged(bool aNowDirty) {
+ if (mDirtyState == static_cast<int8_t>(aNowDirty)) {
+ return;
+ }
+ UpdateCommandGroup(CommandGroup::Save);
+ UpdateCommandGroup(CommandGroup::Undo);
+ mDirtyState = aNowDirty;
+ }
+
+ protected:
+ virtual ~ComposerCommandsUpdater();
+
+ enum {
+ eStateUninitialized = -1,
+ eStateOff = 0,
+ eStateOn = 1,
+ };
+
+ bool SelectionIsCollapsed();
+ MOZ_CAN_RUN_SCRIPT nsresult UpdateOneCommand(const char* aCommand);
+ enum class CommandGroup {
+ Save,
+ Style,
+ Undo,
+ };
+ MOZ_CAN_RUN_SCRIPT void UpdateCommandGroup(CommandGroup aCommandGroup);
+
+ nsCommandManager* GetCommandManager();
+
+ nsresult PrimeUpdateTimer();
+ void TimerCallback();
+
+ nsCOMPtr<nsITimer> mUpdateTimer;
+ nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow;
+ nsCOMPtr<nsIDocShell> mDocShell;
+
+ int8_t mDirtyState;
+ int8_t mSelectionCollapsed;
+ bool mFirstDoOfFirstUndo;
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_ComposerCommandsUpdater_h
diff --git a/editor/composer/crashtests/351236-1.html b/editor/composer/crashtests/351236-1.html
new file mode 100644
index 0000000000..99674f1814
--- /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>
diff --git a/editor/composer/crashtests/407062-1.html b/editor/composer/crashtests/407062-1.html
new file mode 100644
index 0000000000..9bd5d02e59
--- /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.firstChild.remove();
+
+ 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 0000000000..417530c13a
--- /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 0000000000..1cc72d0856
--- /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 0000000000..793aababd4
--- /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");
+ });
+ 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 0000000000..fea188e646
--- /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);
+ document.execCommand("undo", false, null);
+}
+
+function boom()
+{
+ document.execCommand("selectAll", false, null);
+ document.execCommand("strikethrough", false, null);
+ document.addEventListener("DOMSubtreeModified", uu);
+ 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 0000000000..db84e0e5b1
--- /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 0000000000..cbf206d7ed
--- /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 0000000000..cbf104ac99
--- /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 0000000000..48335a2704
--- /dev/null
+++ b/editor/composer/moz.build
@@ -0,0 +1,63 @@
+# -*- 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 += [
+ "ComposerCommandsUpdater.cpp",
+ "nsEditingSession.cpp",
+]
+
+EXPORTS += [
+ "nsEditingSession.h",
+]
+
+EXPORTS.mozilla += [
+ "ComposerCommandsUpdater.h",
+]
+
+# Needed because we include HTMLEditor.h which indirectly includes Document.h
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/html", # For nsHTMLDocument
+ "/editor/spellchecker", # nsComposeTxtSrvFilter.h
+ "/layout/style", # For things nsHTMLDocument includes.
+]
+
+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["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/editor/composer/nsEditingSession.cpp b/editor/composer/nsEditingSession.cpp
new file mode 100644
index 0000000000..0b785fd5b9
--- /dev/null
+++ b/editor/composer/nsEditingSession.cpp
@@ -0,0 +1,1303 @@
+/* -*- 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 "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater
+#include "mozilla/FlushType.h" // for FlushType::Frames
+#include "mozilla/HTMLEditor.h" // for HTMLEditor
+#include "mozilla/mozalloc.h" // for operator new
+#include "mozilla/PresShell.h" // for PresShell
+#include "nsAString.h"
+#include "nsBaseCommandController.h" // for nsBaseCommandController
+#include "nsCommandManager.h" // for nsCommandManager
+#include "nsComponentManagerUtils.h" // for do_CreateInstance
+#include "nsContentUtils.h"
+#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 "nsIContentViewer.h" // for nsIContentViewer
+#include "nsIControllers.h" // for nsIControllers
+#include "nsID.h" // for NS_GET_IID, etc
+#include "nsHTMLDocument.h" // for nsHTMLDocument
+#include "nsIDocShell.h" // for nsIDocShell
+#include "mozilla/dom/Document.h" // for Document
+#include "nsIEditor.h" // for nsIEditor
+#include "nsIInterfaceRequestorUtils.h" // for do_GetInterface
+#include "nsIRefreshURI.h" // for nsIRefreshURI
+#include "nsIRequest.h" // for nsIRequest
+#include "nsITimer.h" // for nsITimer, etc
+#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 "nsPIDOMWindow.h" // for nsPIDOMWindow
+#include "nsPresContext.h" // for nsPresContext
+#include "nsReadableUtils.h" // for AppendUTF16toUTF8
+#include "nsStringFwd.h" // for nsString
+#include "mozilla/dom/BrowsingContext.h" // for BrowsingContext
+#include "mozilla/dom/Selection.h" // for AutoHideSelectionChanges, etc
+#include "nsFrameSelection.h" // for nsFrameSelection
+#include "nsBaseCommandController.h" // for nsBaseCommandController
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+
+class nsISupports;
+class nsIURI;
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/*---------------------------------------------------------------------------
+
+ 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(*docShell);
+ 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(
+ nsBaseCommandController::CreateEditingController, 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(
+ nsBaseCommandController::CreateHTMLEditorDocStateController, 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(MOZ_KnownLive(*window));
+
+ // 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;
+}
+
+nsresult nsEditingSession::DisableJSAndPlugins(nsIDocShell& aDocShell) {
+ bool tmp;
+ nsresult rv = aDocShell.GetAllowJavascript(&tmp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mScriptsEnabled = tmp;
+
+ rv = aDocShell.SetAllowJavascript(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Disable plugins in this document:
+ mPluginsEnabled = aDocShell.PluginsAllowedInCurrentDoc();
+
+ rv = aDocShell.GetBrowsingContext()->SetAllowPlugins(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDisabledJSAndPlugins = true;
+
+ return NS_OK;
+}
+
+nsresult nsEditingSession::RestoreJSAndPlugins(nsPIDOMWindowOuter* aWindow) {
+ if (!mDisabledJSAndPlugins) {
+ return NS_OK;
+ }
+
+ mDisabledJSAndPlugins = false;
+
+ if (NS_WARN_IF(!aWindow)) {
+ // DetachFromWindow may call this method with nullptr.
+ return NS_ERROR_FAILURE;
+ }
+ nsIDocShell* docShell = aWindow->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsresult rv = docShell->SetAllowJavascript(mScriptsEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Disable plugins in this document:
+ auto* browsingContext = aWindow->GetBrowsingContext();
+ NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE);
+
+ return browsingContext->SetAllowPlugins(mPluginsEnabled);
+}
+
+/*---------------------------------------------------------------------------
+
+ 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);
+}
+
+bool IsSupportedTextType(const nsAString& aMIMEType) {
+ // 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
+ static constexpr nsLiteralString sSupportedTextTypes[] = {
+ u"text/plain"_ns,
+ u"text/css"_ns,
+ u"text/rdf"_ns,
+ u"text/xsl"_ns,
+ u"text/javascript"_ns, // obsolete type
+ u"text/ecmascript"_ns, // obsolete type
+ u"application/javascript"_ns,
+ u"application/ecmascript"_ns,
+ u"application/x-javascript"_ns, // obsolete type
+ u"text/xul"_ns // obsolete type
+ };
+
+ for (const nsLiteralString& supportedTextType : sSupportedTextTypes) {
+ if (aMIMEType.Equals(supportedTextType)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult nsEditingSession::SetupEditorOnWindow(nsPIDOMWindowOuter& aWindow) {
+ mDoneSetup = true;
+
+ // 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
+ nsAutoString mimeType;
+
+ // then lets check the mime type
+ if (RefPtr<Document> doc = aWindow.GetDoc()) {
+ doc->GetContentType(mimeType);
+
+ if (IsSupportedTextType(mimeType)) {
+ mEditorType.AssignLiteral("text");
+ mimeType.AssignLiteral("text/plain");
+ } else if (!doc->IsHTMLOrXHTML()) {
+ // Neither an acceptable text or html type.
+ mEditorStatus = eEditorErrorCantEditMimeType;
+
+ // Turn editor into HTML -- we will load blank page later
+ mEditorType.AssignLiteral("html");
+ mimeType.AssignLiteral("text/html");
+ }
+
+ // Flush out frame construction to make sure that the subframe's
+ // presshell is set up if it needs to be.
+ doc->FlushPendingNotifications(mozilla::FlushType::Frames);
+ if (mMakeWholeDocumentEditable) {
+ doc->SetEditableFlag(true);
+ // Enable usage of the execCommand API
+ doc->SetEditingState(Document::EditingState::eDesignMode);
+ }
+ }
+ bool needHTMLController = false;
+
+ if (mEditorType.EqualsLiteral("textmail")) {
+ mEditorFlags = nsIEditor::eEditorPlaintextMask |
+ nsIEditor::eEditorEnableWrapHackMask |
+ nsIEditor::eEditorMailMask;
+ } else if (mEditorType.EqualsLiteral("text")) {
+ mEditorFlags =
+ nsIEditor::eEditorPlaintextMask | nsIEditor::eEditorEnableWrapHackMask;
+ } else if (mEditorType.EqualsLiteral("htmlmail")) {
+ if (mimeType.EqualsLiteral("text/html")) {
+ needHTMLController = true;
+ mEditorFlags = nsIEditor::eEditorMailMask;
+ } else {
+ // Set the flags back to textplain.
+ mEditorFlags = nsIEditor::eEditorPlaintextMask |
+ nsIEditor::eEditorEnableWrapHackMask;
+ }
+ } else {
+ // Defaulted to html
+ needHTMLController = true;
+ }
+
+ if (mInteractive) {
+ mEditorFlags |= nsIEditor::eEditorAllowInteraction;
+ }
+
+ // make the UI state maintainer
+ mComposerCommandsUpdater = new ComposerCommandsUpdater();
+
+ // now init the state maintainer
+ // This allows notification of error state
+ // even if we don't create an editor
+ mComposerCommandsUpdater->Init(aWindow);
+
+ if (mEditorStatus != eEditorCreationInProgress) {
+ RefPtr<ComposerCommandsUpdater> updater = mComposerCommandsUpdater;
+ updater->OnHTMLEditorCreated();
+
+ // At this point we have made a final decision that we don't support
+ // editing the current document. This is an internal failure state, but
+ // we return NS_OK to avoid throwing an exception from the designMode
+ // setter for web compatibility. The document editing APIs will tell the
+ // developer if editing has been disabled because we're in a document type
+ // that doesn't support editing.
+ return NS_OK;
+ }
+
+ // Create editor and do other things
+ // only if we haven't found some error above,
+ nsCOMPtr<nsIDocShell> docShell = aWindow.GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return 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);
+ AutoHideSelectionChanges hideSelectionChanges(fs);
+
+ // create and set editor
+ // Try to reuse an existing editor
+ nsCOMPtr<nsIEditor> editor = do_QueryReferent(mExistingEditor);
+ RefPtr<HTMLEditor> htmlEditor = editor ? editor->AsHTMLEditor() : nullptr;
+ MOZ_ASSERT(!editor || htmlEditor);
+ if (htmlEditor) {
+ htmlEditor->PreDestroy(false);
+ } else {
+ htmlEditor = new HTMLEditor();
+ mExistingEditor =
+ do_GetWeakReference(static_cast<nsIEditor*>(htmlEditor.get()));
+ }
+ // set the editor on the docShell. The docShell now owns it.
+ nsresult rv = docShell->SetHTMLEditor(htmlEditor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // setup the HTML editor command controller
+ if (needHTMLController) {
+ // The third controller takes an nsIEditor as the context
+ rv = SetupEditorCommandController(
+ nsBaseCommandController::CreateHTMLEditorController, &aWindow,
+ static_cast<nsIEditor*>(htmlEditor), &mHTMLCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set mimetype on editor
+ rv = htmlEditor->SetContentsMIMEType(mimeType);
+ 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);
+
+ RefPtr<Document> doc = contentViewer->GetDocument();
+ if (NS_WARN_IF(!doc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set up as a doc state listener
+ // Important! We must have this to broadcast the "obs_documentCreated" message
+ htmlEditor->SetComposerCommandsUpdater(mComposerCommandsUpdater);
+
+ rv = htmlEditor->Init(*doc, nullptr /* root content */, nullptr, mEditorFlags,
+ u""_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<Selection> selection = htmlEditor->GetSelection();
+ if (NS_WARN_IF(!selection)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // and as a transaction listener
+ MOZ_ASSERT(mComposerCommandsUpdater);
+ DebugOnly<bool> addedTransactionListener =
+ htmlEditor->AddTransactionListener(*mComposerCommandsUpdater);
+ NS_WARNING_ASSERTION(addedTransactionListener,
+ "Failed to add transaction listener to the editor");
+
+ // Set context on all controllers to be the editor
+ rv = SetEditorOnControllers(aWindow, htmlEditor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Everything went fine!
+ mEditorStatus = eEditorOK;
+
+ // This will trigger documentCreation notification
+ return htmlEditor->PostCreate();
+}
+
+// Removes all listeners and controllers from aWindow and aEditor.
+void nsEditingSession::RemoveListenersAndControllers(
+ nsPIDOMWindowOuter* aWindow, HTMLEditor* aHTMLEditor) {
+ if (!mComposerCommandsUpdater || !aHTMLEditor) {
+ return;
+ }
+
+ // Remove all the listeners
+ aHTMLEditor->SetComposerCommandsUpdater(nullptr);
+ DebugOnly<bool> removedTransactionListener =
+ aHTMLEditor->RemoveTransactionListener(*mComposerCommandsUpdater);
+ NS_WARNING_ASSERTION(removedTransactionListener,
+ "Failed to remove transaction listener from the editor");
+
+ // 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);
+
+ // 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);
+
+ RefPtr<Document> doc = window->GetDoc();
+ bool stopEditing = doc && doc->IsEditingOn();
+ if (stopEditing) {
+ RemoveWebProgressListener(window);
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor();
+ if (stopEditing) {
+ doc->TearingDownEditor();
+ }
+
+ if (mComposerCommandsUpdater && htmlEditor) {
+ // Null out the editor on the controllers first to prevent their weak
+ // references from pointing to a destroyed editor.
+ SetEditorOnControllers(*window, 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, htmlEditor);
+
+ if (stopEditing) {
+ // Make things the way they were before we started editing.
+ RestoreJSAndPlugins(window);
+ RestoreAnimationMode(window);
+
+ if (mMakeWholeDocumentEditable) {
+ doc->SetEditableFlag(false);
+ doc->SetEditingState(Document::EditingState::eOff);
+ }
+ }
+
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ GetEditorForFrame
+
+ nsIEditor getEditorForFrame (in nsIDOMWindow aWindow);
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::GetEditorForWindow(mozIDOMWindowProxy* aWindow,
+ nsIEditor** outEditor) {
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIEditor> editor = GetHTMLEditorForWindow(aWindow);
+ editor.forget(outEditor);
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ 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) {
+ nsCString 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);
+ RefPtr<Document> doc = piWindow->GetDoc();
+ nsHTMLDocument* htmlDoc =
+ doc && doc->IsHTMLOrXHTML() ? doc->AsHTMLDocument() : nullptr;
+ if (htmlDoc && doc->IsWriting()) {
+ nsAutoString designMode;
+ htmlDoc->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) {
+ nsCString 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) {
+ MOZ_ASSERT_UNREACHABLE("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);
+
+ RefPtr<Document> 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);
+
+ RefPtr<nsCommandManager> commandManager = docShell->GetCommandManager();
+ commandManager->CommandStatusChanged("obs_documentLocationChanged");
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ OnStatusChange
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ OnSecurityChange
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ OnContentBlockingEvent
+
+----------------------------------------------------------------------------*/
+NS_IMETHODIMP
+nsEditingSession::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ MOZ_ASSERT_UNREACHABLE("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;
+ nsCString 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;
+ }
+
+ auto* window = nsPIDOMWindowOuter::From(domWindow);
+ nsIDocShell* docShell = window->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?
+ needsSetup = !docShell->GetHTMLEditor();
+ }
+
+ if (needsSetup) {
+ mCanCreateEditor = false;
+ rv = SetupEditorOnWindow(MOZ_KnownLive(*window));
+ 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;
+ }
+
+ rv = NS_NewTimerWithFuncCallback(getter_AddRefs(mLoadBlankDocTimer),
+ nsEditingSession::TimerCallback,
+ static_cast<void*>(mDocShell.get()),
+ 10, nsITimer::TYPE_ONE_SHOT,
+ "nsEditingSession::EndDocumentLoad");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEditorStatus = eEditorCreationInProgress;
+ }
+ }
+ }
+ }
+ 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) {
+ LoadURIOptions loadURIOptions;
+ loadURIOptions.mTriggeringPrincipal =
+ nsContentUtils::GetSystemPrincipal();
+ webNav->LoadURI(u"about:blank"_ns, loadURIOptions);
+ }
+ }
+}
+
+/*---------------------------------------------------------------------------
+
+ 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;
+ nsCString 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(
+ nsEditingSession::ControllerCreatorFn aControllerCreatorFn,
+ mozIDOMWindowProxy* aWindow, nsISupports* aContext,
+ uint32_t* aControllerId) {
+ NS_ENSURE_ARG_POINTER(aControllerCreatorFn);
+ 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) {
+ RefPtr<nsBaseCommandController> commandController = aControllerCreatorFn();
+ NS_ENSURE_TRUE(commandController, NS_ERROR_FAILURE);
+
+ // 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, commandController);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remember the ID for the controller
+ rv = controllers->GetControllerId(commandController, aControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set the context
+ return SetContextOnControllerById(controllers, aContext, *aControllerId);
+}
+
+nsresult nsEditingSession::SetEditorOnControllers(nsPIDOMWindowOuter& aWindow,
+ HTMLEditor* aEditor) {
+ nsCOMPtr<nsIControllers> controllers;
+ nsresult rv = aWindow.GetControllers(getter_AddRefs(controllers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> editorAsISupports = static_cast<nsIEditor*>(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);
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return;
+ }
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE_VOID(presContext);
+
+ presContext->SetImageAnimationMode(mImageAnimationMode);
+}
+
+nsresult nsEditingSession::DetachFromWindow(nsPIDOMWindowOuter* aWindow) {
+ NS_ENSURE_TRUE(mDoneSetup, NS_OK);
+
+ NS_ASSERTION(mComposerCommandsUpdater,
+ "mComposerCommandsUpdater should exist.");
+
+ // Kill any existing reload timer
+ if (mLoadBlankDocTimer) {
+ mLoadBlankDocTimer->Cancel();
+ mLoadBlankDocTimer = nullptr;
+ }
+
+ // Remove controllers, webprogress listener, and otherwise
+ // make things the way they were before we started editing.
+ RemoveEditorControllers(aWindow);
+ RemoveWebProgressListener(aWindow);
+ RestoreJSAndPlugins(aWindow);
+ RestoreAnimationMode(aWindow);
+
+ // 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(nsPIDOMWindowOuter* aWindow) {
+ NS_ENSURE_TRUE(mDoneSetup, NS_OK);
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
+
+ NS_ASSERTION(mComposerCommandsUpdater,
+ "mComposerCommandsUpdater should exist.");
+
+ // Imitate nsEditorDocShell::MakeEditable() to reattach the
+ // old editor to the window.
+ nsresult rv;
+
+ nsIDocShell* docShell = aWindow->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+ mDocShell = do_GetWeakReference(docShell);
+
+ // Disable plugins.
+ if (!mInteractive) {
+ rv = DisableJSAndPlugins(*docShell);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Tells embedder that startup is in progress.
+ mEditorStatus = eEditorCreationInProgress;
+
+ // Adds back web progress listener.
+ rv = PrepareForEditing(aWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setup the command controllers again.
+ rv = SetupEditorCommandController(
+ nsBaseCommandController::CreateEditingController, aWindow,
+ static_cast<nsIEditingSession*>(this), &mBaseCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupEditorCommandController(
+ nsBaseCommandController::CreateHTMLEditorDocStateController, aWindow,
+ static_cast<nsIEditingSession*>(this), &mDocStateControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mComposerCommandsUpdater) {
+ mComposerCommandsUpdater->Init(*aWindow);
+ }
+
+ // Get editor
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditorForWindow(aWindow);
+ if (NS_WARN_IF(!htmlEditor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mInteractive) {
+ // Disable animation of images in this document:
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return 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(
+ nsBaseCommandController::CreateHTMLEditorController, aWindow,
+ static_cast<nsIEditor*>(htmlEditor.get()), &mHTMLCommandControllerId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set context on all controllers to be the editor
+ rv = SetEditorOnControllers(*aWindow, htmlEditor);
+ 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;
+}
+
+HTMLEditor* nsIEditingSession::GetHTMLEditorForWindow(
+ mozIDOMWindowProxy* aWindow) {
+ if (NS_WARN_IF(!aWindow)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell =
+ nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ return nullptr;
+ }
+
+ return docShell->GetHTMLEditor();
+}
diff --git a/editor/composer/nsEditingSession.h b/editor/composer/nsEditingSession.h
new file mode 100644
index 0000000000..5dd9e4d262
--- /dev/null
+++ b/editor/composer/nsEditingSession.h
@@ -0,0 +1,170 @@
+/* -*- 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__
+
+#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 nsBaseCommandController;
+class nsIDOMWindow;
+class nsISupports;
+class nsITimer;
+class nsIChannel;
+class nsIControllers;
+class nsIDocShell;
+class nsIWebProgress;
+
+namespace mozilla {
+class ComposerCommandsUpdater;
+class HTMLEditor;
+} // namespace mozilla
+
+class nsEditingSession final : public nsIEditingSession,
+ public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ public:
+ nsEditingSession();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIWebProgressListener
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ // nsIEditingSession
+ NS_DECL_NSIEDITINGSESSION
+
+ /**
+ * Removes all the editor's controllers/listeners etc and makes the window
+ * uneditable.
+ */
+ nsresult DetachFromWindow(nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * Undos DetachFromWindow(), reattaches this editing session/editor
+ * to the window.
+ */
+ nsresult ReattachToWindow(nsPIDOMWindowOuter* aWindow);
+
+ protected:
+ virtual ~nsEditingSession();
+
+ typedef already_AddRefed<nsBaseCommandController> (*ControllerCreatorFn)();
+
+ nsresult SetupEditorCommandController(
+ ControllerCreatorFn aControllerCreatorFn, mozIDOMWindowProxy* aWindow,
+ nsISupports* aContext, uint32_t* aControllerId);
+
+ nsresult SetContextOnControllerById(nsIControllers* aControllers,
+ nsISupports* aContext, uint32_t aID);
+
+ /**
+ * Set the editor on the controller(s) for this window
+ */
+ nsresult SetEditorOnControllers(nsPIDOMWindowOuter& aWindow,
+ mozilla::HTMLEditor* aEditor);
+
+ /**
+ * Setup editor and related support objects
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult SetupEditorOnWindow(nsPIDOMWindowOuter& aWindow);
+
+ nsresult PrepareForEditing(nsPIDOMWindowOuter* aWindow);
+
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+ nsCOMPtr<nsITimer> mLoadBlankDocTimer;
+
+ // progress load stuff
+ nsresult StartDocumentLoad(nsIWebProgress* aWebProgress,
+ bool isToBeMadeEditable);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ 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,
+ mozilla::HTMLEditor* aHTMLEditor);
+
+ /**
+ * Disable scripts and plugins in aDocShell.
+ */
+ nsresult DisableJSAndPlugins(nsIDocShell& aDocShell);
+
+ /**
+ * Restore JS and plugins (enable/disable them) according to the state they
+ * were before the last call to disableJSAndPlugins.
+ */
+ nsresult RestoreJSAndPlugins(nsPIDOMWindowOuter* aWindow);
+
+ 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<mozilla::ComposerCommandsUpdater> mComposerCommandsUpdater;
+
+ // 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/nsIEditingSession.idl b/editor/composer/nsIEditingSession.idl
new file mode 100644
index 0000000000..a26b0add25
--- /dev/null
+++ b/editor/composer/nsIEditingSession.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+#include "domstubs.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIEditor;
+
+%{ C++
+class mozIDOMWindowProxy;
+namespace mozilla {
+class HTMLEditor;
+} // namespace mozilla
+%}
+
+[scriptable, builtinclass, 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
+ */
+ [can_run_script]
+ 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);
+
+ /**
+ * Destroy editor and related support objects
+ */
+ [noscript] void tearDownEditorOnWindow(in mozIDOMWindowProxy window);
+
+%{C++
+ /**
+ * This method is implemented with nsIDocShell::GetHTMLEditor(). I.e.,
+ * This method doesn't depend on nsEditingSession. Therefore, even if
+ * there were some implementation of nsIEditingSession interface, this
+ * would be safe to use.
+ */
+ mozilla::HTMLEditor* GetHTMLEditorForWindow(mozIDOMWindowProxy* aWindow);
+%}
+};
+
diff --git a/editor/composer/res/EditorOverride.css b/editor/composer/res/EditorOverride.css
new file mode 100644
index 0000000000..373ed869a1
--- /dev/null
+++ b/editor/composer/res/EditorOverride.css
@@ -0,0 +1,322 @@
+/* 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 {
+ color: -moz-hyperlinktext;
+}
+
+/* Allow double-clicks on these widgets to open properties dialogs
+ XXX except when the widget has disabled attribute */
+input, button, textarea {
+ 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"] {
+ 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 {
+ user-select: all !important;
+}
+
+::-moz-display-comboboxcontrol-frame {
+ user-select: text !important;
+}
+
+option {
+ user-select: text !important;
+}
+
+#mozToc.readonly {
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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 0000000000..06749a64fc
--- /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 0000000000..3ec50b82ee
--- /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 0000000000..29679f9812
--- /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 0000000000..8891be969c
--- /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 0000000000..1e205291e1
--- /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 0000000000..7b54537e40
--- /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 0000000000..d4a3ffe5e9
--- /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 0000000000..cc01da2c9e
--- /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 0000000000..a829351b64
--- /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 0000000000..3f1a39d981
--- /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 0000000000..34f1e0adec
--- /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 0000000000..e8f1d10b0c
--- /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 0000000000..1682170cb3
--- /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 0000000000..4dfbde4ce2
--- /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 0000000000..fd11bb52c3
--- /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 0000000000..d8071da0a9
--- /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 0000000000..4dfbde4ce2
--- /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 0000000000..fd11bb52c3
--- /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 0000000000..d8071da0a9
--- /dev/null
+++ b/editor/composer/res/table-remove-row.gif
Binary files differ
diff --git a/editor/composer/test/.eslintrc.js b/editor/composer/test/.eslintrc.js
new file mode 100644
index 0000000000..19d9df957a
--- /dev/null
+++ b/editor/composer/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/chrome-test", "plugin:mozilla/mochitest-test"],
+};
diff --git a/editor/composer/test/chrome.ini b/editor/composer/test/chrome.ini
new file mode 100644
index 0000000000..cd25f64656
--- /dev/null
+++ b/editor/composer/test/chrome.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+skip-if = os == 'android'
+
+[test_bug434998.xhtml]
+[test_bug1266815.html]
diff --git a/editor/composer/test/file_bug1453190.html b/editor/composer/test/file_bug1453190.html
new file mode 100644
index 0000000000..74b13bb0ec
--- /dev/null
+++ b/editor/composer/test/file_bug1453190.html
@@ -0,0 +1,12 @@
+<script>
+window.onload = function () {
+ document.createElement("frameset").setAttribute("onunload", "go()")
+}
+function go() {
+ let a = document.getElementById("a");
+ let b = document.getElementById("b");
+ a.appendChild(b);
+}
+</script>
+<div id="a">
+<li id="b" contenteditable="true">
diff --git a/editor/composer/test/mochitest.ini b/editor/composer/test/mochitest.ini
new file mode 100644
index 0000000000..4e221a0320
--- /dev/null
+++ b/editor/composer/test/mochitest.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ file_bug1453190.html
+
+[test_bug348497.html]
+[test_bug384147.html]
+[test_bug389350.html]
+[test_bug519928.html]
+[test_bug738440.html]
+[test_bug1453190.html]
+skip-if = os == "android"
diff --git a/editor/composer/test/test_bug1266815.html b/editor/composer/test/test_bug1266815.html
new file mode 100644
index 0000000000..d2a0ba9e26
--- /dev/null
+++ b/editor/composer/test/test_bug1266815.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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">
+// XXX(nika): Why are we using SpecialPowers here? If we're a chrome mochitest
+// can't we avoid using the SpecialPowers wrappers?
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+const Cu = SpecialPowers.Cu;
+
+const {ComponentUtils} = ChromeUtils.import("resource://gre/modules/ComponentUtils.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(aLauncher, aWindowContext, aReason) {
+ ok(true, "Whether showing Dialog");
+ resolve();
+ registrar.unregisterFactory(MOCK_HELPERAPP_DIALOG_CID,
+ mockHelperAppService);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIHelperAppLauncherDialog"]),
+ };
+
+ mockHelperAppService = ComponentUtils._getFactory(HelperAppLauncherDialog);
+ registrar.registerFactory(MOCK_HELPERAPP_DIALOG_CID, "",
+ HELPERAPP_DIALOG_CONTRACT_ID,
+ mockHelperAppService);
+});
+
+add_task(async 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);
+ });
+
+ await promise;
+
+ let iframe = document.getElementById("testframe");
+ let docShell = SpecialPowers.wrap(iframe.contentWindow).docShell;
+
+ ok(docShell.hasEditingSession, "Should have editing session");
+
+ document.getElementById("testframe").src =
+ "data:application/octet-stream,TESTCONTENT";
+
+ await helperAppDlgPromise;
+
+ ok(docShell.hasEditingSession, "Should have editing session");
+});
+</script>
+</body>
+</html>
diff --git a/editor/composer/test/test_bug1453190.html b/editor/composer/test/test_bug1453190.html
new file mode 100644
index 0000000000..cf6b0d888a
--- /dev/null
+++ b/editor/composer/test/test_bug1453190.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1453190
+-->
+<head>
+ <title>Test for Bug 1453190</title>
+ <script 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=1453190">Mozilla Bug 1453190</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+let testWindow = window.open("file_bug1453190.html");
+testWindow.addEventListener("load", () => {
+ SimpleTest.executeSoon(() => {
+ runTest(testWindow);
+ });
+}, {once: true});
+
+function runTest(win) {
+ ok(!win.closed, "test window is opened");
+ win.close();
+ ok(win.closed, "test window is closed");
+ SimpleTest.finish();
+}
+</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 0000000000..898fb49ae1
--- /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 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 0000000000..6feb8b4ad4
--- /dev/null
+++ b/editor/composer/test/test_bug384147.html
@@ -0,0 +1,203 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=384147
+-->
+<head>
+ <title>Test for Bug 384147</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script 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">
+
+/** 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 0000000000..9b9aecd1e7
--- /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 src="/tests/SimpleTest/SimpleTest.js"></script>
+<script 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.xhtml b/editor/composer/test/test_bug434998.xhtml
new file mode 100644
index 0000000000..db2261e3a5
--- /dev/null
+++ b/editor/composer/test/test_bug434998.xhtml
@@ -0,0 +1,106 @@
+<?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 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script 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="true"
+ 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(aEditor)
+ {
+ this.mEditor = aEditor;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener",
+ "nsISupportsWeakReference"]),
+
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Ci.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, Ci.nsIWebProgress.NOTIFY_ALL);
+ SimpleTest.finish();
+ }
+ }
+ },
+
+
+ onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress)
+ {
+ },
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ },
+
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ },
+
+ onSecurityChange(aWebProgress, aRequest, aState)
+ {
+ },
+
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent)
+ {
+ },
+
+ mEditor: null
+ };
+
+ var progress, progressListener;
+
+ function runTest() {
+ var newEditorElement = document.getElementById("editor");
+ newEditorElement.makeEditable("html", true);
+ var docShell = newEditorElement.docShell;
+ progress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
+ progressListener = new EditorContentListener(newEditorElement);
+ progress.addProgressListener(progressListener, Ci.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 0000000000..cb5d713613
--- /dev/null
+++ b/editor/composer/test/test_bug519928.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=519928
+-->
+<head>
+ <title>Test for Bug 519928</title>
+ <script 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).docShell.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() {
+ var msg = "The inner iframe should" + (allowed ? "" : " not") + " be able to run Javascript";
+ is(self_.ICanRunMyJS, allowed, msg);
+ callback();
+ }, {once: true});
+ // eslint-disable-next-line no-useless-concat
+ var iframeSrc = "<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>";
+ innerFrame.srcdoc = iframeSrc;
+}
+
+SimpleTest.waitForExplicitFinish();
+/* eslint-disable max-nested-callbacks */
+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();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+});
+/* eslint-enable max-nested-callbacks */
+
+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() {
+ var msg = "The inner iframe should not be able to run Javascript";
+ is(self_.ICanRunMyJS, false, msg);
+ SimpleTest.finish();
+ }, {once: true});
+ // eslint-disable-next-line no-useless-concat
+ var iframeSrc = "<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>";
+ innerFrame.srcdoc = iframeSrc;
+}
+
+</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 0000000000..a021906cfc
--- /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>