diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /editor/composer | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'editor/composer')
75 files changed, 8379 insertions, 0 deletions
diff --git a/editor/composer/crashtests/351236-1.html b/editor/composer/crashtests/351236-1.html new file mode 100644 index 000000000..22133a215 --- /dev/null +++ b/editor/composer/crashtests/351236-1.html @@ -0,0 +1,37 @@ +<html><head> +<title>Testcase bug 351236 - Crash [@ nsGetInterface::operator()] with designMode iframes, removing styles, removing iframes, reloading, etc</title> +<script> +function designmodes(i){ +try { +window.frames[0].document.designMode='on'; +window.frames[0].focus(); +window.frames[0].getSelection().collapse(window.frames[0].document.body.childNodes[0],window.frames[0].document.body.childNodes[0].length-2) +window.frames[0].document.execCommand('inserthtml', false, 'tesxt '); +} catch(e) {} + +setTimeout(designmodes,50); +} + +function removestyles(){ +document.getElementsByTagName('iframe')[0].removeAttribute('style'); +document.getElementsByTagName('q')[0].removeAttribute('style'); +} + +function doe() { +setTimeout(designmodes,200); +setTimeout(removestyles,500); +setTimeout(function() {document.removeChild(document.documentElement);}, 1000); +setTimeout(function() {window.location.reload();}, 1500); +} +window.onload=doe; +</script> + +</head> +<body> +This page should not crash Mozilla within 2 seconds<br> +<q style="display: table-row;"> +<iframe style="display: table-row;"></iframe> +<iframe></iframe> +</q> +</body> +</html>
\ No newline at end of file diff --git a/editor/composer/crashtests/407062-1.html b/editor/composer/crashtests/407062-1.html new file mode 100644 index 000000000..81083c235 --- /dev/null +++ b/editor/composer/crashtests/407062-1.html @@ -0,0 +1,20 @@ +<html contentEditable="true"> +<head> + +<script type="text/javascript"> + +function boom() +{ + var r = document.documentElement; + while(r.firstChild) + r.removeChild(r.firstChild); + + document.execCommand("contentReadOnly", false, ""); + document.documentElement.focus(); +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/editor/composer/crashtests/419563-1.xhtml b/editor/composer/crashtests/419563-1.xhtml new file mode 100644 index 000000000..417530c13 --- /dev/null +++ b/editor/composer/crashtests/419563-1.xhtml @@ -0,0 +1,20 @@ +<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait"> +<head> +<script type="text/javascript"> + +function boom() +{ + document.documentElement.appendChild(document.body); + document.getElementById("s").contentEditable = "true"; + document.getElementById("v").focus(); + document.body.focus(); + document.execCommand("delete", false, null); + document.documentElement.removeAttribute("class"); +} + +</script> +</head> + +<span id="s">thesewords arenot realwords</span><body contenteditable="true" onload="setTimeout(boom, 0);"><span contenteditable="false"><div id="v" contenteditable="true"></div>Five</span></body> + +</html> diff --git a/editor/composer/crashtests/428844-1-inner.xhtml b/editor/composer/crashtests/428844-1-inner.xhtml new file mode 100644 index 000000000..1cc72d085 --- /dev/null +++ b/editor/composer/crashtests/428844-1-inner.xhtml @@ -0,0 +1,4 @@ +<?xml-stylesheet type="text/xsl" href="#a"?> +<html xmlns="http://www.w3.org/1999/xhtml" onload="dump('Inner onload\n'); window.location.reload()" contenteditable="true"> +<xsl:stylesheet version="1.0" id="a" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/> +</html> diff --git a/editor/composer/crashtests/428844-1.html b/editor/composer/crashtests/428844-1.html new file mode 100644 index 000000000..8bebdbf4b --- /dev/null +++ b/editor/composer/crashtests/428844-1.html @@ -0,0 +1,17 @@ +<html class="reftest-wait"> +<head> +<script> +function boom() { + var iframe = document.getElementById('inner'); + iframe.addEventListener("load", function() { + document.documentElement.removeAttribute("class"); + }, false); + iframe.src = "data:text/html,"; + dump("Outer onload\n"); +} +</script> +</head> +<body onload="boom()"> +<iframe src="428844-1-inner.xhtml" id="inner"></iframe> +</body> +</html> diff --git a/editor/composer/crashtests/461049-1.html b/editor/composer/crashtests/461049-1.html new file mode 100644 index 000000000..c25ed991f --- /dev/null +++ b/editor/composer/crashtests/461049-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> + +function uu() +{ + document.removeEventListener("DOMSubtreeModified", uu, false); + document.execCommand("undo", false, null); +} + +function boom() +{ + document.execCommand("selectAll", false, null); + document.execCommand("strikethrough", false, null); + document.addEventListener("DOMSubtreeModified", uu, false); + document.execCommand("undo", false, null); +} + +</script> +</head> + +<body contenteditable="true" onload="boom();"><div></div></body> + +</html> diff --git a/editor/composer/crashtests/crashtests.list b/editor/composer/crashtests/crashtests.list new file mode 100644 index 000000000..db84e0e5b --- /dev/null +++ b/editor/composer/crashtests/crashtests.list @@ -0,0 +1,6 @@ +load 351236-1.html +load 407062-1.html +load 419563-1.xhtml +load 428844-1.html +load 461049-1.html +load removing-editable-xslt.html diff --git a/editor/composer/crashtests/removing-editable-xslt-inner.xhtml b/editor/composer/crashtests/removing-editable-xslt-inner.xhtml new file mode 100644 index 000000000..cbf206d7e --- /dev/null +++ b/editor/composer/crashtests/removing-editable-xslt-inner.xhtml @@ -0,0 +1,4 @@ +<?xml-stylesheet type="text/xsl" href="#a"?> +<html xmlns="http://www.w3.org/1999/xhtml" contenteditable="true"> +<xsl:stylesheet version="1.0" id="a" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/> +</html> diff --git a/editor/composer/crashtests/removing-editable-xslt.html b/editor/composer/crashtests/removing-editable-xslt.html new file mode 100644 index 000000000..cbf104ac9 --- /dev/null +++ b/editor/composer/crashtests/removing-editable-xslt.html @@ -0,0 +1,17 @@ +<html class="reftest-wait"> +<head> +<script> +function boom() +{ + document.getElementById("i").src = "removing-editable-xslt-inner.xhtml"; + setTimeout(function() { + document.body.removeChild(document.getElementById("i")); + document.documentElement.removeAttribute("class"); + }, 0); +} +</script> +</head> +<body onload="boom();"> +<iframe id="i"></iframe> +</body> +</html> diff --git a/editor/composer/moz.build b/editor/composer/moz.build new file mode 100644 index 000000000..4db8c9130 --- /dev/null +++ b/editor/composer/moz.build @@ -0,0 +1,53 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += ['test/mochitest.ini'] + +MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] + +XPIDL_SOURCES += [ + 'nsIEditingSession.idl', +] + +XPIDL_MODULE = 'composer' + +UNIFIED_SOURCES += [ + 'nsComposerCommands.cpp', + 'nsComposerCommandsUpdater.cpp', + 'nsComposerController.cpp', + 'nsComposerDocumentCommands.cpp', + 'nsComposerRegistration.cpp', + 'nsComposeTxtSrvFilter.cpp', + 'nsEditingSession.cpp', + 'nsEditorSpellCheck.cpp', +] + +FINAL_LIBRARY = 'xul' +RESOURCE_FILES += [ + 'res/EditorOverride.css', + 'res/grabber.gif', + 'res/table-add-column-after-active.gif', + 'res/table-add-column-after-hover.gif', + 'res/table-add-column-after.gif', + 'res/table-add-column-before-active.gif', + 'res/table-add-column-before-hover.gif', + 'res/table-add-column-before.gif', + 'res/table-add-row-after-active.gif', + 'res/table-add-row-after-hover.gif', + 'res/table-add-row-after.gif', + 'res/table-add-row-before-active.gif', + 'res/table-add-row-before-hover.gif', + 'res/table-add-row-before.gif', + 'res/table-remove-column-active.gif', + 'res/table-remove-column-hover.gif', + 'res/table-remove-column.gif', + 'res/table-remove-row-active.gif', + 'res/table-remove-row-hover.gif', + 'res/table-remove-row.gif', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/editor/composer/nsComposeTxtSrvFilter.cpp b/editor/composer/nsComposeTxtSrvFilter.cpp new file mode 100644 index 000000000..ba66bca95 --- /dev/null +++ b/editor/composer/nsComposeTxtSrvFilter.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsComposeTxtSrvFilter.h" +#include "nsError.h" // for NS_OK +#include "nsIContent.h" // for nsIContent +#include "nsIDOMNode.h" // for nsIDOMNode +#include "nsNameSpaceManager.h" // for kNameSpaceID_None +#include "nsLiteralString.h" // for NS_LITERAL_STRING +#include "nscore.h" // for NS_IMETHODIMP + +nsComposeTxtSrvFilter::nsComposeTxtSrvFilter() : + mIsForMail(false) +{ +} + +NS_IMPL_ISUPPORTS(nsComposeTxtSrvFilter, nsITextServicesFilter) + +NS_IMETHODIMP +nsComposeTxtSrvFilter::Skip(nsIDOMNode* aNode, bool *_retval) +{ + *_retval = false; + + // Check to see if we can skip this node + // For nodes that are blockquotes, we must make sure + // their type is "cite" + nsCOMPtr<nsIContent> content(do_QueryInterface(aNode)); + if (content) { + if (content->IsHTMLElement(nsGkAtoms::blockquote)) { + if (mIsForMail) { + *_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::cite, eIgnoreCase); + } + } else if (content->IsHTMLElement(nsGkAtoms::span)) { + if (mIsForMail) { + *_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozquote, + nsGkAtoms::_true, eIgnoreCase); + if (!*_retval) { + *_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_class, + nsGkAtoms::mozsignature, eCaseMatters); + } + } + } else if (content->IsAnyOfHTMLElements(nsGkAtoms::script, + nsGkAtoms::textarea, + nsGkAtoms::select, + nsGkAtoms::style, + nsGkAtoms::map)) { + *_retval = true; + } else if (content->IsHTMLElement(nsGkAtoms::table)) { + if (mIsForMail) { + *_retval = + content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_class, + NS_LITERAL_STRING("moz-email-headers-table"), + eCaseMatters); + } + } + } + + return NS_OK; +} diff --git a/editor/composer/nsComposeTxtSrvFilter.h b/editor/composer/nsComposeTxtSrvFilter.h new file mode 100644 index 000000000..0e5bba433 --- /dev/null +++ b/editor/composer/nsComposeTxtSrvFilter.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsComposeTxtSrvFilter_h__ +#define nsComposeTxtSrvFilter_h__ + +#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS +#include "nsITextServicesFilter.h" + +/** + * This class implements a filter interface, that enables + * those using it to skip over certain nodes when traversing content + * + * This filter is used to skip over various form control nodes and + * mail's cite nodes + */ +class nsComposeTxtSrvFilter final : public nsITextServicesFilter +{ +public: + nsComposeTxtSrvFilter(); + + // nsISupports interface... + NS_DECL_ISUPPORTS + + // nsITextServicesFilter + NS_DECL_NSITEXTSERVICESFILTER + + // Helper - Intializer + void Init(bool aIsForMail) { mIsForMail = aIsForMail; } + +private: + ~nsComposeTxtSrvFilter() {} + + bool mIsForMail; +}; + +#define NS_COMPOSERTXTSRVFILTER_CID \ +{/* {171E72DB-0F8A-412a-8461-E4C927A3A2AC}*/ \ +0x171e72db, 0xf8a, 0x412a, \ +{ 0x84, 0x61, 0xe4, 0xc9, 0x27, 0xa3, 0xa2, 0xac} } + +#define NS_COMPOSERTXTSRVFILTERMAIL_CID \ +{/* {7FBD2146-5FF4-4674-B069-A7BBCE66E773}*/ \ +0x7fbd2146, 0x5ff4, 0x4674, \ +{ 0xb0, 0x69, 0xa7, 0xbb, 0xce, 0x66, 0xe7, 0x73} } + +// Generic for the editor +#define COMPOSER_TXTSRVFILTER_CONTRACTID "@mozilla.org/editor/txtsrvfilter;1" + +// This is the same but includes "cite" typed blocked quotes +#define COMPOSER_TXTSRVFILTERMAIL_CONTRACTID "@mozilla.org/editor/txtsrvfiltermail;1" + +#endif diff --git a/editor/composer/nsComposerCommands.cpp b/editor/composer/nsComposerCommands.cpp new file mode 100644 index 000000000..3853604e4 --- /dev/null +++ b/editor/composer/nsComposerCommands.cpp @@ -0,0 +1,1525 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include <stdio.h> // for printf + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "nsAString.h" +#include "nsCOMPtr.h" // for nsCOMPtr, do_QueryInterface, etc +#include "nsComponentManagerUtils.h" // for do_CreateInstance +#include "nsComposerCommands.h" +#include "nsDebug.h" // for NS_ENSURE_TRUE, etc +#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc +#include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::font, etc +#include "nsIAtom.h" // for nsIAtom, etc +#include "nsIClipboard.h" // for nsIClipboard, etc +#include "nsICommandParams.h" // for nsICommandParams, etc +#include "nsID.h" +#include "nsIDOMElement.h" // for nsIDOMElement +#include "nsIEditor.h" // for nsIEditor +#include "nsIHTMLAbsPosEditor.h" // for nsIHTMLAbsPosEditor +#include "nsIHTMLEditor.h" // for nsIHTMLEditor, etc +#include "nsLiteralString.h" // for NS_LITERAL_STRING +#include "nsReadableUtils.h" // for EmptyString +#include "nsString.h" // for nsAutoString, nsString, etc +#include "nsStringFwd.h" // for nsAFlatString + +class nsISupports; + +//prototype +nsresult GetListState(nsIHTMLEditor* aEditor, bool* aMixed, + nsAString& aLocalName); +nsresult RemoveOneProperty(nsIHTMLEditor* aEditor, const nsAString& aProp); +nsresult RemoveTextProperty(nsIHTMLEditor* aEditor, const nsAString& aProp); +nsresult SetTextProperty(nsIHTMLEditor *aEditor, const nsAString& aProp); + + +//defines +#define STATE_ENABLED "state_enabled" +#define STATE_ALL "state_all" +#define STATE_ANY "state_any" +#define STATE_MIXED "state_mixed" +#define STATE_BEGIN "state_begin" +#define STATE_END "state_end" +#define STATE_ATTRIBUTE "state_attribute" +#define STATE_DATA "state_data" + + +nsBaseComposerCommand::nsBaseComposerCommand() +{ +} + +NS_IMPL_ISUPPORTS(nsBaseComposerCommand, nsIControllerCommand) + + +nsBaseStateUpdatingCommand::nsBaseStateUpdatingCommand(nsIAtom* aTagName) +: nsBaseComposerCommand() +, mTagName(aTagName) +{ + MOZ_ASSERT(mTagName); +} + +nsBaseStateUpdatingCommand::~nsBaseStateUpdatingCommand() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsBaseStateUpdatingCommand, nsBaseComposerCommand) + +NS_IMETHODIMP +nsBaseStateUpdatingCommand::IsCommandEnabled(const char *aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsBaseStateUpdatingCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_NOT_INITIALIZED); + + return ToggleState(editor); +} + +NS_IMETHODIMP +nsBaseStateUpdatingCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsBaseStateUpdatingCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) + return GetCurrentState(editor, aParams); + + return NS_OK; +} + +NS_IMETHODIMP +nsPasteNoFormattingCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + NS_ENSURE_ARG_POINTER(outCmdEnabled); + *outCmdEnabled = false; + + // This command is only implemented by nsIHTMLEditor, since + // pasting in a plaintext editor automatically only supplies + // "unformatted" text + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIEditor> editor = do_QueryInterface(htmlEditor); + NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG); + + return editor->CanPaste(nsIClipboard::kGlobalClipboard, outCmdEnabled); +} + + +NS_IMETHODIMP +nsPasteNoFormattingCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED); + + return htmlEditor->PasteNoFormatting(nsIClipboard::kGlobalClipboard); +} + +NS_IMETHODIMP +nsPasteNoFormattingCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsPasteNoFormattingCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + + bool enabled = false; + nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled); + NS_ENSURE_SUCCESS(rv, rv); + + return aParams->SetBooleanValue(STATE_ENABLED, enabled); +} + +nsStyleUpdatingCommand::nsStyleUpdatingCommand(nsIAtom* aTagName) +: nsBaseStateUpdatingCommand(aTagName) +{ +} + +nsresult +nsStyleUpdatingCommand::GetCurrentState(nsIEditor *aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_INITIALIZED); + + bool firstOfSelectionHasProp = false; + bool anyOfSelectionHasProp = false; + bool allOfSelectionHasProp = false; + + nsresult rv = htmlEditor->GetInlineProperty(mTagName, EmptyString(), + EmptyString(), + &firstOfSelectionHasProp, + &anyOfSelectionHasProp, + &allOfSelectionHasProp); + + aParams->SetBooleanValue(STATE_ENABLED, NS_SUCCEEDED(rv)); + aParams->SetBooleanValue(STATE_ALL, allOfSelectionHasProp); + aParams->SetBooleanValue(STATE_ANY, anyOfSelectionHasProp); + aParams->SetBooleanValue(STATE_MIXED, anyOfSelectionHasProp + && !allOfSelectionHasProp); + aParams->SetBooleanValue(STATE_BEGIN, firstOfSelectionHasProp); + aParams->SetBooleanValue(STATE_END, allOfSelectionHasProp);//not completely accurate + return NS_OK; +} + +nsresult +nsStyleUpdatingCommand::ToggleState(nsIEditor *aEditor) +{ + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NO_INTERFACE); + + //create some params now... + nsresult rv; + nsCOMPtr<nsICommandParams> params = + do_CreateInstance(NS_COMMAND_PARAMS_CONTRACTID,&rv); + if (NS_FAILED(rv) || !params) + return rv; + + // tags "href" and "name" are special cases in the core editor + // they are used to remove named anchor/link and shouldn't be used for insertion + bool doTagRemoval; + if (mTagName == nsGkAtoms::href || mTagName == nsGkAtoms::name) { + doTagRemoval = true; + } else { + // check current selection; set doTagRemoval if formatting should be removed + rv = GetCurrentState(aEditor, params); + NS_ENSURE_SUCCESS(rv, rv); + rv = params->GetBooleanValue(STATE_ALL, &doTagRemoval); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (doTagRemoval) { + // Also remove equivalent properties (bug 317093) + if (mTagName == nsGkAtoms::b) { + rv = RemoveTextProperty(htmlEditor, NS_LITERAL_STRING("strong")); + NS_ENSURE_SUCCESS(rv, rv); + } else if (mTagName == nsGkAtoms::i) { + rv = RemoveTextProperty(htmlEditor, NS_LITERAL_STRING("em")); + NS_ENSURE_SUCCESS(rv, rv); + } else if (mTagName == nsGkAtoms::strike) { + rv = RemoveTextProperty(htmlEditor, NS_LITERAL_STRING("s")); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = RemoveTextProperty(htmlEditor, nsDependentAtomString(mTagName)); + } else { + // Superscript and Subscript styles are mutually exclusive + aEditor->BeginTransaction(); + + nsDependentAtomString tagName(mTagName); + if (mTagName == nsGkAtoms::sub || mTagName == nsGkAtoms::sup) { + rv = RemoveTextProperty(htmlEditor, tagName); + } + if (NS_SUCCEEDED(rv)) + rv = SetTextProperty(htmlEditor, tagName); + + aEditor->EndTransaction(); + } + + return rv; +} + +nsListCommand::nsListCommand(nsIAtom* aTagName) +: nsBaseStateUpdatingCommand(aTagName) +{ +} + +nsresult +nsListCommand::GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams) +{ + NS_ASSERTION(aEditor, "Need editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NO_INTERFACE); + + bool bMixed; + nsAutoString localName; + nsresult rv = GetListState(htmlEditor, &bMixed, localName); + NS_ENSURE_SUCCESS(rv, rv); + + bool inList = mTagName->Equals(localName); + aParams->SetBooleanValue(STATE_ALL, !bMixed && inList); + aParams->SetBooleanValue(STATE_MIXED, bMixed); + aParams->SetBooleanValue(STATE_ENABLED, true); + return NS_OK; +} + +nsresult +nsListCommand::ToggleState(nsIEditor *aEditor) +{ + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(editor, NS_NOINTERFACE); + + nsresult rv; + nsCOMPtr<nsICommandParams> params = + do_CreateInstance(NS_COMMAND_PARAMS_CONTRACTID,&rv); + if (NS_FAILED(rv) || !params) + return rv; + + rv = GetCurrentState(aEditor, params); + NS_ENSURE_SUCCESS(rv, rv); + + bool inList; + rv = params->GetBooleanValue(STATE_ALL,&inList); + NS_ENSURE_SUCCESS(rv, rv); + + nsDependentAtomString listType(mTagName); + if (inList) { + rv = editor->RemoveList(listType); + } else { + rv = editor->MakeOrChangeList(listType, false, EmptyString()); + } + + return rv; +} + +nsListItemCommand::nsListItemCommand(nsIAtom* aTagName) +: nsBaseStateUpdatingCommand(aTagName) +{ +} + +nsresult +nsListItemCommand::GetCurrentState(nsIEditor* aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need editor here"); + // 39584 + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_NOINTERFACE); + + bool bMixed, bLI, bDT, bDD; + nsresult rv = htmlEditor->GetListItemState(&bMixed, &bLI, &bDT, &bDD); + NS_ENSURE_SUCCESS(rv, rv); + + bool inList = false; + if (!bMixed) { + if (bLI) { + inList = mTagName == nsGkAtoms::li; + } else if (bDT) { + inList = mTagName == nsGkAtoms::dt; + } else if (bDD) { + inList = mTagName == nsGkAtoms::dd; + } + } + + aParams->SetBooleanValue(STATE_ALL, !bMixed && inList); + aParams->SetBooleanValue(STATE_MIXED, bMixed); + + return NS_OK; +} + +nsresult +nsListItemCommand::ToggleState(nsIEditor *aEditor) +{ + NS_ASSERTION(aEditor, "Need editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_INITIALIZED); + + bool inList; + // Need to use mTagName???? + nsresult rv; + nsCOMPtr<nsICommandParams> params = + do_CreateInstance(NS_COMMAND_PARAMS_CONTRACTID,&rv); + if (NS_FAILED(rv) || !params) + return rv; + rv = GetCurrentState(aEditor, params); + rv = params->GetBooleanValue(STATE_ALL,&inList); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (inList) { + // To remove a list, first get what kind of list we're in + bool bMixed; + nsAutoString localName; + rv = GetListState(htmlEditor, &bMixed, localName); + NS_ENSURE_SUCCESS(rv, rv); + if (localName.IsEmpty() || bMixed) { + return rv; + } + return htmlEditor->RemoveList(localName); + } + + // Set to the requested paragraph type + //XXX Note: This actually doesn't work for "LI", + // but we currently don't use this for non DL lists anyway. + // Problem: won't this replace any current block paragraph style? + return htmlEditor->SetParagraphFormat(nsDependentAtomString(mTagName)); +} + +NS_IMETHODIMP +nsRemoveListCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + *outCmdEnabled = false; + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_OK); + + bool isEditable = false; + nsresult rv = editor->GetIsSelectionEditable(&isEditable); + NS_ENSURE_SUCCESS(rv, rv); + if (!isEditable) { + return NS_OK; + } + + // It is enabled if we are in any list type + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NO_INTERFACE); + + bool bMixed; + nsAutoString localName; + rv = GetListState(htmlEditor, &bMixed, localName); + NS_ENSURE_SUCCESS(rv, rv); + + *outCmdEnabled = bMixed || !localName.IsEmpty(); + return NS_OK; +} + + +NS_IMETHODIMP +nsRemoveListCommand::DoCommand(const char *aCommandName, nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + + nsresult rv = NS_OK; + if (editor) { + // This removes any list type + rv = editor->RemoveList(EmptyString()); + } + + return rv; +} + +NS_IMETHODIMP +nsRemoveListCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsRemoveListCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled); +} + +NS_IMETHODIMP +nsIndentCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsIndentCommand::DoCommand(const char *aCommandName, nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + + nsresult rv = NS_OK; + if (editor) { + rv = editor->Indent(NS_LITERAL_STRING("indent")); + } + + return rv; +} + +NS_IMETHODIMP +nsIndentCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsIndentCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled); +} + + +//OUTDENT + +NS_IMETHODIMP +nsOutdentCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + *outCmdEnabled = false; + + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) { + nsresult rv = editor->GetIsSelectionEditable(outCmdEnabled); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsOutdentCommand::DoCommand(const char *aCommandName, nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(refCon); + if (htmlEditor) + return htmlEditor->Indent(NS_LITERAL_STRING("outdent")); + + return NS_OK; +} + +NS_IMETHODIMP +nsOutdentCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsOutdentCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled); +} + +nsMultiStateCommand::nsMultiStateCommand() +: nsBaseComposerCommand() +{ +} + +nsMultiStateCommand::~nsMultiStateCommand() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsMultiStateCommand, nsBaseComposerCommand) + +NS_IMETHODIMP +nsMultiStateCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + // should be disabled sometimes, like if the current selection is an image + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsMultiStateCommand::DoCommand(const char *aCommandName, nsISupports *refCon) +{ +#ifdef DEBUG + printf("who is calling nsMultiStateCommand::DoCommand \ + (no implementation)? %s\n", aCommandName); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiStateCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + + nsresult rv = NS_OK; + if (editor) { + nsAutoString tString; + + if (aParams) { + nsXPIDLCString s; + rv = aParams->GetCStringValue(STATE_ATTRIBUTE, getter_Copies(s)); + if (NS_SUCCEEDED(rv)) + tString.AssignWithConversion(s); + else + rv = aParams->GetStringValue(STATE_ATTRIBUTE, tString); + } + + rv = SetState(editor, tString); + } + + return rv; +} + +NS_IMETHODIMP +nsMultiStateCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + nsresult rv = NS_OK; + if (editor) { + rv = GetCurrentState(editor, aParams); + } + return rv; +} + +nsParagraphStateCommand::nsParagraphStateCommand() +: nsMultiStateCommand() +{ +} + +nsresult +nsParagraphStateCommand::GetCurrentState(nsIEditor *aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + bool outMixed; + nsAutoString outStateString; + nsresult rv = htmlEditor->GetParagraphState(&outMixed, outStateString); + if (NS_SUCCEEDED(rv)) { + nsAutoCString tOutStateString; + tOutStateString.AssignWithConversion(outStateString); + aParams->SetBooleanValue(STATE_MIXED,outMixed); + aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get()); + } + return rv; +} + + +nsresult +nsParagraphStateCommand::SetState(nsIEditor *aEditor, nsString& newState) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + return htmlEditor->SetParagraphFormat(newState); +} + +nsFontFaceStateCommand::nsFontFaceStateCommand() +: nsMultiStateCommand() +{ +} + +nsresult +nsFontFaceStateCommand::GetCurrentState(nsIEditor *aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + nsAutoString outStateString; + bool outMixed; + nsresult rv = htmlEditor->GetFontFaceState(&outMixed, outStateString); + if (NS_SUCCEEDED(rv)) { + aParams->SetBooleanValue(STATE_MIXED,outMixed); + aParams->SetCStringValue(STATE_ATTRIBUTE, NS_ConvertUTF16toUTF8(outStateString).get()); + } + return rv; +} + + +nsresult +nsFontFaceStateCommand::SetState(nsIEditor *aEditor, nsString& newState) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + if (newState.EqualsLiteral("tt")) { + // The old "teletype" attribute + nsresult rv = htmlEditor->SetInlineProperty(nsGkAtoms::tt, EmptyString(), + EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + // Clear existing font face + return htmlEditor->RemoveInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("face")); + } + + // Remove any existing TT nodes + nsresult rv = htmlEditor->RemoveInlineProperty(nsGkAtoms::tt, EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + + if (newState.IsEmpty() || newState.EqualsLiteral("normal")) { + return htmlEditor->RemoveInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("face")); + } + + return htmlEditor->SetInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("face"), newState); +} + +nsFontSizeStateCommand::nsFontSizeStateCommand() + : nsMultiStateCommand() +{ +} + +// nsAutoCString tOutStateString; +// tOutStateString.AssignWithConversion(outStateString); +nsresult +nsFontSizeStateCommand::GetCurrentState(nsIEditor *aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_INVALID_ARG); + + nsAutoString outStateString; + nsCOMPtr<nsIAtom> fontAtom = NS_Atomize("font"); + bool firstHas, anyHas, allHas; + nsresult rv = htmlEditor->GetInlinePropertyWithAttrValue(fontAtom, + NS_LITERAL_STRING("size"), + EmptyString(), + &firstHas, &anyHas, &allHas, + outStateString); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString tOutStateString; + tOutStateString.AssignWithConversion(outStateString); + aParams->SetBooleanValue(STATE_MIXED, anyHas && !allHas); + aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get()); + aParams->SetBooleanValue(STATE_ENABLED, true); + + return rv; +} + + +// acceptable values for "newState" are: +// -2 +// -1 +// 0 +// +1 +// +2 +// +3 +// medium +// normal +nsresult +nsFontSizeStateCommand::SetState(nsIEditor *aEditor, nsString& newState) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_INVALID_ARG); + + if (!newState.IsEmpty() && + !newState.EqualsLiteral("normal") && + !newState.EqualsLiteral("medium")) { + return htmlEditor->SetInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("size"), newState); + } + + // remove any existing font size, big or small + nsresult rv = htmlEditor->RemoveInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("size")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = htmlEditor->RemoveInlineProperty(nsGkAtoms::big, EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + + return htmlEditor->RemoveInlineProperty(nsGkAtoms::small, EmptyString()); +} + +nsFontColorStateCommand::nsFontColorStateCommand() +: nsMultiStateCommand() +{ +} + +nsresult +nsFontColorStateCommand::GetCurrentState(nsIEditor *aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + bool outMixed; + nsAutoString outStateString; + nsresult rv = htmlEditor->GetFontColorState(&outMixed, outStateString); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString tOutStateString; + tOutStateString.AssignWithConversion(outStateString); + aParams->SetBooleanValue(STATE_MIXED, outMixed); + aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get()); + return NS_OK; +} + +nsresult +nsFontColorStateCommand::SetState(nsIEditor *aEditor, nsString& newState) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + if (newState.IsEmpty() || newState.EqualsLiteral("normal")) { + return htmlEditor->RemoveInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("color")); + } + + return htmlEditor->SetInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("color"), newState); +} + +nsHighlightColorStateCommand::nsHighlightColorStateCommand() +: nsMultiStateCommand() +{ +} + +nsresult +nsHighlightColorStateCommand::GetCurrentState(nsIEditor *aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + bool outMixed; + nsAutoString outStateString; + nsresult rv = htmlEditor->GetHighlightColorState(&outMixed, outStateString); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString tOutStateString; + tOutStateString.AssignWithConversion(outStateString); + aParams->SetBooleanValue(STATE_MIXED, outMixed); + aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get()); + return NS_OK; +} + +nsresult +nsHighlightColorStateCommand::SetState(nsIEditor *aEditor, nsString& newState) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + if (newState.IsEmpty() || newState.EqualsLiteral("normal")) { + return htmlEditor->RemoveInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("bgcolor")); + } + + return htmlEditor->SetInlineProperty(nsGkAtoms::font, + NS_LITERAL_STRING("bgcolor"), + newState); +} + +NS_IMETHODIMP +nsHighlightColorStateCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +nsBackgroundColorStateCommand::nsBackgroundColorStateCommand() +: nsMultiStateCommand() +{ +} + +nsresult +nsBackgroundColorStateCommand::GetCurrentState(nsIEditor *aEditor, + nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + bool outMixed; + nsAutoString outStateString; + nsresult rv = htmlEditor->GetBackgroundColorState(&outMixed, outStateString); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString tOutStateString; + tOutStateString.AssignWithConversion(outStateString); + aParams->SetBooleanValue(STATE_MIXED, outMixed); + aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get()); + return NS_OK; +} + +nsresult +nsBackgroundColorStateCommand::SetState(nsIEditor *aEditor, nsString& newState) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + return htmlEditor->SetBackgroundColor(newState); +} + +nsAlignCommand::nsAlignCommand() +: nsMultiStateCommand() +{ +} + +nsresult +nsAlignCommand::GetCurrentState(nsIEditor *aEditor, nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + nsIHTMLEditor::EAlignment firstAlign; + bool outMixed; + nsresult rv = htmlEditor->GetAlignment(&outMixed, &firstAlign); + + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString outStateString; + switch (firstAlign) { + default: + case nsIHTMLEditor::eLeft: + outStateString.AssignLiteral("left"); + break; + + case nsIHTMLEditor::eCenter: + outStateString.AssignLiteral("center"); + break; + + case nsIHTMLEditor::eRight: + outStateString.AssignLiteral("right"); + break; + + case nsIHTMLEditor::eJustify: + outStateString.AssignLiteral("justify"); + break; + } + nsAutoCString tOutStateString; + tOutStateString.AssignWithConversion(outStateString); + aParams->SetBooleanValue(STATE_MIXED,outMixed); + aParams->SetCStringValue(STATE_ATTRIBUTE, tOutStateString.get()); + return NS_OK; +} + +nsresult +nsAlignCommand::SetState(nsIEditor *aEditor, nsString& newState) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + return htmlEditor->Align(newState); +} + +nsAbsolutePositioningCommand::nsAbsolutePositioningCommand() +: nsBaseStateUpdatingCommand(nsGkAtoms::_empty) +{ +} + +NS_IMETHODIMP +nsAbsolutePositioningCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *aCommandRefCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon); + nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(aCommandRefCon); + if (htmlEditor) { + bool isEditable = false; + nsresult rv = editor->GetIsSelectionEditable(&isEditable); + NS_ENSURE_SUCCESS(rv, rv); + if (isEditable) + return htmlEditor->GetAbsolutePositioningEnabled(outCmdEnabled); + } + + *outCmdEnabled = false; + return NS_OK; +} + +nsresult +nsAbsolutePositioningCommand::GetCurrentState(nsIEditor *aEditor, nsICommandParams *aParams) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + bool isEnabled; + htmlEditor->GetAbsolutePositioningEnabled(&isEnabled); + if (!isEnabled) { + aParams->SetBooleanValue(STATE_MIXED,false); + aParams->SetCStringValue(STATE_ATTRIBUTE, ""); + return NS_OK; + } + + nsCOMPtr<nsIDOMElement> elt; + nsresult rv = htmlEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString outStateString; + if (elt) + outStateString.AssignLiteral("absolute"); + + aParams->SetBooleanValue(STATE_MIXED,false); + aParams->SetCStringValue(STATE_ATTRIBUTE, NS_ConvertUTF16toUTF8(outStateString).get()); + return NS_OK; +} + +nsresult +nsAbsolutePositioningCommand::ToggleState(nsIEditor *aEditor) +{ + NS_ASSERTION(aEditor, "Need an editor here"); + + nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(aEditor); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMElement> elt; + nsresult rv = htmlEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); + NS_ENSURE_SUCCESS(rv, rv); + + return htmlEditor->AbsolutePositionSelection(!elt); +} + + +NS_IMETHODIMP +nsDecreaseZIndexCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + htmlEditor->GetAbsolutePositioningEnabled(outCmdEnabled); + if (!(*outCmdEnabled)) + return NS_OK; + + nsCOMPtr<nsIDOMElement> positionedElement; + htmlEditor->GetPositionedElement(getter_AddRefs(positionedElement)); + *outCmdEnabled = false; + if (positionedElement) { + int32_t z; + nsresult rv = htmlEditor->GetElementZIndex(positionedElement, &z); + NS_ENSURE_SUCCESS(rv, rv); + *outCmdEnabled = (z > 0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDecreaseZIndexCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED); + + return htmlEditor->RelativeChangeZIndex(-1); +} + +NS_IMETHODIMP +nsDecreaseZIndexCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsDecreaseZIndexCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + + bool enabled = false; + nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled); + NS_ENSURE_SUCCESS(rv, rv); + + return aParams->SetBooleanValue(STATE_ENABLED, enabled); +} + +NS_IMETHODIMP +nsIncreaseZIndexCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE); + + htmlEditor->GetAbsolutePositioningEnabled(outCmdEnabled); + if (!(*outCmdEnabled)) + return NS_OK; + + nsCOMPtr<nsIDOMElement> positionedElement; + htmlEditor->GetPositionedElement(getter_AddRefs(positionedElement)); + *outCmdEnabled = (nullptr != positionedElement); + return NS_OK; +} + +NS_IMETHODIMP +nsIncreaseZIndexCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLAbsPosEditor> htmlEditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmlEditor, NS_ERROR_NOT_IMPLEMENTED); + + return htmlEditor->RelativeChangeZIndex(1); +} + +NS_IMETHODIMP +nsIncreaseZIndexCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsIncreaseZIndexCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + + bool enabled = false; + nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled); + NS_ENSURE_SUCCESS(rv, rv); + + return aParams->SetBooleanValue(STATE_ENABLED, enabled); +} + + +NS_IMETHODIMP +nsRemoveStylesCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + // test if we have any styles? + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + + +NS_IMETHODIMP +nsRemoveStylesCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + + nsresult rv = NS_OK; + if (editor) { + rv = editor->RemoveAllInlineProperties(); + } + + return rv; +} + +NS_IMETHODIMP +nsRemoveStylesCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsRemoveStylesCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled); +} + +NS_IMETHODIMP +nsIncreaseFontSizeCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + // test if we are at max size? + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsIncreaseFontSizeCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + + nsresult rv = NS_OK; + if (editor) { + rv = editor->IncreaseFontSize(); + } + + return rv; +} + +NS_IMETHODIMP +nsIncreaseFontSizeCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsIncreaseFontSizeCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled); +} + +NS_IMETHODIMP +nsDecreaseFontSizeCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + // test if we are at min size? + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsDecreaseFontSizeCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + + nsresult rv = NS_OK; + if (editor) { + rv = editor->DecreaseFontSize(); + } + + return rv; +} + +NS_IMETHODIMP +nsDecreaseFontSizeCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return DoCommand(aCommandName, refCon); +} + +NS_IMETHODIMP +nsDecreaseFontSizeCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED,outCmdEnabled); +} + +NS_IMETHODIMP +nsInsertHTMLCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + NS_ENSURE_ARG_POINTER(outCmdEnabled); + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsInsertHTMLCommand::DoCommand(const char *aCommandName, nsISupports *refCon) +{ + // If nsInsertHTMLCommand is called with no parameters, it was probably called with + // an empty string parameter ''. In this case, it should act the same as the delete command + NS_ENSURE_ARG_POINTER(refCon); + + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED); + + nsString html = EmptyString(); + return editor->InsertHTML(html); +} + +NS_IMETHODIMP +nsInsertHTMLCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + NS_ENSURE_ARG_POINTER(refCon); + + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED); + + // Get HTML source string to insert from command params + nsAutoString html; + nsresult rv = aParams->GetStringValue(STATE_DATA, html); + NS_ENSURE_SUCCESS(rv, rv); + + return editor->InsertHTML(html); +} + +NS_IMETHODIMP +nsInsertHTMLCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + NS_ENSURE_ARG_POINTER(refCon); + + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled); +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsInsertTagCommand, nsBaseComposerCommand) + +nsInsertTagCommand::nsInsertTagCommand(nsIAtom* aTagName) +: nsBaseComposerCommand() +, mTagName(aTagName) +{ + MOZ_ASSERT(mTagName); +} + +nsInsertTagCommand::~nsInsertTagCommand() +{ +} + +NS_IMETHODIMP +nsInsertTagCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + NS_ENSURE_ARG_POINTER(outCmdEnabled); + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) + return editor->GetIsSelectionEditable(outCmdEnabled); + + *outCmdEnabled = false; + return NS_OK; +} + + +// corresponding STATE_ATTRIBUTE is: src (img) and href (a) +NS_IMETHODIMP +nsInsertTagCommand::DoCommand(const char *aCmdName, nsISupports *refCon) +{ + NS_ENSURE_TRUE(mTagName == nsGkAtoms::hr, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIDOMElement> domElem; + nsresult rv = editor->CreateElementWithDefaults( + nsDependentAtomString(mTagName), getter_AddRefs(domElem)); + NS_ENSURE_SUCCESS(rv, rv); + + return editor->InsertElementAtSelection(domElem, true); +} + +NS_IMETHODIMP +nsInsertTagCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(refCon); + + // inserting an hr shouldn't have an parameters, just call DoCommand for that + if (mTagName == nsGkAtoms::hr) { + return DoCommand(aCommandName, refCon); + } + + NS_ENSURE_ARG_POINTER(aParams); + + nsCOMPtr<nsIHTMLEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_NOT_IMPLEMENTED); + + // do we have an href to use for creating link? + nsXPIDLCString s; + nsresult rv = aParams->GetCStringValue(STATE_ATTRIBUTE, getter_Copies(s)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString attrib; attrib.AssignWithConversion(s); + + if (attrib.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + // filter out tags we don't know how to insert + nsAutoString attributeType; + if (mTagName == nsGkAtoms::a) { + attributeType.AssignLiteral("href"); + } else if (mTagName == nsGkAtoms::img) { + attributeType.AssignLiteral("src"); + } else { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsCOMPtr<nsIDOMElement> domElem; + rv = editor->CreateElementWithDefaults(nsDependentAtomString(mTagName), + getter_AddRefs(domElem)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = domElem->SetAttribute(attributeType, attrib); + NS_ENSURE_SUCCESS(rv, rv); + + // do actual insertion + if (mTagName == nsGkAtoms::a) + return editor->InsertLinkAroundSelection(domElem); + + return editor->InsertElementAtSelection(domElem, true); +} + +NS_IMETHODIMP +nsInsertTagCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + NS_ENSURE_ARG_POINTER(refCon); + + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + return aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled); +} + + +/****************************/ +//HELPER METHODS +/****************************/ + +nsresult +GetListState(nsIHTMLEditor* aEditor, bool* aMixed, nsAString& aLocalName) +{ + MOZ_ASSERT(aEditor); + MOZ_ASSERT(aMixed); + + *aMixed = false; + aLocalName.Truncate(); + + bool bOL, bUL, bDL; + nsresult rv = aEditor->GetListState(aMixed, &bOL, &bUL, &bDL); + NS_ENSURE_SUCCESS(rv, rv); + + if (*aMixed) { + return NS_OK; + } + + if (bOL) { + aLocalName.AssignLiteral("ol"); + } else if (bUL) { + aLocalName.AssignLiteral("ul"); + } else if (bDL) { + aLocalName.AssignLiteral("dl"); + } + return NS_OK; +} + +nsresult +RemoveOneProperty(nsIHTMLEditor* aEditor, const nsAString& aProp) +{ + MOZ_ASSERT(aEditor); + + /// XXX Hack alert! Look in nsIEditProperty.h for this + nsCOMPtr<nsIAtom> styleAtom = NS_Atomize(aProp); + NS_ENSURE_TRUE(styleAtom, NS_ERROR_OUT_OF_MEMORY); + + return aEditor->RemoveInlineProperty(styleAtom, EmptyString()); +} + + +// the name of the attribute here should be the contents of the appropriate +// tag, e.g. 'b' for bold, 'i' for italics. +nsresult +RemoveTextProperty(nsIHTMLEditor* aEditor, const nsAString& aProp) +{ + MOZ_ASSERT(aEditor); + + if (aProp.LowerCaseEqualsLiteral("all")) { + return aEditor->RemoveAllInlineProperties(); + } + + return RemoveOneProperty(aEditor, aProp); +} + +// the name of the attribute here should be the contents of the appropriate +// tag, e.g. 'b' for bold, 'i' for italics. +nsresult +SetTextProperty(nsIHTMLEditor* aEditor, const nsAString& aProp) +{ + MOZ_ASSERT(aEditor); + + /// XXX Hack alert! Look in nsIEditProperty.h for this + nsCOMPtr<nsIAtom> styleAtom = NS_Atomize(aProp); + NS_ENSURE_TRUE(styleAtom, NS_ERROR_OUT_OF_MEMORY); + + return aEditor->SetInlineProperty(styleAtom, EmptyString(), EmptyString()); +} diff --git a/editor/composer/nsComposerCommands.h b/editor/composer/nsComposerCommands.h new file mode 100644 index 000000000..3d3855d40 --- /dev/null +++ b/editor/composer/nsComposerCommands.h @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsComposerCommands_h_ +#define nsComposerCommands_h_ + +#include "nsIControllerCommand.h" +#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS_INHERITED, etc +#include "nscore.h" // for nsresult, NS_IMETHOD + +class nsIAtom; +class nsICommandParams; +class nsIEditor; +class nsISupports; +class nsString; + +// This is a virtual base class for commands registered with the composer controller. +// Note that such commands are instantiated once per composer, so can store state. +// Also note that IsCommandEnabled can be called with an editor that may not +// have an editor yet (because the document is loading). Most commands will want +// to return false in this case. +// Don't hold on to any references to the editor or document from +// your command. This will cause leaks. Also, be aware that the document the +// editor is editing can change under you (if the user Reverts the file, for +// instance). +class nsBaseComposerCommand : public nsIControllerCommand +{ +protected: + virtual ~nsBaseComposerCommand() {} + +public: + + nsBaseComposerCommand(); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIControllerCommand. Declared longhand so we can make them pure virtual + NS_IMETHOD IsCommandEnabled(const char * aCommandName, nsISupports *aCommandRefCon, bool *_retval) override = 0; + NS_IMETHOD DoCommand(const char * aCommandName, nsISupports *aCommandRefCon) override = 0; + +}; + + +#define NS_DECL_COMPOSER_COMMAND(_cmd) \ +class _cmd : public nsBaseComposerCommand \ +{ \ +public: \ + NS_DECL_NSICONTROLLERCOMMAND \ +}; + +// virtual base class for commands that need to save and update Boolean state (like styles etc) +class nsBaseStateUpdatingCommand : public nsBaseComposerCommand +{ +public: + explicit nsBaseStateUpdatingCommand(nsIAtom* aTagName); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSICONTROLLERCOMMAND + +protected: + virtual ~nsBaseStateUpdatingCommand(); + + // get the current state (on or off) for this style or block format + virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams) = 0; + + // add/remove the style + virtual nsresult ToggleState(nsIEditor* aEditor) = 0; + +protected: + nsIAtom* mTagName; +}; + + +// Shared class for the various style updating commands like bold, italics etc. +// Suitable for commands whose state is either 'on' or 'off'. +class nsStyleUpdatingCommand : public nsBaseStateUpdatingCommand +{ +public: + explicit nsStyleUpdatingCommand(nsIAtom* aTagName); + +protected: + + // get the current state (on or off) for this style or block format + virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams); + + // add/remove the style + virtual nsresult ToggleState(nsIEditor* aEditor); +}; + + +class nsInsertTagCommand : public nsBaseComposerCommand +{ +public: + explicit nsInsertTagCommand(nsIAtom* aTagName); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSICONTROLLERCOMMAND + +protected: + virtual ~nsInsertTagCommand(); + + nsIAtom* mTagName; +}; + + +class nsListCommand : public nsBaseStateUpdatingCommand +{ +public: + explicit nsListCommand(nsIAtom* aTagName); + +protected: + + // get the current state (on or off) for this style or block format + virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams); + + // add/remove the style + virtual nsresult ToggleState(nsIEditor* aEditor); +}; + +class nsListItemCommand : public nsBaseStateUpdatingCommand +{ +public: + explicit nsListItemCommand(nsIAtom* aTagName); + +protected: + + // get the current state (on or off) for this style or block format + virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams); + + // add/remove the style + virtual nsresult ToggleState(nsIEditor* aEditor); +}; + +// Base class for commands whose state consists of a string (e.g. para format) +class nsMultiStateCommand : public nsBaseComposerCommand +{ +public: + + nsMultiStateCommand(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICONTROLLERCOMMAND + +protected: + virtual ~nsMultiStateCommand(); + + virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams) =0; + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState) = 0; + +}; + + +class nsParagraphStateCommand : public nsMultiStateCommand +{ +public: + nsParagraphStateCommand(); + +protected: + + virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams); + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState); +}; + +class nsFontFaceStateCommand : public nsMultiStateCommand +{ +public: + nsFontFaceStateCommand(); + +protected: + + virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams); + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState); +}; + +class nsFontSizeStateCommand : public nsMultiStateCommand +{ +public: + nsFontSizeStateCommand(); + +protected: + + virtual nsresult GetCurrentState(nsIEditor *aEditor, + nsICommandParams* aParams); + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState); +}; + +class nsHighlightColorStateCommand : public nsMultiStateCommand +{ +public: + nsHighlightColorStateCommand(); + +protected: + + NS_IMETHOD IsCommandEnabled(const char *aCommandName, nsISupports *aCommandRefCon, bool *_retval); + virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams); + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState); + +}; + +class nsFontColorStateCommand : public nsMultiStateCommand +{ +public: + nsFontColorStateCommand(); + +protected: + + virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams); + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState); +}; + +class nsAlignCommand : public nsMultiStateCommand +{ +public: + nsAlignCommand(); + +protected: + + virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams); + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState); +}; + +class nsBackgroundColorStateCommand : public nsMultiStateCommand +{ +public: + nsBackgroundColorStateCommand(); + +protected: + + virtual nsresult GetCurrentState(nsIEditor *aEditor, nsICommandParams* aParams); + virtual nsresult SetState(nsIEditor *aEditor, nsString& newState); +}; + +class nsAbsolutePositioningCommand : public nsBaseStateUpdatingCommand +{ +public: + nsAbsolutePositioningCommand(); + +protected: + + NS_IMETHOD IsCommandEnabled(const char *aCommandName, nsISupports *aCommandRefCon, bool *_retval); + virtual nsresult GetCurrentState(nsIEditor* aEditor, nsICommandParams* aParams); + virtual nsresult ToggleState(nsIEditor* aEditor); +}; + +// composer commands + +NS_DECL_COMPOSER_COMMAND(nsCloseCommand) +NS_DECL_COMPOSER_COMMAND(nsDocumentStateCommand) +NS_DECL_COMPOSER_COMMAND(nsSetDocumentStateCommand) +NS_DECL_COMPOSER_COMMAND(nsSetDocumentOptionsCommand) +//NS_DECL_COMPOSER_COMMAND(nsPrintingCommands) + +NS_DECL_COMPOSER_COMMAND(nsDecreaseZIndexCommand) +NS_DECL_COMPOSER_COMMAND(nsIncreaseZIndexCommand) + +// Generic commands + +// File menu +NS_DECL_COMPOSER_COMMAND(nsNewCommands) // handles 'new' anything + +// Edit menu +NS_DECL_COMPOSER_COMMAND(nsPasteNoFormattingCommand) + +// Block transformations +NS_DECL_COMPOSER_COMMAND(nsIndentCommand) +NS_DECL_COMPOSER_COMMAND(nsOutdentCommand) + +NS_DECL_COMPOSER_COMMAND(nsRemoveListCommand) +NS_DECL_COMPOSER_COMMAND(nsRemoveStylesCommand) +NS_DECL_COMPOSER_COMMAND(nsIncreaseFontSizeCommand) +NS_DECL_COMPOSER_COMMAND(nsDecreaseFontSizeCommand) + +// Insert content commands +NS_DECL_COMPOSER_COMMAND(nsInsertHTMLCommand) + +#endif // nsComposerCommands_h_ diff --git a/editor/composer/nsComposerCommandsUpdater.cpp b/editor/composer/nsComposerCommandsUpdater.cpp new file mode 100644 index 000000000..4999f7b92 --- /dev/null +++ b/editor/composer/nsComposerCommandsUpdater.cpp @@ -0,0 +1,381 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mozalloc.h" // for operator new +#include "nsAString.h" +#include "nsComponentManagerUtils.h" // for do_CreateInstance +#include "nsComposerCommandsUpdater.h" +#include "nsDebug.h" // for NS_ENSURE_TRUE, etc +#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc +#include "nsICommandManager.h" // for nsICommandManager +#include "nsID.h" // for NS_GET_IID, etc +#include "nsIDOMWindow.h" // for nsIDOMWindow +#include "nsIDocShell.h" // for nsIDocShell +#include "nsIInterfaceRequestorUtils.h" // for do_GetInterface +#include "nsISelection.h" // for nsISelection +#include "nsITransactionManager.h" // for nsITransactionManager +#include "nsLiteralString.h" // for NS_LITERAL_STRING +#include "nsPICommandUpdater.h" // for nsPICommandUpdater +#include "nsPIDOMWindow.h" // for nsPIDOMWindow + +class nsIDOMDocument; +class nsITransaction; + +nsComposerCommandsUpdater::nsComposerCommandsUpdater() +: mDirtyState(eStateUninitialized) +, mSelectionCollapsed(eStateUninitialized) +, mFirstDoOfFirstUndo(true) +{ +} + +nsComposerCommandsUpdater::~nsComposerCommandsUpdater() +{ + // cancel any outstanding update timer + if (mUpdateTimer) { + mUpdateTimer->Cancel(); + } +} + +NS_IMPL_ISUPPORTS(nsComposerCommandsUpdater, nsISelectionListener, + nsIDocumentStateListener, nsITransactionListener, nsITimerCallback) + +#if 0 +#pragma mark - +#endif + +NS_IMETHODIMP +nsComposerCommandsUpdater::NotifyDocumentCreated() +{ + // Trigger an nsIObserve notification that the document has been created + UpdateOneCommand("obs_documentCreated"); + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::NotifyDocumentWillBeDestroyed() +{ + // cancel any outstanding update timer + if (mUpdateTimer) { + mUpdateTimer->Cancel(); + mUpdateTimer = nullptr; + } + + // We can't call this right now; it is too late in some cases and the window + // is already partially destructed (e.g. JS objects may be gone). +#if 0 + // Trigger an nsIObserve notification that the document will be destroyed + UpdateOneCommand("obs_documentWillBeDestroyed"); +#endif + return NS_OK; +} + + +NS_IMETHODIMP +nsComposerCommandsUpdater::NotifyDocumentStateChanged(bool aNowDirty) +{ + // update document modified. We should have some other notifications for this too. + return UpdateDirtyState(aNowDirty); +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::NotifySelectionChanged(nsIDOMDocument *, + nsISelection *, int16_t) +{ + return PrimeUpdateTimer(); +} + +#if 0 +#pragma mark - +#endif + +NS_IMETHODIMP +nsComposerCommandsUpdater::WillDo(nsITransactionManager *aManager, + nsITransaction *aTransaction, bool *aInterrupt) +{ + *aInterrupt = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::DidDo(nsITransactionManager *aManager, + nsITransaction *aTransaction, nsresult aDoResult) +{ + // only need to update if the status of the Undo menu item changes. + int32_t undoCount; + aManager->GetNumberOfUndoItems(&undoCount); + if (undoCount == 1) { + if (mFirstDoOfFirstUndo) { + UpdateCommandGroup(NS_LITERAL_STRING("undo")); + } + mFirstDoOfFirstUndo = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::WillUndo(nsITransactionManager *aManager, + nsITransaction *aTransaction, + bool *aInterrupt) +{ + *aInterrupt = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::DidUndo(nsITransactionManager *aManager, + nsITransaction *aTransaction, + nsresult aUndoResult) +{ + int32_t undoCount; + aManager->GetNumberOfUndoItems(&undoCount); + if (undoCount == 0) + mFirstDoOfFirstUndo = true; // reset the state for the next do + + UpdateCommandGroup(NS_LITERAL_STRING("undo")); + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::WillRedo(nsITransactionManager *aManager, + nsITransaction *aTransaction, + bool *aInterrupt) +{ + *aInterrupt = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::DidRedo(nsITransactionManager *aManager, + nsITransaction *aTransaction, + nsresult aRedoResult) +{ + UpdateCommandGroup(NS_LITERAL_STRING("undo")); + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::WillBeginBatch(nsITransactionManager *aManager, + bool *aInterrupt) +{ + *aInterrupt = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::DidBeginBatch(nsITransactionManager *aManager, + nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::WillEndBatch(nsITransactionManager *aManager, + bool *aInterrupt) +{ + *aInterrupt = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::DidEndBatch(nsITransactionManager *aManager, + nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::WillMerge(nsITransactionManager *aManager, + nsITransaction *aTopTransaction, + nsITransaction *aTransactionToMerge, + bool *aInterrupt) +{ + *aInterrupt = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComposerCommandsUpdater::DidMerge(nsITransactionManager *aManager, + nsITransaction *aTopTransaction, + nsITransaction *aTransactionToMerge, + bool aDidMerge, nsresult aMergeResult) +{ + return NS_OK; +} + +#if 0 +#pragma mark - +#endif + +nsresult +nsComposerCommandsUpdater::Init(nsPIDOMWindowOuter* aDOMWindow) +{ + NS_ENSURE_ARG(aDOMWindow); + mDOMWindow = do_GetWeakReference(aDOMWindow); + mDocShell = do_GetWeakReference(aDOMWindow->GetDocShell()); + return NS_OK; +} + +nsresult +nsComposerCommandsUpdater::PrimeUpdateTimer() +{ + if (!mUpdateTimer) { + nsresult rv = NS_OK; + mUpdateTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + const uint32_t kUpdateTimerDelay = 150; + return mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this), + kUpdateTimerDelay, + nsITimer::TYPE_ONE_SHOT); +} + + +void nsComposerCommandsUpdater::TimerCallback() +{ + // if the selection state has changed, update stuff + bool isCollapsed = SelectionIsCollapsed(); + if (static_cast<int8_t>(isCollapsed) != mSelectionCollapsed) { + UpdateCommandGroup(NS_LITERAL_STRING("select")); + mSelectionCollapsed = isCollapsed; + } + + // isn't this redundant with the UpdateCommandGroup above? + // can we just nuke the above call? or create a meta command group? + UpdateCommandGroup(NS_LITERAL_STRING("style")); +} + +nsresult +nsComposerCommandsUpdater::UpdateDirtyState(bool aNowDirty) +{ + if (mDirtyState != static_cast<int8_t>(aNowDirty)) { + UpdateCommandGroup(NS_LITERAL_STRING("save")); + UpdateCommandGroup(NS_LITERAL_STRING("undo")); + mDirtyState = aNowDirty; + } + + return NS_OK; +} + +nsresult +nsComposerCommandsUpdater::UpdateCommandGroup(const nsAString& aCommandGroup) +{ + nsCOMPtr<nsPICommandUpdater> commandUpdater = GetCommandUpdater(); + NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE); + + + // This hardcoded list of commands is temporary. + // This code should use nsIControllerCommandGroup. + if (aCommandGroup.EqualsLiteral("undo")) { + commandUpdater->CommandStatusChanged("cmd_undo"); + commandUpdater->CommandStatusChanged("cmd_redo"); + return NS_OK; + } + + if (aCommandGroup.EqualsLiteral("select") || + aCommandGroup.EqualsLiteral("style")) { + commandUpdater->CommandStatusChanged("cmd_bold"); + commandUpdater->CommandStatusChanged("cmd_italic"); + commandUpdater->CommandStatusChanged("cmd_underline"); + commandUpdater->CommandStatusChanged("cmd_tt"); + + commandUpdater->CommandStatusChanged("cmd_strikethrough"); + commandUpdater->CommandStatusChanged("cmd_superscript"); + commandUpdater->CommandStatusChanged("cmd_subscript"); + commandUpdater->CommandStatusChanged("cmd_nobreak"); + + commandUpdater->CommandStatusChanged("cmd_em"); + commandUpdater->CommandStatusChanged("cmd_strong"); + commandUpdater->CommandStatusChanged("cmd_cite"); + commandUpdater->CommandStatusChanged("cmd_abbr"); + commandUpdater->CommandStatusChanged("cmd_acronym"); + commandUpdater->CommandStatusChanged("cmd_code"); + commandUpdater->CommandStatusChanged("cmd_samp"); + commandUpdater->CommandStatusChanged("cmd_var"); + + commandUpdater->CommandStatusChanged("cmd_increaseFont"); + commandUpdater->CommandStatusChanged("cmd_decreaseFont"); + + commandUpdater->CommandStatusChanged("cmd_paragraphState"); + commandUpdater->CommandStatusChanged("cmd_fontFace"); + commandUpdater->CommandStatusChanged("cmd_fontColor"); + commandUpdater->CommandStatusChanged("cmd_backgroundColor"); + commandUpdater->CommandStatusChanged("cmd_highlight"); + return NS_OK; + } + + if (aCommandGroup.EqualsLiteral("save")) { + // save commands (most are not in C++) + commandUpdater->CommandStatusChanged("cmd_setDocumentModified"); + commandUpdater->CommandStatusChanged("cmd_save"); + return NS_OK; + } + + return NS_OK; +} + +nsresult +nsComposerCommandsUpdater::UpdateOneCommand(const char *aCommand) +{ + nsCOMPtr<nsPICommandUpdater> commandUpdater = GetCommandUpdater(); + NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE); + + commandUpdater->CommandStatusChanged(aCommand); + + return NS_OK; +} + +bool +nsComposerCommandsUpdater::SelectionIsCollapsed() +{ + nsCOMPtr<nsPIDOMWindowOuter> domWindow = do_QueryReferent(mDOMWindow); + NS_ENSURE_TRUE(domWindow, true); + + nsCOMPtr<nsISelection> domSelection = domWindow->GetSelection(); + if (NS_WARN_IF(!domSelection)) { + return false; + } + + bool selectionCollapsed = false; + domSelection->GetIsCollapsed(&selectionCollapsed); + return selectionCollapsed; +} + +already_AddRefed<nsPICommandUpdater> +nsComposerCommandsUpdater::GetCommandUpdater() +{ + nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell); + NS_ENSURE_TRUE(docShell, nullptr); + nsCOMPtr<nsICommandManager> manager = docShell->GetCommandManager(); + nsCOMPtr<nsPICommandUpdater> updater = do_QueryInterface(manager); + return updater.forget(); +} + +#if 0 +#pragma mark - +#endif + +nsresult +nsComposerCommandsUpdater::Notify(nsITimer *timer) +{ + NS_ASSERTION(timer == mUpdateTimer.get(), "Hey, this ain't my timer!"); + TimerCallback(); + return NS_OK; +} + +#if 0 +#pragma mark - +#endif + + +nsresult +NS_NewComposerCommandsUpdater(nsISelectionListener** aInstancePtrResult) +{ + RefPtr<nsComposerCommandsUpdater> newThang = new nsComposerCommandsUpdater; + newThang.forget(aInstancePtrResult); + return NS_OK; +} diff --git a/editor/composer/nsComposerCommandsUpdater.h b/editor/composer/nsComposerCommandsUpdater.h new file mode 100644 index 000000000..3b853edeb --- /dev/null +++ b/editor/composer/nsComposerCommandsUpdater.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + +#ifndef nsComposerCommandsUpdater_h__ +#define nsComposerCommandsUpdater_h__ + +#include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr +#include "nsIDocumentStateListener.h" +#include "nsISelectionListener.h" +#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS +#include "nsITimer.h" // for NS_DECL_NSITIMERCALLBACK, etc +#include "nsITransactionListener.h" // for nsITransactionListener +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr +#include "nscore.h" // for NS_IMETHOD, nsresult, etc + +class nsPIDOMWindowOuter; +class nsITransaction; +class nsITransactionManager; +class nsPICommandUpdater; + +class nsComposerCommandsUpdater : public nsISelectionListener, + public nsIDocumentStateListener, + public nsITransactionListener, + public nsITimerCallback +{ +public: + + nsComposerCommandsUpdater(); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsISelectionListener + NS_DECL_NSISELECTIONLISTENER + + // nsIDocumentStateListener + NS_DECL_NSIDOCUMENTSTATELISTENER + + // nsITimerCallback interfaces + NS_DECL_NSITIMERCALLBACK + + /** nsITransactionListener interfaces + */ + NS_IMETHOD WillDo(nsITransactionManager *aManager, nsITransaction *aTransaction, bool *aInterrupt) override; + NS_IMETHOD DidDo(nsITransactionManager *aManager, nsITransaction *aTransaction, nsresult aDoResult) override; + NS_IMETHOD WillUndo(nsITransactionManager *aManager, nsITransaction *aTransaction, bool *aInterrupt) override; + NS_IMETHOD DidUndo(nsITransactionManager *aManager, nsITransaction *aTransaction, nsresult aUndoResult) override; + NS_IMETHOD WillRedo(nsITransactionManager *aManager, nsITransaction *aTransaction, bool *aInterrupt) override; + NS_IMETHOD DidRedo(nsITransactionManager *aManager, nsITransaction *aTransaction, nsresult aRedoResult) override; + NS_IMETHOD WillBeginBatch(nsITransactionManager *aManager, bool *aInterrupt) override; + NS_IMETHOD DidBeginBatch(nsITransactionManager *aManager, nsresult aResult) override; + NS_IMETHOD WillEndBatch(nsITransactionManager *aManager, bool *aInterrupt) override; + NS_IMETHOD DidEndBatch(nsITransactionManager *aManager, nsresult aResult) override; + NS_IMETHOD WillMerge(nsITransactionManager *aManager, nsITransaction *aTopTransaction, + nsITransaction *aTransactionToMerge, bool *aInterrupt) override; + NS_IMETHOD DidMerge(nsITransactionManager *aManager, nsITransaction *aTopTransaction, + nsITransaction *aTransactionToMerge, + bool aDidMerge, nsresult aMergeResult) override; + + + nsresult Init(nsPIDOMWindowOuter* aDOMWindow); + +protected: + + virtual ~nsComposerCommandsUpdater(); + + enum { + eStateUninitialized = -1, + eStateOff = false, + eStateOn = true + }; + + bool SelectionIsCollapsed(); + nsresult UpdateDirtyState(bool aNowDirty); + nsresult UpdateOneCommand(const char* aCommand); + nsresult UpdateCommandGroup(const nsAString& aCommandGroup); + + already_AddRefed<nsPICommandUpdater> GetCommandUpdater(); + + nsresult PrimeUpdateTimer(); + void TimerCallback(); + nsCOMPtr<nsITimer> mUpdateTimer; + + nsWeakPtr mDOMWindow; + nsWeakPtr mDocShell; + int8_t mDirtyState; + int8_t mSelectionCollapsed; + bool mFirstDoOfFirstUndo; + + +}; + +extern "C" nsresult NS_NewComposerCommandsUpdater(nsISelectionListener** aInstancePtrResult); + + +#endif // nsComposerCommandsUpdater_h__ diff --git a/editor/composer/nsComposerController.cpp b/editor/composer/nsComposerController.cpp new file mode 100644 index 000000000..5d90fbff0 --- /dev/null +++ b/editor/composer/nsComposerController.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mozalloc.h" // for operator new +#include "nsComposerCommands.h" // for nsStyleUpdatingCommand, etc +#include "nsComposerController.h" +#include "nsError.h" // for NS_OK +#include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::a, etc +#include "nsIControllerCommandTable.h" // for nsIControllerCommandTable + +class nsIControllerCommand; + +#define NS_REGISTER_ONE_COMMAND(_cmdClass, _cmdName) \ + { \ + _cmdClass* theCmd = new _cmdClass(); \ + inCommandTable->RegisterCommand(_cmdName, \ + static_cast<nsIControllerCommand *>(theCmd)); \ + } + +#define NS_REGISTER_FIRST_COMMAND(_cmdClass, _cmdName) \ + { \ + _cmdClass* theCmd = new _cmdClass(); \ + inCommandTable->RegisterCommand(_cmdName, \ + static_cast<nsIControllerCommand *>(theCmd)); + +#define NS_REGISTER_NEXT_COMMAND(_cmdClass, _cmdName) \ + inCommandTable->RegisterCommand(_cmdName, \ + static_cast<nsIControllerCommand *>(theCmd)); + +#define NS_REGISTER_LAST_COMMAND(_cmdClass, _cmdName) \ + inCommandTable->RegisterCommand(_cmdName, \ + static_cast<nsIControllerCommand *>(theCmd)); \ + } + +#define NS_REGISTER_STYLE_COMMAND(_cmdClass, _cmdName, _styleTag) \ + { \ + _cmdClass* theCmd = new _cmdClass(_styleTag); \ + inCommandTable->RegisterCommand(_cmdName, \ + static_cast<nsIControllerCommand *>(theCmd)); \ + } + +#define NS_REGISTER_TAG_COMMAND(_cmdClass, _cmdName, _tagName) \ + { \ + _cmdClass* theCmd = new _cmdClass(_tagName); \ + inCommandTable->RegisterCommand(_cmdName, \ + static_cast<nsIControllerCommand *>(theCmd)); \ + } + + +// static +nsresult +nsComposerController::RegisterEditorDocStateCommands( + nsIControllerCommandTable *inCommandTable) +{ + // observer commands for document state + NS_REGISTER_FIRST_COMMAND(nsDocumentStateCommand, "obs_documentCreated") + NS_REGISTER_NEXT_COMMAND(nsDocumentStateCommand, "obs_documentWillBeDestroyed") + NS_REGISTER_LAST_COMMAND(nsDocumentStateCommand, "obs_documentLocationChanged") + + // commands that may get or change state + NS_REGISTER_FIRST_COMMAND(nsSetDocumentStateCommand, "cmd_setDocumentModified") + NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_setDocumentUseCSS") + NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_setDocumentReadOnly") + NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_insertBrOnReturn") + NS_REGISTER_NEXT_COMMAND(nsSetDocumentStateCommand, "cmd_enableObjectResizing") + NS_REGISTER_LAST_COMMAND(nsSetDocumentStateCommand, "cmd_enableInlineTableEditing") + + NS_REGISTER_ONE_COMMAND(nsSetDocumentOptionsCommand, "cmd_setDocumentOptions") + + return NS_OK; +} + +// static +nsresult +nsComposerController::RegisterHTMLEditorCommands( + nsIControllerCommandTable *inCommandTable) +{ + // Edit menu + NS_REGISTER_ONE_COMMAND(nsPasteNoFormattingCommand, "cmd_pasteNoFormatting"); + + // indent/outdent + NS_REGISTER_ONE_COMMAND(nsIndentCommand, "cmd_indent"); + NS_REGISTER_ONE_COMMAND(nsOutdentCommand, "cmd_outdent"); + + // Styles + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_bold", nsGkAtoms::b); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_italic", nsGkAtoms::i); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_underline", nsGkAtoms::u); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_tt", nsGkAtoms::tt); + + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_strikethrough", nsGkAtoms::strike); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_superscript", nsGkAtoms::sup); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_subscript", nsGkAtoms::sub); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_nobreak", nsGkAtoms::nobr); + + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_em", nsGkAtoms::em); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_strong", nsGkAtoms::strong); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_cite", nsGkAtoms::cite); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_abbr", nsGkAtoms::abbr); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_acronym", nsGkAtoms::acronym); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_code", nsGkAtoms::code); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_samp", nsGkAtoms::samp); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_var", nsGkAtoms::var); + NS_REGISTER_STYLE_COMMAND(nsStyleUpdatingCommand, "cmd_removeLinks", nsGkAtoms::href); + + // lists + NS_REGISTER_STYLE_COMMAND(nsListCommand, "cmd_ol", nsGkAtoms::ol); + NS_REGISTER_STYLE_COMMAND(nsListCommand, "cmd_ul", nsGkAtoms::ul); + NS_REGISTER_STYLE_COMMAND(nsListItemCommand, "cmd_dt", nsGkAtoms::dt); + NS_REGISTER_STYLE_COMMAND(nsListItemCommand, "cmd_dd", nsGkAtoms::dd); + NS_REGISTER_ONE_COMMAND(nsRemoveListCommand, "cmd_removeList"); + + // format stuff + NS_REGISTER_ONE_COMMAND(nsParagraphStateCommand, "cmd_paragraphState"); + NS_REGISTER_ONE_COMMAND(nsFontFaceStateCommand, "cmd_fontFace"); + NS_REGISTER_ONE_COMMAND(nsFontSizeStateCommand, "cmd_fontSize"); + NS_REGISTER_ONE_COMMAND(nsFontColorStateCommand, "cmd_fontColor"); + NS_REGISTER_ONE_COMMAND(nsBackgroundColorStateCommand, "cmd_backgroundColor"); + NS_REGISTER_ONE_COMMAND(nsHighlightColorStateCommand, "cmd_highlight"); + + NS_REGISTER_ONE_COMMAND(nsAlignCommand, "cmd_align"); + NS_REGISTER_ONE_COMMAND(nsRemoveStylesCommand, "cmd_removeStyles"); + + NS_REGISTER_ONE_COMMAND(nsIncreaseFontSizeCommand, "cmd_increaseFont"); + NS_REGISTER_ONE_COMMAND(nsDecreaseFontSizeCommand, "cmd_decreaseFont"); + + // Insert content + NS_REGISTER_ONE_COMMAND(nsInsertHTMLCommand, "cmd_insertHTML"); + NS_REGISTER_TAG_COMMAND(nsInsertTagCommand, "cmd_insertLinkNoUI", nsGkAtoms::a); + NS_REGISTER_TAG_COMMAND(nsInsertTagCommand, "cmd_insertImageNoUI", nsGkAtoms::img); + NS_REGISTER_TAG_COMMAND(nsInsertTagCommand, "cmd_insertHR", nsGkAtoms::hr); + + NS_REGISTER_ONE_COMMAND(nsAbsolutePositioningCommand, "cmd_absPos"); + NS_REGISTER_ONE_COMMAND(nsDecreaseZIndexCommand, "cmd_decreaseZIndex"); + NS_REGISTER_ONE_COMMAND(nsIncreaseZIndexCommand, "cmd_increaseZIndex"); + + return NS_OK; +} diff --git a/editor/composer/nsComposerController.h b/editor/composer/nsComposerController.h new file mode 100644 index 000000000..768c67b80 --- /dev/null +++ b/editor/composer/nsComposerController.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsComposerController_h__ +#define nsComposerController_h__ + + +#include "nscore.h" // for nsresult + +class nsIControllerCommandTable; + + +// The plaintext editor controller is used for basic text editing and html editing +// commands in composer +// The refCon that gets passed to its commands is initially nsIEditingSession, +// and after successfule editor creation it is changed to nsIEditor. +#define NS_EDITORDOCSTATECONTROLLER_CID \ + { 0x50e95301, 0x17a8, 0x11d4, { 0x9f, 0x7e, 0xdd, 0x53, 0x0d, 0x5f, 0x05, 0x7c } } + +// The HTMLEditor controller is used only for HTML editors and takes nsIEditor as refCon +#define NS_HTMLEDITORCONTROLLER_CID \ + { 0x62db0002, 0xdbb6, 0x43f4, { 0x8f, 0xb7, 0x9d, 0x25, 0x38, 0xbc, 0x57, 0x47 } } + + +class nsComposerController +{ +public: + static nsresult RegisterEditorDocStateCommands(nsIControllerCommandTable* inCommandTable); + static nsresult RegisterHTMLEditorCommands(nsIControllerCommandTable* inCommandTable); +}; + +#endif /* nsComposerController_h__ */ diff --git a/editor/composer/nsComposerDocumentCommands.cpp b/editor/composer/nsComposerDocumentCommands.cpp new file mode 100644 index 000000000..d44e940f6 --- /dev/null +++ b/editor/composer/nsComposerDocumentCommands.cpp @@ -0,0 +1,480 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsCOMPtr.h" // for nsCOMPtr, do_QueryInterface, etc +#include "nsCRT.h" // for nsCRT +#include "nsComposerCommands.h" // for nsSetDocumentOptionsCommand, etc +#include "nsDebug.h" // for NS_ENSURE_ARG_POINTER, etc +#include "nsError.h" // for NS_ERROR_INVALID_ARG, etc +#include "nsICommandParams.h" // for nsICommandParams +#include "nsIDOMDocument.h" // for nsIDOMDocument +#include "nsIDocShell.h" // for nsIDocShell +#include "nsIDocument.h" // for nsIDocument +#include "nsIEditingSession.h" // for nsIEditingSession, etc +#include "nsIEditor.h" // for nsIEditor +#include "nsIHTMLEditor.h" // for nsIHTMLEditor +#include "nsIHTMLInlineTableEditor.h" // for nsIHTMLInlineTableEditor +#include "nsIHTMLObjectResizer.h" // for nsIHTMLObjectResizer +#include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc +#include "nsIPresShell.h" // for nsIPresShell +#include "nsISelectionController.h" // for nsISelectionController +#include "nsISupportsImpl.h" // for nsPresContext::Release +#include "nsISupportsUtils.h" // for NS_IF_ADDREF +#include "nsIURI.h" // for nsIURI +#include "nsPresContext.h" // for nsPresContext +#include "nscore.h" // for NS_IMETHODIMP, nsresult, etc + +class nsISupports; + +//defines +#define STATE_ENABLED "state_enabled" +#define STATE_ALL "state_all" +#define STATE_ATTRIBUTE "state_attribute" +#define STATE_DATA "state_data" + +static +nsresult +GetPresContextFromEditor(nsIEditor *aEditor, nsPresContext **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + NS_ENSURE_ARG_POINTER(aEditor); + + nsCOMPtr<nsISelectionController> selCon; + nsresult rv = aEditor->GetSelectionController(getter_AddRefs(selCon)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); + + nsCOMPtr<nsIPresShell> presShell = do_QueryInterface(selCon); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + NS_IF_ADDREF(*aResult = presShell->GetPresContext()); + return NS_OK; +} + +NS_IMETHODIMP +nsSetDocumentOptionsCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + NS_ENSURE_ARG_POINTER(outCmdEnabled); + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) { + return editor->GetIsSelectionEditable(outCmdEnabled); + } + + *outCmdEnabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsSetDocumentOptionsCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSetDocumentOptionsCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG); + + RefPtr<nsPresContext> presContext; + nsresult rv = GetPresContextFromEditor(editor, getter_AddRefs(presContext)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + int32_t animationMode; + rv = aParams->GetLongValue("imageAnimation", &animationMode); + if (NS_SUCCEEDED(rv)) { + // for possible values of animation mode, see: + // http://lxr.mozilla.org/seamonkey/source/image/public/imgIContainer.idl + presContext->SetImageAnimationMode(animationMode); + } + + bool allowPlugins; + rv = aParams->GetBooleanValue("plugins", &allowPlugins); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIDocShell> docShell(presContext->GetDocShell()); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + rv = docShell->SetAllowPlugins(allowPlugins); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSetDocumentOptionsCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + NS_ENSURE_ARG_POINTER(refCon); + + // The base editor owns most state info + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG); + + // Always get the enabled state + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + nsresult rv = aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled); + NS_ENSURE_SUCCESS(rv, rv); + + // get pres context + RefPtr<nsPresContext> presContext; + rv = GetPresContextFromEditor(editor, getter_AddRefs(presContext)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + int32_t animationMode; + rv = aParams->GetLongValue("imageAnimation", &animationMode); + if (NS_SUCCEEDED(rv)) { + // for possible values of animation mode, see + // http://lxr.mozilla.org/seamonkey/source/image/public/imgIContainer.idl + rv = aParams->SetLongValue("imageAnimation", + presContext->ImageAnimationMode()); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool allowPlugins = false; + rv = aParams->GetBooleanValue("plugins", &allowPlugins); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIDocShell> docShell(presContext->GetDocShell()); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + allowPlugins = docShell->PluginsAllowedInCurrentDoc(); + + rv = aParams->SetBooleanValue("plugins", allowPlugins); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + + +/** + * Commands for document state that may be changed via doCommandParams + * As of 11/11/02, this is just "cmd_setDocumentModified" + * Note that you can use the same command class, nsSetDocumentStateCommand, + * for more than one of this type of command + * We check the input command param for different behavior + */ + +NS_IMETHODIMP +nsSetDocumentStateCommand::IsCommandEnabled(const char * aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + // These commands are always enabled + NS_ENSURE_ARG_POINTER(outCmdEnabled); + *outCmdEnabled = true; + return NS_OK; +} + +NS_IMETHODIMP +nsSetDocumentStateCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSetDocumentStateCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG); + + if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentModified")) { + NS_ENSURE_ARG_POINTER(aParams); + + bool modified; + nsresult rv = aParams->GetBooleanValue(STATE_ATTRIBUTE, &modified); + + // Should we fail if this param wasn't set? + // I'm not sure we should be that strict + NS_ENSURE_SUCCESS(rv, rv); + + if (modified) { + return editor->IncrementModificationCount(1); + } + + return editor->ResetModificationCount(); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentReadOnly")) { + NS_ENSURE_ARG_POINTER(aParams); + bool isReadOnly; + nsresult rvRO = aParams->GetBooleanValue(STATE_ATTRIBUTE, &isReadOnly); + NS_ENSURE_SUCCESS(rvRO, rvRO); + + uint32_t flags; + editor->GetFlags(&flags); + if (isReadOnly) { + flags |= nsIPlaintextEditor::eEditorReadonlyMask; + } else { + flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask); + } + + return editor->SetFlags(flags); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentUseCSS")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG); + + bool desireCSS; + nsresult rvCSS = aParams->GetBooleanValue(STATE_ATTRIBUTE, &desireCSS); + NS_ENSURE_SUCCESS(rvCSS, rvCSS); + + return htmleditor->SetIsCSSEnabled(desireCSS); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_insertBrOnReturn")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG); + + bool insertBrOnReturn; + nsresult rvBR = aParams->GetBooleanValue(STATE_ATTRIBUTE, + &insertBrOnReturn); + NS_ENSURE_SUCCESS(rvBR, rvBR); + + return htmleditor->SetReturnInParagraphCreatesNewParagraph(!insertBrOnReturn); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_enableObjectResizing")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLObjectResizer> resizer = do_QueryInterface(refCon); + NS_ENSURE_TRUE(resizer, NS_ERROR_INVALID_ARG); + + bool enabled; + nsresult rvOR = aParams->GetBooleanValue(STATE_ATTRIBUTE, &enabled); + NS_ENSURE_SUCCESS(rvOR, rvOR); + + return resizer->SetObjectResizingEnabled(enabled); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_enableInlineTableEditing")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLInlineTableEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG); + + bool enabled; + nsresult rvOR = aParams->GetBooleanValue(STATE_ATTRIBUTE, &enabled); + NS_ENSURE_SUCCESS(rvOR, rvOR); + + return editor->SetInlineTableEditingEnabled(enabled); + } + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSetDocumentStateCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + NS_ENSURE_ARG_POINTER(refCon); + + // The base editor owns most state info + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG); + + // Always get the enabled state + bool outCmdEnabled = false; + IsCommandEnabled(aCommandName, refCon, &outCmdEnabled); + nsresult rv = aParams->SetBooleanValue(STATE_ENABLED, outCmdEnabled); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentModified")) { + bool modified; + rv = editor->GetDocumentModified(&modified); + NS_ENSURE_SUCCESS(rv, rv); + + return aParams->SetBooleanValue(STATE_ATTRIBUTE, modified); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentReadOnly")) { + NS_ENSURE_ARG_POINTER(aParams); + + uint32_t flags; + editor->GetFlags(&flags); + bool isReadOnly = flags & nsIPlaintextEditor::eEditorReadonlyMask; + return aParams->SetBooleanValue(STATE_ATTRIBUTE, isReadOnly); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentUseCSS")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG); + + bool isCSS; + htmleditor->GetIsCSSEnabled(&isCSS); + return aParams->SetBooleanValue(STATE_ALL, isCSS); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_insertBrOnReturn")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLEditor> htmleditor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(htmleditor, NS_ERROR_INVALID_ARG); + + bool createPOnReturn; + htmleditor->GetReturnInParagraphCreatesNewParagraph(&createPOnReturn); + return aParams->SetBooleanValue(STATE_ATTRIBUTE, !createPOnReturn); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_enableObjectResizing")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLObjectResizer> resizer = do_QueryInterface(refCon); + NS_ENSURE_TRUE(resizer, NS_ERROR_INVALID_ARG); + + bool enabled; + resizer->GetObjectResizingEnabled(&enabled); + return aParams->SetBooleanValue(STATE_ATTRIBUTE, enabled); + } + + if (!nsCRT::strcmp(aCommandName, "cmd_enableInlineTableEditing")) { + NS_ENSURE_ARG_POINTER(aParams); + nsCOMPtr<nsIHTMLInlineTableEditor> editor = do_QueryInterface(refCon); + NS_ENSURE_TRUE(editor, NS_ERROR_INVALID_ARG); + + bool enabled; + editor->GetInlineTableEditingEnabled(&enabled); + return aParams->SetBooleanValue(STATE_ATTRIBUTE, enabled); + } + + return NS_ERROR_NOT_IMPLEMENTED; +} + +/** + * Commands just for state notification + * As of 11/21/02, possible commands are: + * "obs_documentCreated" + * "obs_documentWillBeDestroyed" + * "obs_documentLocationChanged" + * Note that you can use the same command class, nsDocumentStateCommand + * for these or future observer commands. + * We check the input command param for different behavior + * + * How to use: + * 1. Get the nsICommandManager for the current editor + * 2. Implement an nsIObserve object, e.g: + * + * void Observe( + * in nsISupports aSubject, // The nsICommandManager calling this Observer + * in string aTopic, // command name, e.g.:"obs_documentCreated" + * // or "obs_documentWillBeDestroyed" + in wstring aData ); // ignored (set to "command_status_changed") + * + * 3. Add the observer by: + * commandManager.addObserver(observeobject, obs_documentCreated); + * 4. In the appropriate location in editorSession, editor, or commands code, + * trigger the notification of this observer by something like: + * + * nsCOMPtr<nsICommandManager> commandManager = mDocShell->GetCommandManager(); + * nsCOMPtr<nsPICommandUpdater> commandUpdater = do_QueryInterface(commandManager); + * NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE); + * commandUpdater->CommandStatusChanged(obs_documentCreated); + * + * 5. Use GetCommandStateParams() to obtain state information + * e.g., any creation state codes when creating an editor are + * supplied for "obs_documentCreated" command in the + * "state_data" param's value + * + */ + +NS_IMETHODIMP +nsDocumentStateCommand::IsCommandEnabled(const char* aCommandName, + nsISupports *refCon, + bool *outCmdEnabled) +{ + NS_ENSURE_ARG_POINTER(outCmdEnabled); + // Always return false to discourage callers from using DoCommand() + *outCmdEnabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsDocumentStateCommand::DoCommand(const char *aCommandName, + nsISupports *refCon) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocumentStateCommand::DoCommandParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocumentStateCommand::GetCommandStateParams(const char *aCommandName, + nsICommandParams *aParams, + nsISupports *refCon) +{ + NS_ENSURE_ARG_POINTER(aParams); + NS_ENSURE_ARG_POINTER(aCommandName); + nsresult rv; + + if (!nsCRT::strcmp(aCommandName, "obs_documentCreated")) { + uint32_t editorStatus = nsIEditingSession::eEditorErrorUnknown; + + nsCOMPtr<nsIEditingSession> editingSession = do_QueryInterface(refCon); + if (editingSession) { + // refCon is initially set to nsIEditingSession until editor + // is successfully created and source doc is loaded + // Embedder gets error status if this fails + // If called before startup is finished, + // status = eEditorCreationInProgress + rv = editingSession->GetEditorStatus(&editorStatus); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // If refCon is an editor, then everything started up OK! + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (editor) { + editorStatus = nsIEditingSession::eEditorOK; + } + } + + // Note that if refCon is not-null, but is neither + // an nsIEditingSession or nsIEditor, we return "eEditorErrorUnknown" + aParams->SetLongValue(STATE_DATA, editorStatus); + return NS_OK; + } + + if (!nsCRT::strcmp(aCommandName, "obs_documentLocationChanged")) { + nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon); + if (!editor) { + return NS_OK; + } + + nsCOMPtr<nsIDOMDocument> domDoc; + editor->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsIURI *uri = doc->GetDocumentURI(); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + return aParams->SetISupportsValue(STATE_DATA, (nsISupports*)uri); + } + + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/editor/composer/nsComposerRegistration.cpp b/editor/composer/nsComposerRegistration.cpp new file mode 100644 index 000000000..7a0b3a440 --- /dev/null +++ b/editor/composer/nsComposerRegistration.cpp @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stddef.h> // for nullptr + +#include "mozilla/Module.h" // for Module, Module::CIDEntry, etc +#include "mozilla/ModuleUtils.h" +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for nsCOMPtr, getter_AddRefs, etc +#include "nsComponentManagerUtils.h" // for do_CreateInstance +#include "nsComposeTxtSrvFilter.h" // for nsComposeTxtSrvFilter, etc +#include "nsComposerController.h" // for nsComposerController, etc +#include "nsDebug.h" // for NS_ENSURE_SUCCESS +#include "nsEditingSession.h" // for NS_EDITINGSESSION_CID, etc +#include "nsEditorSpellCheck.h" // for NS_EDITORSPELLCHECK_CID, etc +#include "nsError.h" // for NS_ERROR_NO_AGGREGATION, etc +#include "nsIController.h" // for nsIController +#include "nsIControllerCommandTable.h" // for nsIControllerCommandTable, etc +#include "nsIControllerContext.h" // for nsIControllerContext +#include "nsID.h" // for NS_DEFINE_NAMED_CID, etc +#include "nsISupportsImpl.h" +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "nsServiceManagerUtils.h" // for do_GetService +#include "nscore.h" // for nsresult + +class nsISupports; + +#define NS_HTMLEDITOR_COMMANDTABLE_CID \ +{ 0x13e50d8d, 0x9cee, 0x4ad1, { 0xa3, 0xa2, 0x4a, 0x44, 0x2f, 0xdf, 0x7d, 0xfa } } + +#define NS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID \ +{ 0xa33982d3, 0x1adf, 0x4162, { 0x99, 0x41, 0xf7, 0x34, 0xbc, 0x45, 0xe4, 0xed } } + + +static NS_DEFINE_CID(kHTMLEditorCommandTableCID, NS_HTMLEDITOR_COMMANDTABLE_CID); +static NS_DEFINE_CID(kHTMLEditorDocStateCommandTableCID, NS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID); + + +//////////////////////////////////////////////////////////////////////// +// Define the contructor function for the objects +// +// NOTE: This creates an instance of objects by using the default constructor +// + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsEditingSession) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsEditorSpellCheck) + +// There are no macros that enable us to have 2 constructors +// for the same object +// +// Here we are creating the same object with two different contract IDs +// and then initializing it different. +// Basically, we need to tell the filter whether it is doing mail or not +static nsresult +nsComposeTxtSrvFilterConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult, bool aIsForMail) +{ + *aResult = nullptr; + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + nsComposeTxtSrvFilter * inst = new nsComposeTxtSrvFilter(); + if (!inst) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(inst); + inst->Init(aIsForMail); + nsresult rv = inst->QueryInterface(aIID, aResult); + NS_RELEASE(inst); + return rv; +} + +static nsresult +nsComposeTxtSrvFilterConstructorForComposer(nsISupports *aOuter, + REFNSIID aIID, + void **aResult) +{ + return nsComposeTxtSrvFilterConstructor(aOuter, aIID, aResult, false); +} + +static nsresult +nsComposeTxtSrvFilterConstructorForMail(nsISupports *aOuter, + REFNSIID aIID, + void **aResult) +{ + return nsComposeTxtSrvFilterConstructor(aOuter, aIID, aResult, true); +} + + +// Constructor for a controller set up with a command table specified +// by the CID passed in. This function uses do_GetService to get the +// command table, so that every controller shares a single command +// table, for space-efficiency. +// +// The only reason to go via the service manager for the command table +// is that it holds onto the singleton for us, avoiding static variables here. +static nsresult +CreateControllerWithSingletonCommandTable(const nsCID& inCommandTableCID, nsIController **aResult) +{ + nsresult rv; + nsCOMPtr<nsIController> controller = do_CreateInstance("@mozilla.org/embedcomp/base-command-controller;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIControllerCommandTable> composerCommandTable = do_GetService(inCommandTableCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // this guy is a singleton, so make it immutable + composerCommandTable->MakeImmutable(); + + nsCOMPtr<nsIControllerContext> controllerContext = do_QueryInterface(controller, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = controllerContext->Init(composerCommandTable); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = controller; + NS_ADDREF(*aResult); + return NS_OK; +} + + +// Here we make an instance of the controller that holds doc state commands. +// We set it up with a singleton command table. +static nsresult +nsHTMLEditorDocStateControllerConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + nsCOMPtr<nsIController> controller; + nsresult rv = CreateControllerWithSingletonCommandTable(kHTMLEditorDocStateCommandTableCID, getter_AddRefs(controller)); + NS_ENSURE_SUCCESS(rv, rv); + + return controller->QueryInterface(aIID, aResult); +} + +// Tere we make an instance of the controller that holds composer commands. +// We set it up with a singleton command table. +static nsresult +nsHTMLEditorControllerConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + nsCOMPtr<nsIController> controller; + nsresult rv = CreateControllerWithSingletonCommandTable(kHTMLEditorCommandTableCID, getter_AddRefs(controller)); + NS_ENSURE_SUCCESS(rv, rv); + + return controller->QueryInterface(aIID, aResult); +} + +// Constructor for a command table that is pref-filled with HTML editor commands +static nsresult +nsHTMLEditorCommandTableConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + nsresult rv; + nsCOMPtr<nsIControllerCommandTable> commandTable = + do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsComposerController::RegisterHTMLEditorCommands(commandTable); + NS_ENSURE_SUCCESS(rv, rv); + + // we don't know here whether we're being created as an instance, + // or a service, so we can't become immutable + + return commandTable->QueryInterface(aIID, aResult); +} + + +// Constructor for a command table that is pref-filled with HTML editor doc state commands +static nsresult +nsHTMLEditorDocStateCommandTableConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + nsresult rv; + nsCOMPtr<nsIControllerCommandTable> commandTable = + do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsComposerController::RegisterEditorDocStateCommands(commandTable); + NS_ENSURE_SUCCESS(rv, rv); + + // we don't know here whether we're being created as an instance, + // or a service, so we can't become immutable + + return commandTable->QueryInterface(aIID, aResult); +} + +NS_DEFINE_NAMED_CID(NS_HTMLEDITORCONTROLLER_CID); +NS_DEFINE_NAMED_CID(NS_EDITORDOCSTATECONTROLLER_CID); +NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_COMMANDTABLE_CID); +NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID); +NS_DEFINE_NAMED_CID(NS_EDITINGSESSION_CID); +NS_DEFINE_NAMED_CID(NS_EDITORSPELLCHECK_CID); +NS_DEFINE_NAMED_CID(NS_COMPOSERTXTSRVFILTER_CID); +NS_DEFINE_NAMED_CID(NS_COMPOSERTXTSRVFILTERMAIL_CID); + + +static const mozilla::Module::CIDEntry kComposerCIDs[] = { + { &kNS_HTMLEDITORCONTROLLER_CID, false, nullptr, nsHTMLEditorControllerConstructor }, + { &kNS_EDITORDOCSTATECONTROLLER_CID, false, nullptr, nsHTMLEditorDocStateControllerConstructor }, + { &kNS_HTMLEDITOR_COMMANDTABLE_CID, false, nullptr, nsHTMLEditorCommandTableConstructor }, + { &kNS_HTMLEDITOR_DOCSTATE_COMMANDTABLE_CID, false, nullptr, nsHTMLEditorDocStateCommandTableConstructor }, + { &kNS_EDITINGSESSION_CID, false, nullptr, nsEditingSessionConstructor }, + { &kNS_EDITORSPELLCHECK_CID, false, nullptr, nsEditorSpellCheckConstructor }, + { &kNS_COMPOSERTXTSRVFILTER_CID, false, nullptr, nsComposeTxtSrvFilterConstructorForComposer }, + { &kNS_COMPOSERTXTSRVFILTERMAIL_CID, false, nullptr, nsComposeTxtSrvFilterConstructorForMail }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kComposerContracts[] = { + { "@mozilla.org/editor/htmleditorcontroller;1", &kNS_HTMLEDITORCONTROLLER_CID }, + { "@mozilla.org/editor/editordocstatecontroller;1", &kNS_EDITORDOCSTATECONTROLLER_CID }, + { "@mozilla.org/editor/editingsession;1", &kNS_EDITINGSESSION_CID }, + { "@mozilla.org/editor/editorspellchecker;1", &kNS_EDITORSPELLCHECK_CID }, + { COMPOSER_TXTSRVFILTER_CONTRACTID, &kNS_COMPOSERTXTSRVFILTER_CID }, + { COMPOSER_TXTSRVFILTERMAIL_CONTRACTID, &kNS_COMPOSERTXTSRVFILTERMAIL_CID }, + { nullptr } +}; + +static const mozilla::Module kComposerModule = { + mozilla::Module::kVersion, + kComposerCIDs, + kComposerContracts +}; + +NSMODULE_DEFN(nsComposerModule) = &kComposerModule; diff --git a/editor/composer/nsEditingSession.cpp b/editor/composer/nsEditingSession.cpp new file mode 100644 index 000000000..677b3b50f --- /dev/null +++ b/editor/composer/nsEditingSession.cpp @@ -0,0 +1,1392 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string.h> // for nullptr, strcmp + +#include "imgIContainer.h" // for imgIContainer, etc +#include "mozFlushType.h" // for mozFlushType::Flush_Frames +#include "mozilla/mozalloc.h" // for operator new +#include "nsAString.h" +#include "nsComponentManagerUtils.h" // for do_CreateInstance +#include "nsComposerCommandsUpdater.h" // for nsComposerCommandsUpdater +#include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc +#include "nsEditingSession.h" +#include "nsError.h" // for NS_ERROR_FAILURE, NS_OK, etc +#include "nsIChannel.h" // for nsIChannel +#include "nsICommandManager.h" // for nsICommandManager +#include "nsIContentViewer.h" // for nsIContentViewer +#include "nsIController.h" // for nsIController +#include "nsIControllerContext.h" // for nsIControllerContext +#include "nsIControllers.h" // for nsIControllers +#include "nsID.h" // for NS_GET_IID, etc +#include "nsIDOMDocument.h" // for nsIDOMDocument +#include "nsIDOMHTMLDocument.h" // for nsIDOMHTMLDocument +#include "nsIDOMWindow.h" // for nsIDOMWindow +#include "nsIDocShell.h" // for nsIDocShell +#include "nsIDocument.h" // for nsIDocument +#include "nsIDocumentStateListener.h" +#include "nsIEditor.h" // for nsIEditor +#include "nsIHTMLDocument.h" // for nsIHTMLDocument, etc +#include "nsIInterfaceRequestorUtils.h" // for do_GetInterface +#include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc +#include "nsIPresShell.h" // for nsIPresShell +#include "nsIRefreshURI.h" // for nsIRefreshURI +#include "nsIRequest.h" // for nsIRequest +#include "nsISelection.h" // for nsISelection +#include "nsISelectionPrivate.h" // for nsISelectionPrivate +#include "nsITimer.h" // for nsITimer, etc +#include "nsITransactionManager.h" // for nsITransactionManager +#include "nsIWeakReference.h" // for nsISupportsWeakReference, etc +#include "nsIWebNavigation.h" // for nsIWebNavigation +#include "nsIWebProgress.h" // for nsIWebProgress, etc +#include "nsLiteralString.h" // for NS_LITERAL_STRING +#include "nsPICommandUpdater.h" // for nsPICommandUpdater +#include "nsPIDOMWindow.h" // for nsPIDOMWindow +#include "nsPresContext.h" // for nsPresContext +#include "nsReadableUtils.h" // for AppendUTF16toUTF8 +#include "nsStringFwd.h" // for nsAFlatString +#include "mozilla/dom/Selection.h" // for AutoHideSelectionChanges +#include "nsFrameSelection.h" // for nsFrameSelection + +class nsISupports; +class nsIURI; + +/*--------------------------------------------------------------------------- + + nsEditingSession + +----------------------------------------------------------------------------*/ +nsEditingSession::nsEditingSession() +: mDoneSetup(false) +, mCanCreateEditor(false) +, mInteractive(false) +, mMakeWholeDocumentEditable(true) +, mDisabledJSAndPlugins(false) +, mScriptsEnabled(true) +, mPluginsEnabled(true) +, mProgressListenerRegistered(false) +, mImageAnimationMode(0) +, mEditorFlags(0) +, mEditorStatus(eEditorOK) +, mBaseCommandControllerId(0) +, mDocStateControllerId(0) +, mHTMLCommandControllerId(0) +{ +} + +/*--------------------------------------------------------------------------- + + ~nsEditingSession + +----------------------------------------------------------------------------*/ +nsEditingSession::~nsEditingSession() +{ + // Must cancel previous timer? + if (mLoadBlankDocTimer) + mLoadBlankDocTimer->Cancel(); +} + +NS_IMPL_ISUPPORTS(nsEditingSession, nsIEditingSession, nsIWebProgressListener, + nsISupportsWeakReference) + +/*--------------------------------------------------------------------------- + + MakeWindowEditable + + aEditorType string, "html" "htmlsimple" "text" "textsimple" + void makeWindowEditable(in nsIDOMWindow aWindow, in string aEditorType, + in boolean aDoAfterUriLoad, + in boolean aMakeWholeDocumentEditable, + in boolean aInteractive); +----------------------------------------------------------------------------*/ +#define DEFAULT_EDITOR_TYPE "html" + +NS_IMETHODIMP +nsEditingSession::MakeWindowEditable(mozIDOMWindowProxy* aWindow, + const char *aEditorType, + bool aDoAfterUriLoad, + bool aMakeWholeDocumentEditable, + bool aInteractive) +{ + mEditorType.Truncate(); + mEditorFlags = 0; + + NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE); + auto* window = nsPIDOMWindowOuter::From(aWindow); + + // disable plugins + nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + mDocShell = do_GetWeakReference(docShell); + mInteractive = aInteractive; + mMakeWholeDocumentEditable = aMakeWholeDocumentEditable; + + nsresult rv; + if (!mInteractive) { + rv = DisableJSAndPlugins(aWindow); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Always remove existing editor + TearDownEditorOnWindow(aWindow); + + // Tells embedder that startup is in progress + mEditorStatus = eEditorCreationInProgress; + + //temporary to set editor type here. we will need different classes soon. + if (!aEditorType) + aEditorType = DEFAULT_EDITOR_TYPE; + mEditorType = aEditorType; + + // if all this does is setup listeners and I don't need listeners, + // can't this step be ignored?? (based on aDoAfterURILoad) + rv = PrepareForEditing(window); + NS_ENSURE_SUCCESS(rv, rv); + + // set the flag on the docShell to say that it's editable + rv = docShell->MakeEditable(aDoAfterUriLoad); + NS_ENSURE_SUCCESS(rv, rv); + + // Setup commands common to plaintext and html editors, + // including the document creation observers + // the first is an editing controller + rv = SetupEditorCommandController("@mozilla.org/editor/editingcontroller;1", + aWindow, + static_cast<nsIEditingSession*>(this), + &mBaseCommandControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + // The second is a controller to monitor doc state, + // such as creation and "dirty flag" + rv = SetupEditorCommandController("@mozilla.org/editor/editordocstatecontroller;1", + aWindow, + static_cast<nsIEditingSession*>(this), + &mDocStateControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + // aDoAfterUriLoad can be false only when making an existing window editable + if (!aDoAfterUriLoad) { + rv = SetupEditorOnWindow(aWindow); + + // mEditorStatus is set to the error reason + // Since this is used only when editing an existing page, + // it IS ok to destroy current editor + if (NS_FAILED(rv)) { + TearDownEditorOnWindow(aWindow); + } + } + return rv; +} + +NS_IMETHODIMP +nsEditingSession::DisableJSAndPlugins(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE); + nsIDocShell *docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + bool tmp; + nsresult rv = docShell->GetAllowJavascript(&tmp); + NS_ENSURE_SUCCESS(rv, rv); + + mScriptsEnabled = tmp; + + rv = docShell->SetAllowJavascript(false); + NS_ENSURE_SUCCESS(rv, rv); + + // Disable plugins in this document: + mPluginsEnabled = docShell->PluginsAllowedInCurrentDoc(); + + rv = docShell->SetAllowPlugins(false); + NS_ENSURE_SUCCESS(rv, rv); + + mDisabledJSAndPlugins = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsEditingSession::RestoreJSAndPlugins(mozIDOMWindowProxy* aWindow) +{ + if (!mDisabledJSAndPlugins) { + return NS_OK; + } + + mDisabledJSAndPlugins = false; + + NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE); + nsIDocShell *docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsresult rv = docShell->SetAllowJavascript(mScriptsEnabled); + NS_ENSURE_SUCCESS(rv, rv); + + // Disable plugins in this document: + return docShell->SetAllowPlugins(mPluginsEnabled); +} + +NS_IMETHODIMP +nsEditingSession::GetJsAndPluginsDisabled(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mDisabledJSAndPlugins; + return NS_OK; +} + +/*--------------------------------------------------------------------------- + + WindowIsEditable + + boolean windowIsEditable (in nsIDOMWindow aWindow); +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::WindowIsEditable(mozIDOMWindowProxy* aWindow, + bool *outIsEditable) +{ + NS_ENSURE_STATE(aWindow); + nsCOMPtr<nsIDocShell> docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell(); + NS_ENSURE_STATE(docShell); + + return docShell->GetEditable(outIsEditable); +} + + +// These are MIME types that are automatically parsed as "text/plain" +// and thus we can edit them as plaintext +// Note: in older versions, we attempted to convert the mimetype of +// the network channel for these and "text/xml" to "text/plain", +// but further investigation reveals that strategy doesn't work +const char* const gSupportedTextTypes[] = { + "text/plain", + "text/css", + "text/rdf", + "text/xsl", + "text/javascript", // obsolete type + "text/ecmascript", // obsolete type + "application/javascript", + "application/ecmascript", + "application/x-javascript", // obsolete type + "text/xul", // obsolete type + "application/vnd.mozilla.xul+xml", + nullptr // IMPORTANT! Null must be at end +}; + +bool +IsSupportedTextType(const char* aMIMEType) +{ + NS_ENSURE_TRUE(aMIMEType, false); + + for (size_t i = 0; gSupportedTextTypes[i]; ++i) { + if (!strcmp(gSupportedTextTypes[i], aMIMEType)) { + return true; + } + } + + return false; +} + +/*--------------------------------------------------------------------------- + + SetupEditorOnWindow + + nsIEditor setupEditorOnWindow (in nsIDOMWindow aWindow); +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::SetupEditorOnWindow(mozIDOMWindowProxy* aWindow) +{ + mDoneSetup = true; + + NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE); + auto* window = nsPIDOMWindowOuter::From(aWindow); + + nsresult rv; + + //MIME CHECKING + //must get the content type + // Note: the doc gets this from the network channel during StartPageLoad, + // so we don't have to get it from there ourselves + nsAutoCString mimeCType; + + //then lets check the mime type + if (nsCOMPtr<nsIDocument> doc = window->GetDoc()) { + nsAutoString mimeType; + if (NS_SUCCEEDED(doc->GetContentType(mimeType))) + AppendUTF16toUTF8(mimeType, mimeCType); + + if (IsSupportedTextType(mimeCType.get())) { + mEditorType.AssignLiteral("text"); + mimeCType = "text/plain"; + } else if (!mimeCType.EqualsLiteral("text/html") && + !mimeCType.EqualsLiteral("application/xhtml+xml")) { + // Neither an acceptable text or html type. + mEditorStatus = eEditorErrorCantEditMimeType; + + // Turn editor into HTML -- we will load blank page later + mEditorType.AssignLiteral("html"); + mimeCType.AssignLiteral("text/html"); + } + + // Flush out frame construction to make sure that the subframe's + // presshell is set up if it needs to be. + nsCOMPtr<nsIDocument> document = do_QueryInterface(doc); + if (document) { + document->FlushPendingNotifications(Flush_Frames); + if (mMakeWholeDocumentEditable) { + document->SetEditableFlag(true); + nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(document); + if (htmlDocument) { + // Enable usage of the execCommand API + htmlDocument->SetEditingState(nsIHTMLDocument::eDesignMode); + } + } + } + } + bool needHTMLController = false; + + const char *classString = "@mozilla.org/editor/htmleditor;1"; + if (mEditorType.EqualsLiteral("textmail")) { + mEditorFlags = nsIPlaintextEditor::eEditorPlaintextMask | + nsIPlaintextEditor::eEditorEnableWrapHackMask | + nsIPlaintextEditor::eEditorMailMask; + } else if (mEditorType.EqualsLiteral("text")) { + mEditorFlags = nsIPlaintextEditor::eEditorPlaintextMask | + nsIPlaintextEditor::eEditorEnableWrapHackMask; + } else if (mEditorType.EqualsLiteral("htmlmail")) { + if (mimeCType.EqualsLiteral("text/html")) { + needHTMLController = true; + mEditorFlags = nsIPlaintextEditor::eEditorMailMask; + } else { + // Set the flags back to textplain. + mEditorFlags = nsIPlaintextEditor::eEditorPlaintextMask | + nsIPlaintextEditor::eEditorEnableWrapHackMask; + } + } else { + // Defaulted to html + needHTMLController = true; + } + + if (mInteractive) { + mEditorFlags |= nsIPlaintextEditor::eEditorAllowInteraction; + } + + // make the UI state maintainer + mStateMaintainer = new nsComposerCommandsUpdater(); + + // now init the state maintainer + // This allows notification of error state + // even if we don't create an editor + rv = mStateMaintainer->Init(window); + NS_ENSURE_SUCCESS(rv, rv); + + if (mEditorStatus != eEditorCreationInProgress) { + mStateMaintainer->NotifyDocumentCreated(); + return NS_ERROR_FAILURE; + } + + // Create editor and do other things + // only if we haven't found some error above, + nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + if (!mInteractive) { + // Disable animation of images in this document: + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + mImageAnimationMode = presContext->ImageAnimationMode(); + presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode); + } + + // Hide selection changes during initialization, in order to hide this + // from web pages. + RefPtr<nsFrameSelection> fs = presShell->FrameSelection(); + NS_ENSURE_TRUE(fs, NS_ERROR_FAILURE); + mozilla::dom::AutoHideSelectionChanges hideSelectionChanges(fs); + + // create and set editor + // Try to reuse an existing editor + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mExistingEditor); + if (editor) { + editor->PreDestroy(false); + } else { + editor = do_CreateInstance(classString, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mExistingEditor = do_GetWeakReference(editor); + } + // set the editor on the docShell. The docShell now owns it. + rv = docShell->SetEditor(editor); + NS_ENSURE_SUCCESS(rv, rv); + + // setup the HTML editor command controller + if (needHTMLController) { + // The third controller takes an nsIEditor as the context + rv = SetupEditorCommandController("@mozilla.org/editor/htmleditorcontroller;1", + aWindow, editor, + &mHTMLCommandControllerId); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Set mimetype on editor + rv = editor->SetContentsMIMEType(mimeCType.get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContentViewer> contentViewer; + rv = docShell->GetContentViewer(getter_AddRefs(contentViewer)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(contentViewer, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMDocument> domDoc; + rv = contentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); + + // Set up as a doc state listener + // Important! We must have this to broadcast the "obs_documentCreated" message + rv = editor->AddDocumentStateListener(mStateMaintainer); + NS_ENSURE_SUCCESS(rv, rv); + + rv = editor->Init(domDoc, nullptr /* root content */, + nullptr, mEditorFlags, EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISelection> selection; + editor->GetSelection(getter_AddRefs(selection)); + nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection); + NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE); + + rv = selPriv->AddSelectionListener(mStateMaintainer); + NS_ENSURE_SUCCESS(rv, rv); + + // and as a transaction listener + nsCOMPtr<nsITransactionManager> txnMgr; + editor->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) { + txnMgr->AddListener(mStateMaintainer); + } + + // Set context on all controllers to be the editor + rv = SetEditorOnControllers(aWindow, editor); + NS_ENSURE_SUCCESS(rv, rv); + + // Everything went fine! + mEditorStatus = eEditorOK; + + // This will trigger documentCreation notification + return editor->PostCreate(); +} + +// Removes all listeners and controllers from aWindow and aEditor. +void +nsEditingSession::RemoveListenersAndControllers(nsPIDOMWindowOuter* aWindow, + nsIEditor *aEditor) +{ + if (!mStateMaintainer || !aEditor) { + return; + } + + // Remove all the listeners + nsCOMPtr<nsISelection> selection; + aEditor->GetSelection(getter_AddRefs(selection)); + nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection); + if (selPriv) + selPriv->RemoveSelectionListener(mStateMaintainer); + + aEditor->RemoveDocumentStateListener(mStateMaintainer); + + nsCOMPtr<nsITransactionManager> txnMgr; + aEditor->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) { + txnMgr->RemoveListener(mStateMaintainer); + } + + // Remove editor controllers from the window now that we're not + // editing in that window any more. + RemoveEditorControllers(aWindow); +} + +/*--------------------------------------------------------------------------- + + TearDownEditorOnWindow + + void tearDownEditorOnWindow (in nsIDOMWindow aWindow); +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::TearDownEditorOnWindow(mozIDOMWindowProxy *aWindow) +{ + if (!mDoneSetup) { + return NS_OK; + } + + NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER); + + nsresult rv; + + // Kill any existing reload timer + if (mLoadBlankDocTimer) { + mLoadBlankDocTimer->Cancel(); + mLoadBlankDocTimer = nullptr; + } + + mDoneSetup = false; + + // Check if we're turning off editing (from contentEditable or designMode). + auto* window = nsPIDOMWindowOuter::From(aWindow); + + nsCOMPtr<nsIDocument> doc = window->GetDoc(); + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc); + bool stopEditing = htmlDoc && htmlDoc->IsEditingOn(); + if (stopEditing) { + RemoveWebProgressListener(window); + } + + nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); + NS_ENSURE_STATE(docShell); + + nsCOMPtr<nsIEditor> editor; + rv = docShell->GetEditor(getter_AddRefs(editor)); + NS_ENSURE_SUCCESS(rv, rv); + + if (stopEditing) { + htmlDoc->TearingDownEditor(editor); + } + + if (mStateMaintainer && editor) { + // Null out the editor on the controllers first to prevent their weak + // references from pointing to a destroyed editor. + SetEditorOnControllers(aWindow, nullptr); + } + + // Null out the editor on the docShell to trigger PreDestroy which + // needs to happen before document state listeners are removed below. + docShell->SetEditor(nullptr); + + RemoveListenersAndControllers(window, editor); + + if (stopEditing) { + // Make things the way they were before we started editing. + RestoreJSAndPlugins(aWindow); + RestoreAnimationMode(window); + + if (mMakeWholeDocumentEditable) { + doc->SetEditableFlag(false); + nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(doc); + if (htmlDocument) { + htmlDocument->SetEditingState(nsIHTMLDocument::eOff); + } + } + } + + return rv; +} + +/*--------------------------------------------------------------------------- + + GetEditorForFrame + + nsIEditor getEditorForFrame (in nsIDOMWindow aWindow); +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::GetEditorForWindow(mozIDOMWindowProxy* aWindow, + nsIEditor **outEditor) +{ + NS_ENSURE_STATE(aWindow); + nsCOMPtr<nsIDocShell> docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell(); + NS_ENSURE_STATE(docShell); + + return docShell->GetEditor(outEditor); +} + +/*--------------------------------------------------------------------------- + + OnStateChange + +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::OnStateChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t aStateFlags, nsresult aStatus) +{ + +#ifdef NOISY_DOC_LOADING + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); + if (channel) { + nsAutoCString contentType; + channel->GetContentType(contentType); + if (!contentType.IsEmpty()) { + printf(" ++++++ MIMETYPE = %s\n", contentType.get()); + } + } +#endif + + // + // A Request has started... + // + if (aStateFlags & nsIWebProgressListener::STATE_START) { +#ifdef NOISY_DOC_LOADING + { + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); + if (channel) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri) { + nsXPIDLCString spec; + uri->GetSpec(spec); + printf(" **** STATE_START: CHANNEL URI=%s, flags=%x\n", + spec.get(), aStateFlags); + } + } else { + printf(" STATE_START: NO CHANNEL flags=%x\n", aStateFlags); + } + } +#endif + // Page level notification... + if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) { + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); + StartPageLoad(channel); +#ifdef NOISY_DOC_LOADING + printf("STATE_START & STATE_IS_NETWORK flags=%x\n", aStateFlags); +#endif + } + + // Document level notification... + if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && + !(aStateFlags & nsIWebProgressListener::STATE_RESTORING)) { +#ifdef NOISY_DOC_LOADING + printf("STATE_START & STATE_IS_DOCUMENT flags=%x\n", aStateFlags); +#endif + + bool progressIsForTargetDocument = + IsProgressForTargetDocument(aWebProgress); + + if (progressIsForTargetDocument) { + nsCOMPtr<mozIDOMWindowProxy> window; + aWebProgress->GetDOMWindow(getter_AddRefs(window)); + + auto* piWindow = nsPIDOMWindowOuter::From(window); + nsCOMPtr<nsIDocument> doc = piWindow->GetDoc(); + + nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(doc)); + + if (htmlDoc && htmlDoc->IsWriting()) { + nsCOMPtr<nsIDOMHTMLDocument> htmlDomDoc = do_QueryInterface(doc); + nsAutoString designMode; + htmlDomDoc->GetDesignMode(designMode); + + if (designMode.EqualsLiteral("on")) { + // This notification is for data coming in through + // document.open/write/close(), ignore it. + + return NS_OK; + } + } + + mCanCreateEditor = true; + StartDocumentLoad(aWebProgress, progressIsForTargetDocument); + } + } + } + // + // A Request is being processed + // + else if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) { + if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) { + // document transfer started + } + } + // + // Got a redirection + // + else if (aStateFlags & nsIWebProgressListener::STATE_REDIRECTING) { + if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) { + // got a redirect + } + } + // + // A network or document Request has finished... + // + else if (aStateFlags & nsIWebProgressListener::STATE_STOP) { +#ifdef NOISY_DOC_LOADING + { + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); + if (channel) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri) { + nsXPIDLCString spec; + uri->GetSpec(spec); + printf(" **** STATE_STOP: CHANNEL URI=%s, flags=%x\n", + spec.get(), aStateFlags); + } + } else { + printf(" STATE_STOP: NO CHANNEL flags=%x\n", aStateFlags); + } + } +#endif + + // Document level notification... + if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + EndDocumentLoad(aWebProgress, channel, aStatus, + IsProgressForTargetDocument(aWebProgress)); +#ifdef NOISY_DOC_LOADING + printf("STATE_STOP & STATE_IS_DOCUMENT flags=%x\n", aStateFlags); +#endif + } + + // Page level notification... + if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + (void)EndPageLoad(aWebProgress, channel, aStatus); +#ifdef NOISY_DOC_LOADING + printf("STATE_STOP & STATE_IS_NETWORK flags=%x\n", aStateFlags); +#endif + } + } + + return NS_OK; +} + +/*--------------------------------------------------------------------------- + + OnProgressChange + +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::OnProgressChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +/*--------------------------------------------------------------------------- + + OnLocationChange + +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::OnLocationChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, nsIURI *aURI, + uint32_t aFlags) +{ + nsCOMPtr<mozIDOMWindowProxy> domWindow; + nsresult rv = aWebProgress->GetDOMWindow(getter_AddRefs(domWindow)); + NS_ENSURE_SUCCESS(rv, rv); + + auto* piWindow = nsPIDOMWindowOuter::From(domWindow); + + nsCOMPtr<nsIDocument> doc = piWindow->GetDoc(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + doc->SetDocumentURI(aURI); + + // Notify the location-changed observer that + // the document URL has changed + nsIDocShell *docShell = piWindow->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager(); + nsCOMPtr<nsPICommandUpdater> commandUpdater = + do_QueryInterface(commandManager); + NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE); + + return commandUpdater->CommandStatusChanged("obs_documentLocationChanged"); +} + +/*--------------------------------------------------------------------------- + + OnStatusChange + +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::OnStatusChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + nsresult aStatus, + const char16_t *aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +/*--------------------------------------------------------------------------- + + OnSecurityChange + +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, uint32_t state) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + + +/*--------------------------------------------------------------------------- + + IsProgressForTargetDocument + + Check that this notification is for our document. +----------------------------------------------------------------------------*/ + +bool +nsEditingSession::IsProgressForTargetDocument(nsIWebProgress *aWebProgress) +{ + nsCOMPtr<nsIWebProgress> editedWebProgress = do_QueryReferent(mDocShell); + return editedWebProgress == aWebProgress; +} + + +/*--------------------------------------------------------------------------- + + GetEditorStatus + + Called during GetCommandStateParams("obs_documentCreated"...) + to determine if editor was created and document + was loaded successfully +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::GetEditorStatus(uint32_t *aStatus) +{ + NS_ENSURE_ARG_POINTER(aStatus); + *aStatus = mEditorStatus; + return NS_OK; +} + +/*--------------------------------------------------------------------------- + + StartDocumentLoad + + Called on start of load in a single frame +----------------------------------------------------------------------------*/ +nsresult +nsEditingSession::StartDocumentLoad(nsIWebProgress *aWebProgress, + bool aIsToBeMadeEditable) +{ +#ifdef NOISY_DOC_LOADING + printf("======= StartDocumentLoad ========\n"); +#endif + + NS_ENSURE_ARG_POINTER(aWebProgress); + + if (aIsToBeMadeEditable) { + mEditorStatus = eEditorCreationInProgress; + } + + return NS_OK; +} + +/*--------------------------------------------------------------------------- + + EndDocumentLoad + + Called on end of load in a single frame +----------------------------------------------------------------------------*/ +nsresult +nsEditingSession::EndDocumentLoad(nsIWebProgress *aWebProgress, + nsIChannel* aChannel, nsresult aStatus, + bool aIsToBeMadeEditable) +{ + NS_ENSURE_ARG_POINTER(aWebProgress); + +#ifdef NOISY_DOC_LOADING + printf("======= EndDocumentLoad ========\n"); + printf("with status %d, ", aStatus); + nsCOMPtr<nsIURI> uri; + nsXPIDLCString spec; + if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) { + uri->GetSpec(spec); + printf(" uri %s\n", spec.get()); + } +#endif + + // We want to call the base class EndDocumentLoad, + // but avoid some of the stuff + // that nsDocShell does (need to refactor). + + // OK, time to make an editor on this document + nsCOMPtr<mozIDOMWindowProxy> domWindow; + aWebProgress->GetDOMWindow(getter_AddRefs(domWindow)); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + + // Set the error state -- we will create an editor + // anyway and load empty doc later + if (aIsToBeMadeEditable && aStatus == NS_ERROR_FILE_NOT_FOUND) { + mEditorStatus = eEditorErrorFileNotFound; + } + + nsIDocShell *docShell = nsPIDOMWindowOuter::From(domWindow)->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); // better error handling? + + // cancel refresh from meta tags + // we need to make sure that all pages in editor (whether editable or not) + // can't refresh contents being edited + nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell); + if (refreshURI) { + refreshURI->CancelRefreshURITimers(); + } + + nsresult rv = NS_OK; + + // did someone set the flag to make this shell editable? + if (aIsToBeMadeEditable && mCanCreateEditor) { + bool makeEditable; + docShell->GetEditable(&makeEditable); + + if (makeEditable) { + // To keep pre Gecko 1.9 behavior, setup editor always when + // mMakeWholeDocumentEditable. + bool needsSetup = false; + if (mMakeWholeDocumentEditable) { + needsSetup = true; + } else { + // do we already have an editor here? + nsCOMPtr<nsIEditor> editor; + rv = docShell->GetEditor(getter_AddRefs(editor)); + NS_ENSURE_SUCCESS(rv, rv); + + needsSetup = !editor; + } + + if (needsSetup) { + mCanCreateEditor = false; + rv = SetupEditorOnWindow(domWindow); + if (NS_FAILED(rv)) { + // If we had an error, setup timer to load a blank page later + if (mLoadBlankDocTimer) { + // Must cancel previous timer? + mLoadBlankDocTimer->Cancel(); + mLoadBlankDocTimer = nullptr; + } + + mLoadBlankDocTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mEditorStatus = eEditorCreationInProgress; + mLoadBlankDocTimer->InitWithFuncCallback( + nsEditingSession::TimerCallback, + static_cast<void*> (mDocShell.get()), + 10, nsITimer::TYPE_ONE_SHOT); + } + } + } + } + return rv; +} + + +void +nsEditingSession::TimerCallback(nsITimer* aTimer, void* aClosure) +{ + nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(static_cast<nsIWeakReference*> (aClosure)); + if (docShell) { + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell)); + if (webNav) { + webNav->LoadURI(u"about:blank", 0, nullptr, nullptr, nullptr); + } + } +} + +/*--------------------------------------------------------------------------- + + StartPageLoad + + Called on start load of the entire page (incl. subframes) +----------------------------------------------------------------------------*/ +nsresult +nsEditingSession::StartPageLoad(nsIChannel *aChannel) +{ +#ifdef NOISY_DOC_LOADING + printf("======= StartPageLoad ========\n"); +#endif + return NS_OK; +} + +/*--------------------------------------------------------------------------- + + EndPageLoad + + Called on end load of the entire page (incl. subframes) +----------------------------------------------------------------------------*/ +nsresult +nsEditingSession::EndPageLoad(nsIWebProgress *aWebProgress, + nsIChannel* aChannel, nsresult aStatus) +{ +#ifdef NOISY_DOC_LOADING + printf("======= EndPageLoad ========\n"); + printf(" with status %d, ", aStatus); + nsCOMPtr<nsIURI> uri; + nsXPIDLCString spec; + if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) { + uri->GetSpec(spec); + printf("uri %s\n", spec.get()); + } + + nsAutoCString contentType; + aChannel->GetContentType(contentType); + if (!contentType.IsEmpty()) { + printf(" flags = %d, status = %d, MIMETYPE = %s\n", + mEditorFlags, mEditorStatus, contentType.get()); + } +#endif + + // Set the error state -- we will create an editor anyway + // and load empty doc later + if (aStatus == NS_ERROR_FILE_NOT_FOUND) { + mEditorStatus = eEditorErrorFileNotFound; + } + + nsCOMPtr<mozIDOMWindowProxy> domWindow; + aWebProgress->GetDOMWindow(getter_AddRefs(domWindow)); + + nsIDocShell *docShell = + domWindow ? nsPIDOMWindowOuter::From(domWindow)->GetDocShell() : nullptr; + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + // cancel refresh from meta tags + // we need to make sure that all pages in editor (whether editable or not) + // can't refresh contents being edited + nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell); + if (refreshURI) { + refreshURI->CancelRefreshURITimers(); + } + +#if 0 + // Shouldn't we do this when we want to edit sub-frames? + return MakeWindowEditable(domWindow, "html", false, mInteractive); +#else + return NS_OK; +#endif +} + +/*--------------------------------------------------------------------------- + + PrepareForEditing + + Set up this editing session for one or more editors +----------------------------------------------------------------------------*/ +nsresult +nsEditingSession::PrepareForEditing(nsPIDOMWindowOuter* aWindow) +{ + if (mProgressListenerRegistered) { + return NS_OK; + } + + nsIDocShell *docShell = aWindow ? aWindow->GetDocShell() : nullptr; + + // register callback + nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell); + NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE); + + nsresult rv = + webProgress->AddProgressListener(this, + (nsIWebProgress::NOTIFY_STATE_NETWORK | + nsIWebProgress::NOTIFY_STATE_DOCUMENT | + nsIWebProgress::NOTIFY_LOCATION)); + + mProgressListenerRegistered = NS_SUCCEEDED(rv); + + return rv; +} + +/*--------------------------------------------------------------------------- + + SetupEditorCommandController + + Create a command controller, append to controllers, + get and return the controller ID, and set the context +----------------------------------------------------------------------------*/ +nsresult +nsEditingSession::SetupEditorCommandController( + const char *aControllerClassName, + mozIDOMWindowProxy *aWindow, + nsISupports *aContext, + uint32_t *aControllerId) +{ + NS_ENSURE_ARG_POINTER(aControllerClassName); + NS_ENSURE_ARG_POINTER(aWindow); + NS_ENSURE_ARG_POINTER(aContext); + NS_ENSURE_ARG_POINTER(aControllerId); + + auto* piWindow = nsPIDOMWindowOuter::From(aWindow); + MOZ_ASSERT(piWindow); + + nsCOMPtr<nsIControllers> controllers; + nsresult rv = piWindow->GetControllers(getter_AddRefs(controllers)); + NS_ENSURE_SUCCESS(rv, rv); + + // We only have to create each singleton controller once + // We know this has happened once we have a controllerId value + if (!*aControllerId) { + nsCOMPtr<nsIController> controller; + controller = do_CreateInstance(aControllerClassName, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // We must insert at head of the list to be sure our + // controller is found before other implementations + // (e.g., not-implemented versions by browser) + rv = controllers->InsertControllerAt(0, controller); + NS_ENSURE_SUCCESS(rv, rv); + + // Remember the ID for the controller + rv = controllers->GetControllerId(controller, aControllerId); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Set the context + return SetContextOnControllerById(controllers, aContext, *aControllerId); +} + +/*--------------------------------------------------------------------------- + + SetEditorOnControllers + + Set the editor on the controller(s) for this window +----------------------------------------------------------------------------*/ +NS_IMETHODIMP +nsEditingSession::SetEditorOnControllers(mozIDOMWindowProxy* aWindow, + nsIEditor* aEditor) +{ + NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER); + + auto* piWindow = nsPIDOMWindowOuter::From(aWindow); + + nsCOMPtr<nsIControllers> controllers; + nsresult rv = piWindow->GetControllers(getter_AddRefs(controllers)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> editorAsISupports = do_QueryInterface(aEditor); + if (mBaseCommandControllerId) { + rv = SetContextOnControllerById(controllers, editorAsISupports, + mBaseCommandControllerId); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mDocStateControllerId) { + rv = SetContextOnControllerById(controllers, editorAsISupports, + mDocStateControllerId); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mHTMLCommandControllerId) { + rv = SetContextOnControllerById(controllers, editorAsISupports, + mHTMLCommandControllerId); + } + + return rv; +} + +nsresult +nsEditingSession::SetContextOnControllerById(nsIControllers* aControllers, + nsISupports* aContext, + uint32_t aID) +{ + NS_ENSURE_ARG_POINTER(aControllers); + + // aContext can be null (when destroying editor) + nsCOMPtr<nsIController> controller; + aControllers->GetControllerById(aID, getter_AddRefs(controller)); + + // ok with nil controller + nsCOMPtr<nsIControllerContext> editorController = + do_QueryInterface(controller); + NS_ENSURE_TRUE(editorController, NS_ERROR_FAILURE); + + return editorController->SetCommandContext(aContext); +} + +void +nsEditingSession::RemoveEditorControllers(nsPIDOMWindowOuter* aWindow) +{ + // Remove editor controllers from the aWindow, call when we're + // tearing down/detaching editor. + + nsCOMPtr<nsIControllers> controllers; + if (aWindow) { + aWindow->GetControllers(getter_AddRefs(controllers)); + } + + if (controllers) { + nsCOMPtr<nsIController> controller; + if (mBaseCommandControllerId) { + controllers->GetControllerById(mBaseCommandControllerId, + getter_AddRefs(controller)); + if (controller) { + controllers->RemoveController(controller); + } + } + + if (mDocStateControllerId) { + controllers->GetControllerById(mDocStateControllerId, + getter_AddRefs(controller)); + if (controller) { + controllers->RemoveController(controller); + } + } + + if (mHTMLCommandControllerId) { + controllers->GetControllerById(mHTMLCommandControllerId, + getter_AddRefs(controller)); + if (controller) { + controllers->RemoveController(controller); + } + } + } + + // Clear IDs to trigger creation of new controllers. + mBaseCommandControllerId = 0; + mDocStateControllerId = 0; + mHTMLCommandControllerId = 0; +} + +void +nsEditingSession::RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow) +{ + nsIDocShell *docShell = aWindow ? aWindow->GetDocShell() : nullptr; + nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell); + if (webProgress) { + webProgress->RemoveProgressListener(this); + mProgressListenerRegistered = false; + } +} + +void +nsEditingSession::RestoreAnimationMode(nsPIDOMWindowOuter* aWindow) +{ + if (mInteractive) { + return; + } + + nsCOMPtr<nsIDocShell> docShell = aWindow ? aWindow->GetDocShell() : nullptr; + NS_ENSURE_TRUE_VOID(docShell); + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + NS_ENSURE_TRUE_VOID(presShell); + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE_VOID(presContext); + + presContext->SetImageAnimationMode(mImageAnimationMode); +} + +nsresult +nsEditingSession::DetachFromWindow(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_TRUE(mDoneSetup, NS_OK); + + NS_ASSERTION(mStateMaintainer, "mStateMaintainer should exist."); + + // Kill any existing reload timer + if (mLoadBlankDocTimer) { + mLoadBlankDocTimer->Cancel(); + mLoadBlankDocTimer = nullptr; + } + + auto* window = nsPIDOMWindowOuter::From(aWindow); + + // Remove controllers, webprogress listener, and otherwise + // make things the way they were before we started editing. + RemoveEditorControllers(window); + RemoveWebProgressListener(window); + RestoreJSAndPlugins(aWindow); + RestoreAnimationMode(window); + + // Kill our weak reference to our original window, in case + // it changes on restore, or otherwise dies. + mDocShell = nullptr; + + return NS_OK; +} + +nsresult +nsEditingSession::ReattachToWindow(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_TRUE(mDoneSetup, NS_OK); + NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE); + + NS_ASSERTION(mStateMaintainer, "mStateMaintainer should exist."); + + // Imitate nsEditorDocShell::MakeEditable() to reattach the + // old editor ot the window. + nsresult rv; + + auto* window = nsPIDOMWindowOuter::From(aWindow); + nsIDocShell *docShell = window->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + mDocShell = do_GetWeakReference(docShell); + + // Disable plugins. + if (!mInteractive) { + rv = DisableJSAndPlugins(aWindow); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Tells embedder that startup is in progress. + mEditorStatus = eEditorCreationInProgress; + + // Adds back web progress listener. + rv = PrepareForEditing(window); + NS_ENSURE_SUCCESS(rv, rv); + + // Setup the command controllers again. + rv = SetupEditorCommandController("@mozilla.org/editor/editingcontroller;1", + aWindow, + static_cast<nsIEditingSession*>(this), + &mBaseCommandControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetupEditorCommandController("@mozilla.org/editor/editordocstatecontroller;1", + aWindow, + static_cast<nsIEditingSession*>(this), + &mDocStateControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + if (mStateMaintainer) { + mStateMaintainer->Init(window); + } + + // Get editor + nsCOMPtr<nsIEditor> editor; + rv = GetEditorForWindow(aWindow, getter_AddRefs(editor)); + NS_ENSURE_TRUE(editor, NS_ERROR_FAILURE); + + if (!mInteractive) { + // Disable animation of images in this document: + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + mImageAnimationMode = presContext->ImageAnimationMode(); + presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode); + } + + // The third controller takes an nsIEditor as the context + rv = SetupEditorCommandController("@mozilla.org/editor/htmleditorcontroller;1", + aWindow, editor, + &mHTMLCommandControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + // Set context on all controllers to be the editor + rv = SetEditorOnControllers(aWindow, editor); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef DEBUG + { + bool isEditable; + rv = WindowIsEditable(aWindow, &isEditable); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(isEditable, "Window is not editable after reattaching editor."); + } +#endif // DEBUG + + return NS_OK; +} diff --git a/editor/composer/nsEditingSession.h b/editor/composer/nsEditingSession.h new file mode 100644 index 000000000..6772d3a96 --- /dev/null +++ b/editor/composer/nsEditingSession.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsEditingSession_h__ +#define nsEditingSession_h__ + + +#ifndef nsWeakReference_h__ +#include "nsWeakReference.h" // for nsSupportsWeakReference, etc +#endif + +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr +#include "nsWeakReference.h" // for nsSupportsWeakReference, etc +#include "nscore.h" // for nsresult + +#ifndef __gen_nsIWebProgressListener_h__ +#include "nsIWebProgressListener.h" +#endif + +#ifndef __gen_nsIEditingSession_h__ +#include "nsIEditingSession.h" // for NS_DECL_NSIEDITINGSESSION, etc +#endif + +#include "nsString.h" // for nsCString + +class mozIDOMWindowProxy; +class nsIDOMWindow; +class nsISupports; +class nsITimer; + +#define NS_EDITINGSESSION_CID \ +{ 0xbc26ff01, 0xf2bd, 0x11d4, { 0xa7, 0x3c, 0xe5, 0xa4, 0xb5, 0xa8, 0xbd, 0xfc } } + + +class nsComposerCommandsUpdater; +class nsIChannel; +class nsIControllers; +class nsIDocShell; +class nsIEditor; +class nsIWebProgress; + +class nsEditingSession final : public nsIEditingSession, + public nsIWebProgressListener, + public nsSupportsWeakReference +{ +public: + + nsEditingSession(); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIWebProgressListener + NS_DECL_NSIWEBPROGRESSLISTENER + + // nsIEditingSession + NS_DECL_NSIEDITINGSESSION + +protected: + virtual ~nsEditingSession(); + + nsresult SetupEditorCommandController(const char *aControllerClassName, + mozIDOMWindowProxy* aWindow, + nsISupports *aContext, + uint32_t *aControllerId); + + nsresult SetContextOnControllerById(nsIControllers* aControllers, + nsISupports* aContext, + uint32_t aID); + + nsresult PrepareForEditing(nsPIDOMWindowOuter* aWindow); + + static void TimerCallback(nsITimer *aTimer, void *aClosure); + nsCOMPtr<nsITimer> mLoadBlankDocTimer; + + // progress load stuff + nsresult StartDocumentLoad(nsIWebProgress *aWebProgress, + bool isToBeMadeEditable); + nsresult EndDocumentLoad(nsIWebProgress *aWebProgress, + nsIChannel* aChannel, nsresult aStatus, + bool isToBeMadeEditable); + nsresult StartPageLoad(nsIChannel *aChannel); + nsresult EndPageLoad(nsIWebProgress *aWebProgress, + nsIChannel* aChannel, nsresult aStatus); + + bool IsProgressForTargetDocument(nsIWebProgress *aWebProgress); + + void RemoveEditorControllers(nsPIDOMWindowOuter* aWindow); + void RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow); + void RestoreAnimationMode(nsPIDOMWindowOuter* aWindow); + void RemoveListenersAndControllers(nsPIDOMWindowOuter* aWindow, + nsIEditor *aEditor); + +protected: + + bool mDoneSetup; // have we prepared for editing yet? + + // Used to prevent double creation of editor because nsIWebProgressListener + // receives a STATE_STOP notification before the STATE_START + // for our document, so we wait for the STATE_START, then STATE_STOP + // before creating an editor + bool mCanCreateEditor; + + bool mInteractive; + bool mMakeWholeDocumentEditable; + + bool mDisabledJSAndPlugins; + + // True if scripts were enabled before the editor turned scripts + // off, otherwise false. + bool mScriptsEnabled; + + // True if plugins were enabled before the editor turned plugins + // off, otherwise false. + bool mPluginsEnabled; + + bool mProgressListenerRegistered; + + // The image animation mode before it was turned off. + uint16_t mImageAnimationMode; + + // THE REMAINING MEMBER VARIABLES WILL BECOME A SET WHEN WE EDIT + // MORE THAN ONE EDITOR PER EDITING SESSION + RefPtr<nsComposerCommandsUpdater> mStateMaintainer; + + // Save the editor type so we can create the editor after loading uri + nsCString mEditorType; + uint32_t mEditorFlags; + uint32_t mEditorStatus; + uint32_t mBaseCommandControllerId; + uint32_t mDocStateControllerId; + uint32_t mHTMLCommandControllerId; + + // Make sure the docshell we use is safe + nsWeakPtr mDocShell; + + // See if we can reuse an existing editor + nsWeakPtr mExistingEditor; +}; + + + +#endif // nsEditingSession_h__ diff --git a/editor/composer/nsEditorSpellCheck.cpp b/editor/composer/nsEditorSpellCheck.cpp new file mode 100644 index 000000000..7cae48c1f --- /dev/null +++ b/editor/composer/nsEditorSpellCheck.cpp @@ -0,0 +1,1005 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdlib.h> // for getenv + +#include "mozilla/Attributes.h" // for final +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/Services.h" // for GetXULChromeRegistryService +#include "mozilla/dom/Element.h" // for Element +#include "mozilla/dom/Selection.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsAString.h" // for nsAString_internal::IsEmpty, etc +#include "nsComponentManagerUtils.h" // for do_CreateInstance +#include "nsDebug.h" // for NS_ENSURE_TRUE, etc +#include "nsDependentSubstring.h" // for Substring +#include "nsEditorSpellCheck.h" +#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc +#include "nsIChromeRegistry.h" // for nsIXULChromeRegistry +#include "nsIContent.h" // for nsIContent +#include "nsIContentPrefService.h" // for nsIContentPrefService, etc +#include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc +#include "nsIDOMDocument.h" // for nsIDOMDocument +#include "nsIDOMElement.h" // for nsIDOMElement +#include "nsIDocument.h" // for nsIDocument +#include "nsIEditor.h" // for nsIEditor +#include "nsIHTMLEditor.h" // for nsIHTMLEditor +#include "nsILoadContext.h" +#include "nsISelection.h" // for nsISelection +#include "nsISpellChecker.h" // for nsISpellChecker, etc +#include "nsISupportsBase.h" // for nsISupports +#include "nsISupportsUtils.h" // for NS_ADDREF +#include "nsITextServicesDocument.h" // for nsITextServicesDocument +#include "nsITextServicesFilter.h" // for nsITextServicesFilter +#include "nsIURI.h" // for nsIURI +#include "nsVariant.h" // for nsIWritableVariant, etc +#include "nsLiteralString.h" // for NS_LITERAL_STRING, etc +#include "nsMemory.h" // for nsMemory +#include "nsRange.h" +#include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc +#include "nsServiceManagerUtils.h" // for do_GetService +#include "nsString.h" // for nsAutoString, nsString, etc +#include "nsStringFwd.h" // for nsAFlatString +#include "nsStyleUtil.h" // for nsStyleUtil +#include "nsXULAppAPI.h" // for XRE_GetProcessType +#include "nsIPlaintextEditor.h" // for editor flags + +using namespace mozilla; +using namespace mozilla::dom; + +class UpdateDictionaryHolder { + private: + nsEditorSpellCheck* mSpellCheck; + public: + explicit UpdateDictionaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) { + if (mSpellCheck) { + mSpellCheck->BeginUpdateDictionary(); + } + } + ~UpdateDictionaryHolder() { + if (mSpellCheck) { + mSpellCheck->EndUpdateDictionary(); + } + } +}; + +#define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang") + +/** + * Gets the URI of aEditor's document. + */ +static nsresult +GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI) +{ + NS_ENSURE_ARG_POINTER(aEditor); + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIDOMDocument> domDoc; + aEditor->GetDocument(getter_AddRefs(domDoc)); + NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI(); + NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE); + + *aURI = docUri; + NS_ADDREF(*aURI); + return NS_OK; +} + +static already_AddRefed<nsILoadContext> +GetLoadContext(nsIEditor* aEditor) +{ + nsCOMPtr<nsIDOMDocument> domDoc; + aEditor->GetDocument(getter_AddRefs(domDoc)); + NS_ENSURE_TRUE(domDoc, nullptr); + + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + NS_ENSURE_TRUE(doc, nullptr); + + nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext(); + return loadContext.forget(); +} + +/** + * Fetches the dictionary stored in content prefs and maintains state during the + * fetch, which is asynchronous. + */ +class DictionaryFetcher final : public nsIContentPrefCallback2 +{ +public: + NS_DECL_ISUPPORTS + + DictionaryFetcher(nsEditorSpellCheck* aSpellCheck, + nsIEditorSpellCheckCallback* aCallback, + uint32_t aGroup) + : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {} + + NS_IMETHOD Fetch(nsIEditor* aEditor); + + NS_IMETHOD HandleResult(nsIContentPref* aPref) override + { + nsCOMPtr<nsIVariant> value; + nsresult rv = aPref->GetValue(getter_AddRefs(value)); + NS_ENSURE_SUCCESS(rv, rv); + value->GetAsAString(mDictionary); + return NS_OK; + } + + NS_IMETHOD HandleCompletion(uint16_t reason) override + { + mSpellCheck->DictionaryFetched(this); + return NS_OK; + } + + NS_IMETHOD HandleError(nsresult error) override + { + return NS_OK; + } + + nsCOMPtr<nsIEditorSpellCheckCallback> mCallback; + uint32_t mGroup; + nsString mRootContentLang; + nsString mRootDocContentLang; + nsString mDictionary; + +private: + ~DictionaryFetcher() {} + + RefPtr<nsEditorSpellCheck> mSpellCheck; +}; +NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2) + +NS_IMETHODIMP +DictionaryFetcher::Fetch(nsIEditor* aEditor) +{ + NS_ENSURE_ARG_POINTER(aEditor); + + nsresult rv; + + nsCOMPtr<nsIURI> docUri; + rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString docUriSpec; + rv = docUri->GetSpec(docUriSpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContentPrefService2> contentPrefService = + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor); + rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec), + CPS_PREF_NAME, loadContext, + this); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/** + * Stores the current dictionary for aEditor's document URL. + */ +static nsresult +StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary) +{ + NS_ENSURE_ARG_POINTER(aEditor); + + nsresult rv; + + nsCOMPtr<nsIURI> docUri; + rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString docUriSpec; + rv = docUri->GetSpec(docUriSpec); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsVariant> prefValue = new nsVariant(); + prefValue->SetAsAString(aDictionary); + + nsCOMPtr<nsIContentPrefService2> contentPrefService = + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor); + return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec), + CPS_PREF_NAME, prefValue, loadContext, + nullptr); +} + +/** + * Forgets the current dictionary stored for aEditor's document URL. + */ +static nsresult +ClearCurrentDictionary(nsIEditor* aEditor) +{ + NS_ENSURE_ARG_POINTER(aEditor); + + nsresult rv; + + nsCOMPtr<nsIURI> docUri; + rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString docUriSpec; + rv = docUri->GetSpec(docUriSpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContentPrefService2> contentPrefService = + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor); + return contentPrefService->RemoveByDomainAndName( + NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr); +} + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck) + +NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck) + NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck, + mEditor, + mSpellChecker, + mTxtSrvFilter) + +nsEditorSpellCheck::nsEditorSpellCheck() + : mSuggestedWordIndex(0) + , mDictionaryIndex(0) + , mEditor(nullptr) + , mDictionaryFetcherGroup(0) + , mUpdateDictionaryRunning(false) +{ +} + +nsEditorSpellCheck::~nsEditorSpellCheck() +{ + // Make sure we blow the spellchecker away, just in + // case it hasn't been destroyed already. + mSpellChecker = nullptr; +} + +// The problem is that if the spell checker does not exist, we can not tell +// which dictionaries are installed. This function works around the problem, +// allowing callers to ask if we can spell check without actually doing so (and +// enabling or disabling UI as necessary). This just creates a spellcheck +// object if needed and asks it for the dictionary list. +NS_IMETHODIMP +nsEditorSpellCheck::CanSpellCheck(bool* _retval) +{ + nsresult rv; + nsCOMPtr<nsISpellChecker> spellChecker; + if (! mSpellChecker) { + spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } else { + spellChecker = mSpellChecker; + } + nsTArray<nsString> dictList; + rv = spellChecker->GetDictionaryList(&dictList); + NS_ENSURE_SUCCESS(rv, rv); + + *_retval = (dictList.Length() > 0); + return NS_OK; +} + +// Instances of this class can be used as either runnables or RAII helpers. +class CallbackCaller final : public Runnable +{ +public: + explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback) + : mCallback(aCallback) {} + + ~CallbackCaller() + { + Run(); + } + + NS_IMETHOD Run() override + { + if (mCallback) { + mCallback->EditorSpellCheckDone(); + mCallback = nullptr; + } + return NS_OK; + } + +private: + nsCOMPtr<nsIEditorSpellCheckCallback> mCallback; +}; + +NS_IMETHODIMP +nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback) +{ + NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER); + mEditor = aEditor; + + nsresult rv; + + // We can spell check with any editor type + nsCOMPtr<nsITextServicesDocument>tsDoc = + do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER); + + tsDoc->SetFilter(mTxtSrvFilter); + + // Pass the editor to the text services document + rv = tsDoc->InitWithEditor(aEditor); + NS_ENSURE_SUCCESS(rv, rv); + + if (aEnableSelectionChecking) { + // Find out if the section is collapsed or not. + // If it isn't, we want to spellcheck just the selection. + + nsCOMPtr<nsISelection> domSelection; + aEditor->GetSelection(getter_AddRefs(domSelection)); + if (NS_WARN_IF(!domSelection)) { + return NS_ERROR_FAILURE; + } + RefPtr<Selection> selection = domSelection->AsSelection(); + + int32_t count = 0; + + rv = selection->GetRangeCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count > 0) { + RefPtr<nsRange> range = selection->GetRangeAt(0); + NS_ENSURE_STATE(range); + + bool collapsed = false; + rv = range->GetCollapsed(&collapsed); + NS_ENSURE_SUCCESS(rv, rv); + + if (!collapsed) { + // We don't want to touch the range in the selection, + // so create a new copy of it. + + RefPtr<nsRange> rangeBounds = range->CloneRange(); + + // Make sure the new range spans complete words. + + rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds); + NS_ENSURE_SUCCESS(rv, rv); + + // Now tell the text services that you only want + // to iterate over the text in this range. + + rv = tsDoc->SetExtent(rangeBounds); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER); + + rv = mSpellChecker->SetDocument(tsDoc, true); + NS_ENSURE_SUCCESS(rv, rv); + + // do not fail if UpdateCurrentDictionary fails because this method may + // succeed later. + rv = UpdateCurrentDictionary(aCallback); + if (NS_FAILED(rv) && aCallback) { + // However, if it does fail, we still need to call the callback since we + // discard the failure. Do it asynchronously so that the caller is always + // guaranteed async behavior. + RefPtr<CallbackCaller> caller = new CallbackCaller(aCallback); + NS_ENSURE_STATE(caller); + rv = NS_DispatchToMainThread(caller); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + nsAutoString nextMisspelledWord; + + DeleteSuggestedWordList(); + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord, + &mSuggestedWordList); + + *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord); + return rv; +} + +NS_IMETHODIMP +nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord) +{ + nsAutoString word; + // XXX This is buggy if mSuggestedWordList.Length() is over INT32_MAX. + if (mSuggestedWordIndex < static_cast<int32_t>(mSuggestedWordList.Length())) { + *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]); + mSuggestedWordIndex++; + } else { + // A blank string signals that there are no more strings + *aSuggestedWord = ToNewUnicode(EmptyString()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord, + bool *aIsMisspelled) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + DeleteSuggestedWordList(); + return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), + aIsMisspelled, &mSuggestedWordList); +} + +NS_IMETHODIMP +nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord, + bool *aIsMisspelled) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), + aIsMisspelled, nullptr); +} + +NS_IMETHODIMP +nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord, + const char16_t *aReplaceWord, + bool allOccurrences) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + return mSpellChecker->Replace(nsDependentString(aMisspelledWord), + nsDependentString(aReplaceWord), allOccurrences); +} + +NS_IMETHODIMP +nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + return mSpellChecker->IgnoreAll(nsDependentString(aWord)); +} + +NS_IMETHODIMP +nsEditorSpellCheck::GetPersonalDictionary() +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + // We can spell check with any editor type + mDictionaryList.Clear(); + mDictionaryIndex = 0; + return mSpellChecker->GetPersonalDictionary(&mDictionaryList); +} + +NS_IMETHODIMP +nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord) +{ + // XXX This is buggy if mDictionaryList.Length() is over INT32_MAX. + if (mDictionaryIndex < static_cast<int32_t>(mDictionaryList.Length())) { + *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]); + mDictionaryIndex++; + } else { + // A blank string signals that there are no more strings + *aDictionaryWord = ToNewUnicode(EmptyString()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord)); +} + +NS_IMETHODIMP +nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord)); +} + +NS_IMETHODIMP +nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER); + + *aDictionaryList = 0; + *aCount = 0; + + nsTArray<nsString> dictList; + + nsresult rv = mSpellChecker->GetDictionaryList(&dictList); + + NS_ENSURE_SUCCESS(rv, rv); + + char16_t **tmpPtr = 0; + + if (dictList.IsEmpty()) { + // If there are no dictionaries, return an array containing + // one element and a count of one. + + tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *)); + + NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); + + *tmpPtr = 0; + *aDictionaryList = tmpPtr; + *aCount = 0; + + return NS_OK; + } + + tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *) * dictList.Length()); + + NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); + + *aDictionaryList = tmpPtr; + *aCount = dictList.Length(); + + for (uint32_t i = 0; i < *aCount; i++) { + tmpPtr[i] = ToNewUnicode(dictList[i]); + } + + return rv; +} + +NS_IMETHODIMP +nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + return mSpellChecker->GetCurrentDictionary(aDictionary); +} + +NS_IMETHODIMP +nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary) +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this; + + // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if + // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us, + // is on the stack. In other words: Only do this, if the user manually selected a + // dictionary to use. + if (!mUpdateDictionaryRunning) { + + // Ignore pending dictionary fetchers by increasing this number. + mDictionaryFetcherGroup++; + + uint32_t flags = 0; + mEditor->GetFlags(&flags); + if (!(flags & nsIPlaintextEditor::eEditorMailMask)) { + if (!aDictionary.IsEmpty() && (mPreferredLang.IsEmpty() || + !mPreferredLang.Equals(aDictionary, + nsCaseInsensitiveStringComparator()))) { + // When user sets dictionary manually, we store this value associated + // with editor url, if it doesn't match the document language exactly. + // For example on "en" sites, we need to store "en-GB", otherwise + // the language might jump back to en-US although the user explicitly + // chose otherwise. + StoreCurrentDictionary(mEditor, aDictionary); +#ifdef DEBUG_DICT + printf("***** Writing content preferences for |%s|\n", + NS_ConvertUTF16toUTF8(aDictionary).get()); +#endif + } else { + // If user sets a dictionary matching the language defined by + // document, we consider content pref has been canceled, and we clear it. + ClearCurrentDictionary(mEditor); +#ifdef DEBUG_DICT + printf("***** Clearing content preferences for |%s|\n", + NS_ConvertUTF16toUTF8(aDictionary).get()); +#endif + } + + // Also store it in as a preference, so we can use it as a fallback. + // We don't want this for mail composer because it uses + // "spellchecker.dictionary" as a preference. + Preferences::SetString("spellchecker.dictionary", aDictionary); +#ifdef DEBUG_DICT + printf("***** Storing spellchecker.dictionary |%s|\n", + NS_ConvertUTF16toUTF8(aDictionary).get()); +#endif + } + } + return mSpellChecker->SetCurrentDictionary(aDictionary); +} + +NS_IMETHODIMP +nsEditorSpellCheck::UninitSpellChecker() +{ + NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); + + // Cleanup - kill the spell checker + DeleteSuggestedWordList(); + mDictionaryList.Clear(); + mDictionaryIndex = 0; + mSpellChecker = nullptr; + return NS_OK; +} + + +NS_IMETHODIMP +nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter) +{ + mTxtSrvFilter = filter; + return NS_OK; +} + +nsresult +nsEditorSpellCheck::DeleteSuggestedWordList() +{ + mSuggestedWordList.Clear(); + mSuggestedWordIndex = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback) +{ + if (NS_WARN_IF(!mSpellChecker)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv; + + RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this; + + // Get language with html5 algorithm + nsCOMPtr<nsIContent> rootContent; + nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor); + if (htmlEditor) { + rootContent = htmlEditor->GetActiveEditingHost(); + } else { + nsCOMPtr<nsIDOMElement> rootElement; + rv = mEditor->GetRootElement(getter_AddRefs(rootElement)); + NS_ENSURE_SUCCESS(rv, rv); + rootContent = do_QueryInterface(rootElement); + } + + // Try to get topmost document's document element for embedded mail editor. + uint32_t flags = 0; + mEditor->GetFlags(&flags); + if (flags & nsIPlaintextEditor::eEditorMailMask) { + nsCOMPtr<nsIDocument> ownerDoc = rootContent->OwnerDoc(); + NS_ENSURE_TRUE(ownerDoc, NS_ERROR_FAILURE); + nsIDocument* parentDoc = ownerDoc->GetParentDocument(); + if (parentDoc) { + rootContent = do_QueryInterface(parentDoc->GetDocumentElement()); + } + } + + if (!rootContent) { + return NS_ERROR_FAILURE; + } + + RefPtr<DictionaryFetcher> fetcher = + new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup); + rootContent->GetLang(fetcher->mRootContentLang); + nsCOMPtr<nsIDocument> doc = rootContent->GetUncomposedDoc(); + NS_ENSURE_STATE(doc); + doc->GetContentLanguage(fetcher->mRootDocContentLang); + + rv = fetcher->Fetch(mEditor); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// Helper function that iterates over the list of dictionaries and sets the one +// that matches based on a given comparison type. +nsresult +nsEditorSpellCheck::TryDictionary(const nsAString& aDictName, + nsTArray<nsString>& aDictList, + enum dictCompare aCompareType) +{ + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + for (uint32_t i = 0; i < aDictList.Length(); i++) { + nsAutoString dictStr(aDictList.ElementAt(i)); + bool equals = false; + switch (aCompareType) { + case DICT_NORMAL_COMPARE: + equals = aDictName.Equals(dictStr); + break; + case DICT_COMPARE_CASE_INSENSITIVE: + equals = aDictName.Equals(dictStr, nsCaseInsensitiveStringComparator()); + break; + case DICT_COMPARE_DASHMATCH: + equals = nsStyleUtil::DashMatchCompare(dictStr, aDictName, nsCaseInsensitiveStringComparator()); + break; + } + if (equals) { + rv = mSpellChecker->SetCurrentDictionary(dictStr); +#ifdef DEBUG_DICT + if (NS_SUCCEEDED(rv)) + printf("***** Set |%s|.\n", NS_ConvertUTF16toUTF8(dictStr).get()); +#endif + // We always break here. We tried to set the dictionary to an existing + // dictionary from the list. This must work, if it doesn't, there is + // no point trying another one. + break; + } + } + return rv; +} + +nsresult +nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) +{ + MOZ_ASSERT(aFetcher); + RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this; + + // Important: declare the holder after the callback caller so that the former + // is destructed first so that it's not active when the callback is called. + CallbackCaller callbackCaller(aFetcher->mCallback); + UpdateDictionaryHolder holder(this); + + if (aFetcher->mGroup < mDictionaryFetcherGroup) { + // SetCurrentDictionary was called after the fetch started. Don't overwrite + // that dictionary with the fetched one. + return NS_OK; + } + + /* + * We try to derive the dictionary to use based on the following priorities: + * 1) Content preference, so the language the user set for the site before. + * (Introduced in bug 678842 and corrected in bug 717433.) + * 2) Language set by the website, or any other dictionary that partly + * matches that. (Introduced in bug 338427.) + * Eg. if the website is "en-GB", a user who only has "en-US" will get + * that. If the website is generic "en", the user will get one of the + * "en-*" installed, (almost) at random. + * However, we prefer what is stored in "spellchecker.dictionary", + * so if the user chose "en-AU" before, they will get "en-AU" on a plain + * "en" site. (Introduced in bug 682564.) + * 3) The value of "spellchecker.dictionary" which reflects a previous + * language choice of the user (on another site). + * (This was the original behaviour before the aforementioned bugs + * landed). + * 4) The user's locale. + * 5) Use the current dictionary that is currently set. + * 6) The content of the "LANG" environment variable (if set). + * 7) The first spell check dictionary installed. + */ + + // Get the language from the element or its closest parent according to: + // https://html.spec.whatwg.org/#attr-lang + // This is used in SetCurrentDictionary. + mPreferredLang.Assign(aFetcher->mRootContentLang); +#ifdef DEBUG_DICT + printf("***** mPreferredLang (element) |%s|\n", + NS_ConvertUTF16toUTF8(mPreferredLang).get()); +#endif + + // If no luck, try the "Content-Language" header. + if (mPreferredLang.IsEmpty()) { + mPreferredLang.Assign(aFetcher->mRootDocContentLang); +#ifdef DEBUG_DICT + printf("***** mPreferredLang (content-language) |%s|\n", + NS_ConvertUTF16toUTF8(mPreferredLang).get()); +#endif + } + + // Auxiliary status. + nsresult rv2; + + // We obtain a list of available dictionaries. + nsTArray<nsString> dictList; + rv2 = mSpellChecker->GetDictionaryList(&dictList); + NS_ENSURE_SUCCESS(rv2, rv2); + + // Priority 1: + // If we successfully fetched a dictionary from content prefs, do not go + // further. Use this exact dictionary. + // Don't use content preferences for editor with eEditorMailMask flag. + nsAutoString dictName; + uint32_t flags; + mEditor->GetFlags(&flags); + if (!(flags & nsIPlaintextEditor::eEditorMailMask)) { + dictName.Assign(aFetcher->mDictionary); + if (!dictName.IsEmpty()) { + if (NS_SUCCEEDED(TryDictionary(dictName, dictList, DICT_NORMAL_COMPARE))) { +#ifdef DEBUG_DICT + printf("***** Assigned from content preferences |%s|\n", + NS_ConvertUTF16toUTF8(dictName).get()); +#endif + + // We take an early exit here, so let's not forget to clear the word + // list. + DeleteSuggestedWordList(); + return NS_OK; + } + // May be dictionary was uninstalled ? + // Clear the content preference and continue. + ClearCurrentDictionary(mEditor); + } + } + + // Priority 2: + // After checking the content preferences, we use the language of the element + // or document. + dictName.Assign(mPreferredLang); +#ifdef DEBUG_DICT + printf("***** Assigned from element/doc |%s|\n", + NS_ConvertUTF16toUTF8(dictName).get()); +#endif + + // Get the preference value. + nsAutoString preferredDict; + preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary"); + + // The following will be driven by this status. Once we were able to set a + // dictionary successfully, we're done. So we start with a "failed" status. + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + if (!dictName.IsEmpty()) { + // RFC 5646 explicitly states that matches should be case-insensitive. + rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE); + + if (NS_FAILED(rv)) { +#ifdef DEBUG_DICT + printf("***** Setting of |%s| failed (or it wasn't available)\n", + NS_ConvertUTF16toUTF8(dictName).get()); +#endif + + // Required dictionary was not available. Try to get a dictionary + // matching at least language part of dictName. + nsAutoString langCode; + int32_t dashIdx = dictName.FindChar('-'); + if (dashIdx != -1) { + langCode.Assign(Substring(dictName, 0, dashIdx)); + } else { + langCode.Assign(dictName); + } + + // Try dictionary.spellchecker preference, if it starts with langCode, + // so we don't just get any random dictionary matching the language. + if (!preferredDict.IsEmpty() && + nsStyleUtil::DashMatchCompare(preferredDict, langCode, nsDefaultStringComparator())) { +#ifdef DEBUG_DICT + printf("***** Trying preference value |%s| since it matches language code\n", + NS_ConvertUTF16toUTF8(preferredDict).get()); +#endif + rv = TryDictionary (preferredDict, dictList, + DICT_COMPARE_CASE_INSENSITIVE); + } + + if (NS_FAILED(rv)) { + // Use any dictionary with the required language. +#ifdef DEBUG_DICT + printf("***** Trying to find match for language code |%s|\n", + NS_ConvertUTF16toUTF8(langCode).get()); +#endif + rv = TryDictionary (langCode, dictList, DICT_COMPARE_DASHMATCH); + } + } + } + + // Priority 3: + // If the document didn't supply a dictionary or the setting failed, + // try the user preference next. + if (NS_FAILED(rv)) { + if (!preferredDict.IsEmpty()) { +#ifdef DEBUG_DICT + printf("***** Trying preference value |%s|\n", + NS_ConvertUTF16toUTF8(preferredDict).get()); +#endif + rv = TryDictionary (preferredDict, dictList, DICT_NORMAL_COMPARE); + } + } + + // Priority 4: + // As next fallback, try the current locale. + if (NS_FAILED(rv)) { + nsCOMPtr<nsIXULChromeRegistry> packageRegistry = + mozilla::services::GetXULChromeRegistryService(); + + if (packageRegistry) { + nsAutoCString utf8DictName; + rv2 = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"), + false, utf8DictName); + dictName.Assign(EmptyString()); + AppendUTF8toUTF16(utf8DictName, dictName); +#ifdef DEBUG_DICT + printf("***** Trying locale |%s|\n", + NS_ConvertUTF16toUTF8(dictName).get()); +#endif + rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE); + } + } + + if (NS_FAILED(rv)) { + // Still no success. + + // Priority 5: + // If we have a current dictionary, don't try anything else. + nsAutoString currentDictionary; + rv2 = GetCurrentDictionary(currentDictionary); +#ifdef DEBUG_DICT + if (NS_SUCCEEDED(rv2)) { + printf("***** Retrieved current dict |%s|\n", + NS_ConvertUTF16toUTF8(currentDictionary).get()); + } +#endif + + if (NS_FAILED(rv2) || currentDictionary.IsEmpty()) { + // Priority 6: + // Try to get current dictionary from environment variable LANG. + // LANG = language[_territory][.charset] + char* env_lang = getenv("LANG"); + if (env_lang) { + nsString lang = NS_ConvertUTF8toUTF16(env_lang); + // Strip trailing charset, if there is any. + int32_t dot_pos = lang.FindChar('.'); + if (dot_pos != -1) { + lang = Substring(lang, 0, dot_pos); + } + + int32_t underScore = lang.FindChar('_'); + if (underScore != -1) { + lang.Replace(underScore, 1, '-'); +#ifdef DEBUG_DICT + printf("***** Trying LANG from environment |%s|\n", + NS_ConvertUTF16toUTF8(lang).get()); +#endif + nsAutoString lang2; + lang2.Assign(lang); + rv = TryDictionary(lang2, dictList, DICT_COMPARE_CASE_INSENSITIVE); + } + } + + // Priority 7: + // If it does not work, pick the first one. + if (NS_FAILED(rv) && !dictList.IsEmpty()) { + nsAutoString firstInList; + firstInList.Assign(dictList[0]); + rv = TryDictionary(firstInList, dictList, DICT_NORMAL_COMPARE); +#ifdef DEBUG_DICT + printf("***** Trying first of list |%s|\n", + NS_ConvertUTF16toUTF8(dictList[0]).get()); + if (NS_SUCCEEDED(rv)) { + printf ("***** Setting worked.\n"); + } +#endif + } + } + } + + // If an error was thrown while setting the dictionary, just + // fail silently so that the spellchecker dialog is allowed to come + // up. The user can manually reset the language to their choice on + // the dialog if it is wrong. + + DeleteSuggestedWordList(); + + return NS_OK; +} diff --git a/editor/composer/nsEditorSpellCheck.h b/editor/composer/nsEditorSpellCheck.h new file mode 100644 index 000000000..e39a2c891 --- /dev/null +++ b/editor/composer/nsEditorSpellCheck.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsEditorSpellCheck_h___ +#define nsEditorSpellCheck_h___ + + +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsCycleCollectionParticipant.h" +#include "nsIEditorSpellCheck.h" // for NS_DECL_NSIEDITORSPELLCHECK, etc +#include "nsISupportsImpl.h" +#include "nsString.h" // for nsString +#include "nsTArray.h" // for nsTArray +#include "nscore.h" // for nsresult + +class nsIEditor; +class nsISpellChecker; +class nsITextServicesFilter; + +#define NS_EDITORSPELLCHECK_CID \ +{ /* {75656ad9-bd13-4c5d-939a-ec6351eea0cc} */ \ + 0x75656ad9, 0xbd13, 0x4c5d, \ + { 0x93, 0x9a, 0xec, 0x63, 0x51, 0xee, 0xa0, 0xcc }\ +} + +class DictionaryFetcher; + +enum dictCompare { + DICT_NORMAL_COMPARE, + DICT_COMPARE_CASE_INSENSITIVE, + DICT_COMPARE_DASHMATCH +}; + +class nsEditorSpellCheck final : public nsIEditorSpellCheck +{ + friend class DictionaryFetcher; + +public: + nsEditorSpellCheck(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsEditorSpellCheck) + + /* Declare all methods in the nsIEditorSpellCheck interface */ + NS_DECL_NSIEDITORSPELLCHECK + +protected: + virtual ~nsEditorSpellCheck(); + + nsCOMPtr<nsISpellChecker> mSpellChecker; + + nsTArray<nsString> mSuggestedWordList; + int32_t mSuggestedWordIndex; + + // these are the words in the current personal dictionary, + // GetPersonalDictionary must be called to load them. + nsTArray<nsString> mDictionaryList; + int32_t mDictionaryIndex; + + nsresult DeleteSuggestedWordList(); + + nsCOMPtr<nsITextServicesFilter> mTxtSrvFilter; + nsCOMPtr<nsIEditor> mEditor; + + nsString mPreferredLang; + + uint32_t mDictionaryFetcherGroup; + + bool mUpdateDictionaryRunning; + + nsresult TryDictionary(const nsAString& aDictName, nsTArray<nsString>& aDictList, + enum dictCompare aCompareType); + + nsresult DictionaryFetched(DictionaryFetcher* aFetchState); + +public: + void BeginUpdateDictionary() { mUpdateDictionaryRunning = true ;} + void EndUpdateDictionary() { mUpdateDictionaryRunning = false ;} +}; + +#endif // nsEditorSpellCheck_h___ + + diff --git a/editor/composer/nsIEditingSession.idl b/editor/composer/nsIEditingSession.idl new file mode 100644 index 000000000..6cbf00481 --- /dev/null +++ b/editor/composer/nsIEditingSession.idl @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "domstubs.idl" + +interface mozIDOMWindowProxy; +interface nsIEditor; + +[scriptable, uuid(24f963d1-e6fc-43ea-a206-99ac5fcc5265)] + +interface nsIEditingSession : nsISupports +{ + /** + * Error codes when we fail to create an editor + * is placed in attribute editorStatus + */ + const long eEditorOK = 0; + const long eEditorCreationInProgress = 1; + const long eEditorErrorCantEditMimeType = 2; + const long eEditorErrorFileNotFound = 3; + const long eEditorErrorCantEditFramesets = 8; + const long eEditorErrorUnknown = 9; + + /** + * Status after editor creation and document loading + * Value is one of the above error codes + */ + readonly attribute unsigned long editorStatus; + + /** + * Make this window editable + * @param aWindow nsIDOMWindow, the window the embedder needs to make editable + * @param aEditorType string, "html" "htmlsimple" "text" "textsimple" + * @param aMakeWholeDocumentEditable if PR_TRUE make the whole document in + * aWindow editable, otherwise it's the + * embedder who should make the document + * (or part of it) editable. + * @param aInteractive if PR_FALSE turn off scripting and plugins + */ + void makeWindowEditable(in mozIDOMWindowProxy window, + in string aEditorType, + in boolean doAfterUriLoad, + in boolean aMakeWholeDocumentEditable, + in boolean aInteractive); + + /** + * Test whether a specific window has had its editable flag set; it may have an editor + * now, or will get one after the uri load. + * + * Use this, passing the content root window, to test if we've set up editing + * for this content. + */ + boolean windowIsEditable(in mozIDOMWindowProxy window); + + /** + * Get the editor for this window. May return null + */ + nsIEditor getEditorForWindow(in mozIDOMWindowProxy window); + + /** + * Setup editor and related support objects + */ + void setupEditorOnWindow(in mozIDOMWindowProxy window); + + /** + * Destroy editor and related support objects + */ + void tearDownEditorOnWindow(in mozIDOMWindowProxy window); + + void setEditorOnControllers(in mozIDOMWindowProxy aWindow, + in nsIEditor aEditor); + + /** + * Disable scripts and plugins in aWindow. + */ + void disableJSAndPlugins(in mozIDOMWindowProxy aWindow); + + /** + * Restore JS and plugins (enable/disable them) according to the state they + * were before the last call to disableJSAndPlugins. + */ + void restoreJSAndPlugins(in mozIDOMWindowProxy aWindow); + + /** + * Removes all the editor's controllers/listeners etc and makes the window + * uneditable. + */ + void detachFromWindow(in mozIDOMWindowProxy aWindow); + + /** + * Undos detachFromWindow(), reattaches this editing session/editor + * to the window. + */ + void reattachToWindow(in mozIDOMWindowProxy aWindow); + + /** + * Whether this session has disabled JS and plugins. + */ + readonly attribute boolean jsAndPluginsDisabled; +}; + diff --git a/editor/composer/res/EditorOverride.css b/editor/composer/res/EditorOverride.css new file mode 100644 index 000000000..4940fbb30 --- /dev/null +++ b/editor/composer/res/EditorOverride.css @@ -0,0 +1,323 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +*|* { + -moz-user-modify: read-write; +} + +/* Styles to alter look of things in the Editor content window + * that should NOT be removed when we display in completely WYSIWYG + * "Browser Preview" mode. + * Anything that should change, like appearance of table borders + * and Named Anchors, should be placed in EditorContent.css instead of here. +*/ + +/* Primary cursor is text I-beam */ + +::-moz-canvas, a:link { + cursor: text; +} + +/* Use default arrow over objects with size that + are selected when clicked on. + Override the browser's pointer cursor over links +*/ + +img, img[usemap], area, +object, object[usemap], +applet, hr, button, input, textarea, select, +a:link img, a:visited img, a:active img, +a[name]:-moz-only-whitespace { + cursor: default; +} + +a:visited, a:active { + cursor: text; +} + +/* Prevent clicking on links from going to link */ +a:link img, a:visited img { + -moz-user-input: none; +} + +/* We suppress user/author's prefs for link underline, + so we must set explicitly. This isn't good! +*/ +a:link { + text-decoration: underline -moz-anchor-decoration; + color: -moz-hyperlinktext; +} + +/* Allow double-clicks on these widgets to open properties dialogs + XXX except when the widget has disabled attribute */ +input, button, textarea { + -moz-user-select: all !important; + -moz-user-input: auto !important; + -moz-user-focus: none !important; +} + +/* XXX Still need a better way of blocking other events to these widgets */ +select, input[disabled], input[type="checkbox"], input[type="radio"], input[type="file"] { + -moz-user-select: all !important; + -moz-user-input: none !important; + -moz-user-focus: none !important; +} + +input[type="hidden"] { + border: 1px solid black !important; + visibility: visible !important; +} + +label { + -moz-user-select: all !important; +} + +::-moz-display-comboboxcontrol-frame { + -moz-user-select: text !important; +} + +option { + -moz-user-select: text !important; +} + +#mozToc.readonly { + -moz-user-select: all !important; + -moz-user-input: none !important; +} + +/* the following rules are for Image Resizing */ + +span[\_moz_anonclass="mozResizer"] { + width: 5px; + height: 5px; + position: absolute; + border: 1px black solid; + background-color: white; + -moz-user-select: none; + z-index: 2147483646; /* max value -1 for this property */ +} + +/* we can't use :active below */ +span[\_moz_anonclass="mozResizer"][\_moz_activated], +span[\_moz_anonclass="mozResizer"]:hover { + background-color: black; +} + +span[\_moz_anonclass="mozResizer"].hidden, +span[\_moz_anonclass="mozResizingShadow"].hidden, +img[\_moz_anonclass="mozResizingShadow"].hidden, +span[\_moz_anonclass="mozGrabber"].hidden, +span[\_moz_anonclass="mozResizingInfo"].hidden, +a[\_moz_anonclass="mozTableRemoveRow"].hidden, +a[\_moz_anonclass="mozTableRemoveColumn"].hidden { + display: none !important; +} + +span[\_moz_anonclass="mozResizer"][anonlocation="nw"] { + cursor: nw-resize; +} +span[\_moz_anonclass="mozResizer"][anonlocation="n"] { + cursor: n-resize; +} +span[\_moz_anonclass="mozResizer"][anonlocation="ne"] { + cursor: ne-resize; +} +span[\_moz_anonclass="mozResizer"][anonlocation="w"] { + cursor: w-resize; +} +span[\_moz_anonclass="mozResizer"][anonlocation="e"] { + cursor: e-resize; +} +span[\_moz_anonclass="mozResizer"][anonlocation="sw"] { + cursor: sw-resize; +} +span[\_moz_anonclass="mozResizer"][anonlocation="s"] { + cursor: s-resize; +} +span[\_moz_anonclass="mozResizer"][anonlocation="se"] { + cursor: se-resize; +} + +span[\_moz_anonclass="mozResizingShadow"], +img[\_moz_anonclass="mozResizingShadow"] { + outline: thin dashed black; + -moz-user-select: none; + opacity: 0.5; + position: absolute; + z-index: 2147483647; /* max value for this property */ +} + +span[\_moz_anonclass="mozResizingInfo"] { + font-family: sans-serif; + font-size: x-small; + color: black; + background-color: #d0d0d0; + border: ridge 2px #d0d0d0; + padding: 2px; + position: absolute; + z-index: 2147483647; /* max value for this property */ +} + +img[\_moz_resizing] { + outline: thin solid black; +} + +*[\_moz_abspos] { + outline: silver ridge 2px; + z-index: 2147483645 !important; /* max value -2 for this property */ +} +*[\_moz_abspos="white"] { + background-color: white !important; +} +*[\_moz_abspos="black"] { + background-color: black !important; +} + +span[\_moz_anonclass="mozGrabber"] { + outline: ridge 2px silver; + padding: 2px; + position: absolute; + width: 12px; + height: 12px; + background-image: url("resource://gre/res/grabber.gif"); + background-repeat: no-repeat; + background-position: center center; + -moz-user-select: none; + cursor: move; + z-index: 2147483647; /* max value for this property */ +} + +/* INLINE TABLE EDITING */ + +a[\_moz_anonclass="mozTableAddColumnBefore"] { + position: absolute; + z-index: 2147483647; /* max value for this property */ + text-decoration: none !important; + border: none 0px !important; + width: 4px; + height: 8px; + background-image: url("resource://gre/res/table-add-column-before.gif"); + background-repeat: no-repeat; + background-position: center center; + -moz-user-select: none !important; + -moz-user-focus: none !important; +} + +a[\_moz_anonclass="mozTableAddColumnBefore"]:hover { + background-image: url("resource://gre/res/table-add-column-before-hover.gif"); +} + +a[\_moz_anonclass="mozTableAddColumnBefore"]:active { + background-image: url("resource://gre/res/table-add-column-before-active.gif"); +} + +a[\_moz_anonclass="mozTableAddColumnAfter"] { + position: absolute; + z-index: 2147483647; /* max value for this property */ + text-decoration: none !important; + border: none 0px !important; + width: 4px; + height: 8px; + background-image: url("resource://gre/res/table-add-column-after.gif"); + background-repeat: no-repeat; + background-position: center center; + -moz-user-select: none !important; + -moz-user-focus: none !important; +} + +a[\_moz_anonclass="mozTableAddColumnAfter"]:hover { + background-image: url("resource://gre/res/table-add-column-after-hover.gif"); +} + +a[\_moz_anonclass="mozTableAddColumnAfter"]:active { + background-image: url("resource://gre/res/table-add-column-after-active.gif"); +} + +a[\_moz_anonclass="mozTableRemoveColumn"] { + position: absolute; + z-index: 2147483647; /* max value for this property */ + text-decoration: none !important; + border: none 0px !important; + width: 8px; + height: 8px; + background-image: url("resource://gre/res/table-remove-column.gif"); + background-repeat: no-repeat; + background-position: center center; + -moz-user-select: none !important; + -moz-user-focus: none !important; +} + +a[\_moz_anonclass="mozTableRemoveColumn"]:hover { + background-image: url("resource://gre/res/table-remove-column-hover.gif"); +} + +a[\_moz_anonclass="mozTableRemoveColumn"]:active { + background-image: url("resource://gre/res/table-remove-column-active.gif"); +} + +a[\_moz_anonclass="mozTableAddRowBefore"] { + position: absolute; + z-index: 2147483647; /* max value for this property */ + text-decoration: none !important; + border: none 0px !important; + width: 8px; + height: 4px; + background-image: url("resource://gre/res/table-add-row-before.gif"); + background-repeat: no-repeat; + background-position: center center; + -moz-user-select: none !important; + -moz-user-focus: none !important; +} + +a[\_moz_anonclass="mozTableAddRowBefore"]:hover { + background-image: url("resource://gre/res/table-add-row-before-hover.gif"); +} + +a[\_moz_anonclass="mozTableAddRowBefore"]:active { + background-image: url("resource://gre/res/table-add-row-before-active.gif"); +} + +a[\_moz_anonclass="mozTableAddRowAfter"] { + position: absolute; + z-index: 2147483647; /* max value for this property */ + text-decoration: none !important; + border: none 0px !important; + width: 8px; + height: 4px; + background-image: url("resource://gre/res/table-add-row-after.gif"); + background-repeat: no-repeat; + background-position: center center; + -moz-user-select: none !important; + -moz-user-focus: none !important; +} + +a[\_moz_anonclass="mozTableAddRowAfter"]:hover { + background-image: url("resource://gre/res/table-add-row-after-hover.gif"); +} + +a[\_moz_anonclass="mozTableAddRowAfter"]:active { + background-image: url("resource://gre/res/table-add-row-after-active.gif"); +} + +a[\_moz_anonclass="mozTableRemoveRow"] { + position: absolute; + z-index: 2147483647; /* max value for this property */ + text-decoration: none !important; + border: none 0px !important; + width: 8px; + height: 8px; + background-image: url("resource://gre/res/table-remove-row.gif"); + background-repeat: no-repeat; + background-position: center center; + -moz-user-select: none !important; + -moz-user-focus: none !important; +} + +a[\_moz_anonclass="mozTableRemoveRow"]:hover { + background-image: url("resource://gre/res/table-remove-row-hover.gif"); +} + +a[\_moz_anonclass="mozTableRemoveRow"]:active { + background-image: url("resource://gre/res/table-remove-row-active.gif"); +} diff --git a/editor/composer/res/grabber.gif b/editor/composer/res/grabber.gif Binary files differnew file mode 100644 index 000000000..06749a64f --- /dev/null +++ b/editor/composer/res/grabber.gif diff --git a/editor/composer/res/table-add-column-after-active.gif b/editor/composer/res/table-add-column-after-active.gif Binary files differnew file mode 100644 index 000000000..3ec50b82e --- /dev/null +++ b/editor/composer/res/table-add-column-after-active.gif diff --git a/editor/composer/res/table-add-column-after-hover.gif b/editor/composer/res/table-add-column-after-hover.gif Binary files differnew file mode 100644 index 000000000..29679f981 --- /dev/null +++ b/editor/composer/res/table-add-column-after-hover.gif diff --git a/editor/composer/res/table-add-column-after.gif b/editor/composer/res/table-add-column-after.gif Binary files differnew file mode 100644 index 000000000..8891be969 --- /dev/null +++ b/editor/composer/res/table-add-column-after.gif diff --git a/editor/composer/res/table-add-column-before-active.gif b/editor/composer/res/table-add-column-before-active.gif Binary files differnew file mode 100644 index 000000000..1e205291e --- /dev/null +++ b/editor/composer/res/table-add-column-before-active.gif diff --git a/editor/composer/res/table-add-column-before-hover.gif b/editor/composer/res/table-add-column-before-hover.gif Binary files differnew file mode 100644 index 000000000..7b54537e4 --- /dev/null +++ b/editor/composer/res/table-add-column-before-hover.gif diff --git a/editor/composer/res/table-add-column-before.gif b/editor/composer/res/table-add-column-before.gif Binary files differnew file mode 100644 index 000000000..d4a3ffe5e --- /dev/null +++ b/editor/composer/res/table-add-column-before.gif diff --git a/editor/composer/res/table-add-row-after-active.gif b/editor/composer/res/table-add-row-after-active.gif Binary files differnew file mode 100644 index 000000000..cc01da2c9 --- /dev/null +++ b/editor/composer/res/table-add-row-after-active.gif diff --git a/editor/composer/res/table-add-row-after-hover.gif b/editor/composer/res/table-add-row-after-hover.gif Binary files differnew file mode 100644 index 000000000..a829351b6 --- /dev/null +++ b/editor/composer/res/table-add-row-after-hover.gif diff --git a/editor/composer/res/table-add-row-after.gif b/editor/composer/res/table-add-row-after.gif Binary files differnew file mode 100644 index 000000000..3f1a39d98 --- /dev/null +++ b/editor/composer/res/table-add-row-after.gif diff --git a/editor/composer/res/table-add-row-before-active.gif b/editor/composer/res/table-add-row-before-active.gif Binary files differnew file mode 100644 index 000000000..34f1e0ade --- /dev/null +++ b/editor/composer/res/table-add-row-before-active.gif diff --git a/editor/composer/res/table-add-row-before-hover.gif b/editor/composer/res/table-add-row-before-hover.gif Binary files differnew file mode 100644 index 000000000..e8f1d10b0 --- /dev/null +++ b/editor/composer/res/table-add-row-before-hover.gif diff --git a/editor/composer/res/table-add-row-before.gif b/editor/composer/res/table-add-row-before.gif Binary files differnew file mode 100644 index 000000000..1682170cb --- /dev/null +++ b/editor/composer/res/table-add-row-before.gif diff --git a/editor/composer/res/table-remove-column-active.gif b/editor/composer/res/table-remove-column-active.gif Binary files differnew file mode 100644 index 000000000..4dfbde4ce --- /dev/null +++ b/editor/composer/res/table-remove-column-active.gif diff --git a/editor/composer/res/table-remove-column-hover.gif b/editor/composer/res/table-remove-column-hover.gif Binary files differnew file mode 100644 index 000000000..fd11bb52c --- /dev/null +++ b/editor/composer/res/table-remove-column-hover.gif diff --git a/editor/composer/res/table-remove-column.gif b/editor/composer/res/table-remove-column.gif Binary files differnew file mode 100644 index 000000000..d8071da0a --- /dev/null +++ b/editor/composer/res/table-remove-column.gif diff --git a/editor/composer/res/table-remove-row-active.gif b/editor/composer/res/table-remove-row-active.gif Binary files differnew file mode 100644 index 000000000..4dfbde4ce --- /dev/null +++ b/editor/composer/res/table-remove-row-active.gif diff --git a/editor/composer/res/table-remove-row-hover.gif b/editor/composer/res/table-remove-row-hover.gif Binary files differnew file mode 100644 index 000000000..fd11bb52c --- /dev/null +++ b/editor/composer/res/table-remove-row-hover.gif diff --git a/editor/composer/res/table-remove-row.gif b/editor/composer/res/table-remove-row.gif Binary files differnew file mode 100644 index 000000000..d8071da0a --- /dev/null +++ b/editor/composer/res/table-remove-row.gif diff --git a/editor/composer/test/bug1200533_subframe.html b/editor/composer/test/bug1200533_subframe.html new file mode 100644 index 000000000..6eaefc266 --- /dev/null +++ b/editor/composer/test/bug1200533_subframe.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> +<meta http-equiv="Content-Language" content="en-US"> +</head> +<body> +<textarea id="none">root en-US</textarea> +<textarea id="en-GB" lang="en-GB">root en-US, but element en-GB</textarea> +<textarea id="en-gb" lang="en-gb">root en-US, but element en-gb (lower case)</textarea> +<textarea id="en-ZA-not-avail" lang="en-ZA">root en-US, but element en-ZA (which is not installed)</textarea> +<textarea id="en-generic" lang="en">root en-US, but element en</textarea> +<textarea id="ko-not-avail" lang="ko">root en-US, but element ko (which is not installed)</textarea> +</body> +</html> diff --git a/editor/composer/test/bug1204147_subframe.html b/editor/composer/test/bug1204147_subframe.html new file mode 100644 index 000000000..a9b1225cd --- /dev/null +++ b/editor/composer/test/bug1204147_subframe.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<textarea id="en-GB" lang="en-GB">element en-GB</textarea> +<textarea id="en-US" lang="testing-XX">element should default to en-US</textarea> + +<div id="trouble-maker" contenteditable>the presence of this div triggers the faulty code path</div> +</body> +</html> diff --git a/editor/composer/test/bug1204147_subframe2.html b/editor/composer/test/bug1204147_subframe2.html new file mode 100644 index 000000000..935777bd9 --- /dev/null +++ b/editor/composer/test/bug1204147_subframe2.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<textarea id="en-GB" lang="en-GB">element en-GB</textarea> +<textarea id="en-US" lang="testing-XX">element should default to en-US</textarea> +</body> +</html> diff --git a/editor/composer/test/bug678842_subframe.html b/editor/composer/test/bug678842_subframe.html new file mode 100644 index 000000000..39d578ee4 --- /dev/null +++ b/editor/composer/test/bug678842_subframe.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<textarea id="textarea" lang="testing-XXX"></textarea> +</body> +</html> diff --git a/editor/composer/test/bug717433_subframe.html b/editor/composer/test/bug717433_subframe.html new file mode 100644 index 000000000..3c2927e88 --- /dev/null +++ b/editor/composer/test/bug717433_subframe.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<textarea id="textarea" lang="en"></textarea> +</body> +</html> diff --git a/editor/composer/test/chrome.ini b/editor/composer/test/chrome.ini new file mode 100644 index 000000000..015cad3d5 --- /dev/null +++ b/editor/composer/test/chrome.ini @@ -0,0 +1,5 @@ +[DEFAULT] +skip-if = os == 'android' + +[test_bug434998.xul] +[test_bug1266815.html] diff --git a/editor/composer/test/de-DE/de_DE.aff b/editor/composer/test/de-DE/de_DE.aff new file mode 100644 index 000000000..5dc6896b6 --- /dev/null +++ b/editor/composer/test/de-DE/de_DE.aff @@ -0,0 +1,2 @@ +# Affix file for German English dictionary +# Fake file, nothing here. diff --git a/editor/composer/test/de-DE/de_DE.dic b/editor/composer/test/de-DE/de_DE.dic new file mode 100644 index 000000000..415c21686 --- /dev/null +++ b/editor/composer/test/de-DE/de_DE.dic @@ -0,0 +1,6 @@ +5 +ein +guter +heute +ist +Tag diff --git a/editor/composer/test/en-AU/en_AU.aff b/editor/composer/test/en-AU/en_AU.aff new file mode 100644 index 000000000..e0c467248 --- /dev/null +++ b/editor/composer/test/en-AU/en_AU.aff @@ -0,0 +1,2 @@ +# Affix file for British English dictionary +# Fake file, nothing here. diff --git a/editor/composer/test/en-AU/en_AU.dic b/editor/composer/test/en-AU/en_AU.dic new file mode 100644 index 000000000..0a1be725d --- /dev/null +++ b/editor/composer/test/en-AU/en_AU.dic @@ -0,0 +1,4 @@ +3 +Mary +Paul +Peter diff --git a/editor/composer/test/en-GB/en_GB.aff b/editor/composer/test/en-GB/en_GB.aff new file mode 100644 index 000000000..e0c467248 --- /dev/null +++ b/editor/composer/test/en-GB/en_GB.aff @@ -0,0 +1,2 @@ +# Affix file for British English dictionary +# Fake file, nothing here. diff --git a/editor/composer/test/en-GB/en_GB.dic b/editor/composer/test/en-GB/en_GB.dic new file mode 100644 index 000000000..0a1be725d --- /dev/null +++ b/editor/composer/test/en-GB/en_GB.dic @@ -0,0 +1,4 @@ +3 +Mary +Paul +Peter diff --git a/editor/composer/test/mochitest.ini b/editor/composer/test/mochitest.ini new file mode 100644 index 000000000..832137d60 --- /dev/null +++ b/editor/composer/test/mochitest.ini @@ -0,0 +1,40 @@ +[DEFAULT] +support-files = + bug678842_subframe.html + bug717433_subframe.html + bug1200533_subframe.html + bug1204147_subframe.html + bug1204147_subframe2.html + en-GB/en_GB.dic + en-GB/en_GB.aff + en-AU/en_AU.dic + en-AU/en_AU.aff + de-DE/de_DE.dic + de-DE/de_DE.aff + +[test_async_UpdateCurrentDictionary.html] +skip-if = os == 'android' +[test_bug338427.html] +skip-if = os == 'android' +[test_bug348497.html] +[test_bug384147.html] +[test_bug389350.html] +skip-if = toolkit == 'android' +[test_bug519928.html] +[test_bug678842.html] +skip-if = os == 'android' +[test_bug697981.html] +skip-if = os == 'android' +[test_bug717433.html] +skip-if = os == 'android' +[test_bug738440.html] +[test_bug1200533.html] +skip-if = os == 'android' +[test_bug1204147.html] +skip-if = os == 'android' +[test_bug1205983.html] +skip-if = os == 'android' +[test_bug1209414.html] +skip-if = os == 'android' +[test_bug1219928.html] +skip-if = e10s || os == 'android' diff --git a/editor/composer/test/test_async_UpdateCurrentDictionary.html b/editor/composer/test/test_async_UpdateCurrentDictionary.html new file mode 100644 index 000000000..53d9ff307 --- /dev/null +++ b/editor/composer/test/test_async_UpdateCurrentDictionary.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=856270 +--> +<head> + <title>Test for Bug 856270 - Async UpdateCurrentDictionary</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=856270">Mozilla Bug 856270</a> +<p id="display"></p> +<div id="content"> +<textarea id="editor" spellcheck="true"></textarea> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript;version=1.8"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(start); + +function start() { + var textarea = document.getElementById("editor"); + textarea.focus(); + + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(textarea, function () { + var isc = SpecialPowers.wrap(textarea).editor.getInlineSpellChecker(false); + ok(isc, "Inline spell checker should exist after focus and spell check"); + var sc = isc.spellChecker; + isnot(sc.GetCurrentDictionary(), lang, + "Current dictionary should not be set yet."); + + // First, set the lang attribute on the textarea, call Update, and make + // sure the spell checker's language was updated appropriately. + var lang = "en-US"; + textarea.setAttribute("lang", lang); + sc.UpdateCurrentDictionary(function () { + is(sc.GetCurrentDictionary(), lang, + "UpdateCurrentDictionary should set the current dictionary."); + + // Second, make some Update calls, but then do a Set. The Set should + // effectively cancel the Updates, but the Updates' callbacks should be + // called nonetheless. + var numCalls = 3; + for (var i = 0; i < numCalls; i++) { + sc.UpdateCurrentDictionary(function () { + is(sc.GetCurrentDictionary(), "", + "No dictionary should be active after Update."); + if (--numCalls == 0) { + // This will clear the content preferences and reset "spellchecker.dictionary". + sc.SetCurrentDictionary(""); + SimpleTest.finish(); + } + }); + } + try { + sc.SetCurrentDictionary("testing-XX"); + } + catch (err) { + // Set throws NS_ERROR_NOT_AVAILABLE because "testing-XX" isn't really + // an available dictionary. + } + is(sc.GetCurrentDictionary(), "", + "No dictionary should be active after Set."); + }); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/editor/composer/test/test_bug1200533.html b/editor/composer/test/test_bug1200533.html new file mode 100644 index 000000000..310b8d7cf --- /dev/null +++ b/editor/composer/test/test_bug1200533.html @@ -0,0 +1,139 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1200533 +--> +<head> + <title>Test for Bug 1200533</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1200533">Mozilla Bug 1200533</a> +<p id="display"></p> +<iframe id="content"></iframe> + +</div> +<pre id="test"> +<script class="testbody" ttype="application/javascript"> + +/** Test for Bug 1200533 **/ +/** Visit the elements defined above and check the dictionary we got **/ +SimpleTest.waitForExplicitFinish(); +var content = document.getElementById('content'); + +var tests = [ + // text area, value of spellchecker.dictionary, result. + // Result: Document language. + [ "none", "", "en-US" ], + // Result: Element language. + [ "en-GB", "", "en-GB" ], + [ "en-gb", "", "en-GB" ], + // Result: Random en-*. + [ "en-ZA-not-avail", "", "*" ], + [ "en-generic", "", "*" ], + // Result: Locale. + [ "ko-not-avail", "", "en-US" ], + + // Result: Preference value in all cases. + [ "en-ZA-not-avail", "en-AU", "en-AU" ], + [ "en-generic", "en-AU", "en-AU" ], + [ "ko-not-avail", "en-AU", "en-AU" ], + + // Result: Random en-*. + [ "en-ZA-not-avail", "de-DE", "*" ], + [ "en-generic", "de-DE", "*" ], + // Result: Preference value. + [ "ko-not-avail", "de-DE", "de-DE" ], + ]; + +var loadCount = 0; +var script; + +var loadListener = function(evt) { + if (loadCount == 0) { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); + + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); + + // Install en-GB, en-AU and de-DE dictionaries. + var en_GB = dir.clone(); + var en_AU = dir.clone(); + var de_DE = dir.clone(); + en_GB.append("en-GB"); + en_AU.append("en-AU"); + de_DE.append("de-DE"); + hunspell.addDirectory(en_GB); + hunspell.addDirectory(en_AU); + hunspell.addDirectory(de_DE); + + addMessageListener("check-existence", + () => [en_GB.exists(), en_AU.exists(), + de_DE.exists()]); + addMessageListener("destroy", () => { + hunspell.removeDirectory(en_GB); + hunspell.removeDirectory(en_AU); + hunspell.removeDirectory(de_DE); + }); + }); + var existenceChecks = script.sendSyncMessage("check-existence")[0][0]; + is(existenceChecks[0], true, "true expected (en-GB directory should exist)"); + is(existenceChecks[1], true, "true expected (en-AU directory should exist)"); + is(existenceChecks[2], true, "true expected (de-DE directory should exist)"); + } + + SpecialPowers.pushPrefEnv({set: [["spellchecker.dictionary", tests[loadCount][1]]]}, + function() { continueTest(evt) }); +} + +function continueTest(evt) { + var doc = evt.target.contentDocument; + var elem = doc.getElementById(tests[loadCount][0]); + var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; + editor.setSpellcheckUserOverride(true); + var inlineSpellChecker = editor.getInlineSpellChecker(true); + + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(elem, function () { + var spellchecker = inlineSpellChecker.spellChecker; + try { + var dict = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + if (tests[loadCount][2] != "*") { + is (dict, tests[loadCount][2], "expected " + tests[loadCount][2]); + } else { + var gotEn = (dict == "en-GB" || dict == "en-AU" || dict == "en-US"); + is (gotEn, true, "expected en-AU or en-GB or en-US"); + } + + loadCount++; + if (loadCount < tests.length) { + // Load the iframe again. + content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1200533_subframe.html?firstload=false'; + } else { + // Remove the fake dictionaries again, since it's otherwise picked up by later tests. + script.sendSyncMessage("destroy"); + + SimpleTest.finish(); + } + }); + +} + +content.addEventListener('load', loadListener, false); + +content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1200533_subframe.html?firstload=true'; + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug1204147.html b/editor/composer/test/test_bug1204147.html new file mode 100644 index 000000000..d7dd90474 --- /dev/null +++ b/editor/composer/test/test_bug1204147.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1204147 +--> +<head> + <title>Test for Bug 1204147</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1204147">Mozilla Bug 1204147</a> +<p id="display"></p> +<iframe id="content"></iframe> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1204147 **/ +SimpleTest.waitForExplicitFinish(); +var content = document.getElementById('content'); +// Load a subframe containing an editor with using "en-GB". At first +// load, it will set the dictionary to "en-GB". The bug was that a content preference +// was also created. At second load, we check the dictionary for another element, +// one that should use "en-US". With the bug corrected, we get "en-US", before +// we got "en-GB" from the content preference. + +var firstLoad = true; +var script; + +var loadListener = function(evt) { + if (firstLoad) { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); + + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); + + // Install en-GB dictionary. + en_GB = dir.clone(); + en_GB.append("en-GB"); + hunspell.addDirectory(en_GB); + + addMessageListener("en_GB-exists", () => en_GB.exists()); + addMessageListener("destroy", () => hunspell.removeDirectory(en_GB)); + }); + is(script.sendSyncMessage("en_GB-exists")[0][0], true, + "true expected (en-GB directory should exist)"); + } + + var doc = evt.target.contentDocument; + var elem; + if (firstLoad) { + elem = doc.getElementById('en-GB'); + } else { + elem = doc.getElementById('en-US'); + } + + var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; + editor.setSpellcheckUserOverride(true); + var inlineSpellChecker = editor.getInlineSpellChecker(true); + + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(elem, function () { + var spellchecker = inlineSpellChecker.spellChecker; + try { + var currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + if (firstLoad) { + firstLoad = false; + + // First time around, the element's language should be used. + is (currentDictonary, "en-GB", "unexpected lang " + currentDictonary + " instead of en-GB"); + + // Note that on second load, we load a different page, which does NOT have the trouble-causing + // contenteditable in it. Sadly, loading the same page with the trouble-maker in it + // doesn't allow the retrieval of the spell check dictionary used for the element, + // because the trouble-maker causes the 'global' spell check dictionary to be set to "en-GB" + // (since it picks the first one from the list) before we have the chance to retrieve + // the dictionary for the element (which happens asynchonously after the spell check has completed). + content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1204147_subframe2.html?firstload=false'; + } else { + // Second time around, the element should default to en-US. + // Without the fix, the first run sets the content preference to en-GB for the whole site. + is (currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US"); + content.removeEventListener('load', loadListener, false); + + // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests. + script.sendSyncMessage("destroy"); + + // Reset the preference, so the last value we set doesn't collide with the next test. + SimpleTest.finish(); + } + }); +} + +content.addEventListener('load', loadListener, false); + +content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1204147_subframe.html?firstload=true'; + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug1205983.html b/editor/composer/test/test_bug1205983.html new file mode 100644 index 000000000..139f6fc3c --- /dev/null +++ b/editor/composer/test/test_bug1205983.html @@ -0,0 +1,137 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1205983 +--> +<head> + <title>Test for Bug 1205983</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205983">Mozilla Bug 1205983</a> +<p id="display"></p> +</div> + +<div contenteditable id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</div> +<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function getMisspelledWords(editor) { + return editor.selectionController.getSelection(SpecialPowers.Ci.nsISelectionController.SELECTION_SPELLCHECK).toString(); +} + +var elem_de; +var editor_de; +var selcon_de; +var script; + +var onSpellCheck = + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck; + +/** Test for Bug 1205983 **/ +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); + + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); + + // Install de-DE dictionary. + var de_DE = dir.clone(); + de_DE.append("de-DE"); + hunspell.addDirectory(de_DE); + + addMessageListener("de_DE-exists", () => de_DE.exists()); + addMessageListener("destroy", () => hunspell.removeDirectory(de_DE)); + }); + is(script.sendSyncMessage("de_DE-exists")[0][0], true, + "true expected (de_DE directory should exist)"); + + document.getElementById('de-DE').focus(); +}); + +function deFocus() { + elem_de = document.getElementById('de-DE'); + + onSpellCheck(elem_de, function () { + var Ci = SpecialPowers.Ci; + var editingSession = SpecialPowers.wrap(window) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIEditingSession); + editor_de = editingSession.getEditorForWindow(window); + selcon_de = editor_de.selectionController; + var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK); + + // Check that we spelled in German, so there is only one misspelled word. + is(sel.toString(), "German", "one misspelled word expected: German"); + + // Now focus the textarea, which requires English spelling. + document.getElementById('en-US').focus(); + }); +} + +function enFocus() { + var elem_en = document.getElementById('en-US'); + var editor_en = + SpecialPowers.wrap(elem_en) + .QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement) + .editor; + editor_en.setSpellcheckUserOverride(true); + var inlineSpellChecker = editor_en.getInlineSpellChecker(true); + + onSpellCheck(elem_en, function () { + var spellchecker = inlineSpellChecker.spellChecker; + try { + currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + // Check that the English dictionary is loaded and that the spell check has worked. + is(currentDictonary, "en-US", "expected en-US"); + is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword"); + + // So far all was boring. The important thing is whether the spell check result + // in the de-DE editor is still the same. After losing focus, no spell check + // updates should take place there. + var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK); + is(sel.toString(), "German", "one misspelled word expected: German"); + + // Remove the fake de_DE dictionary again. + script.sendSyncMessage("destroy"); + + // Focus again, so the spelling gets updated, but before we need to kill the focus handler. + elem_de.onfocus = null; + elem_de.blur(); + elem_de.focus(); + + // After removal, the de_DE editor should refresh the spelling with en-US. + onSpellCheck(elem_de, function () { + var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK); + is(sel.toString(), "heute" + "ist" + "ein" + "guter", + "some misspelled words expected: heute ist ein guter"); + + // If we don't reset this, we cause massive leaks. + selcon_de = null; + editor_de = null; + + SimpleTest.finish(); + }); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug1209414.html b/editor/composer/test/test_bug1209414.html new file mode 100644 index 000000000..bce1bb313 --- /dev/null +++ b/editor/composer/test/test_bug1209414.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1209414 +--> +<head> + <title>Test for Bug 1209414</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1209414">Mozilla Bug 1209414</a> +<p id="display"></p> +</div> + +<textarea id="de-DE" lang="de-DE">heute ist ein guter Tag - today is a good day</textarea> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const Ci = SpecialPowers.Ci; + +function getMisspelledWords(editor) { + return editor.selectionController.getSelection(Ci.nsISelectionController.SELECTION_SPELLCHECK).toString(); +} + +var elem_de; +var editor_de; +var script; + +/** Test for Bug 1209414 **/ +/* + * All we want to do in this test is change the spelling using a right-click and selection from the menu. + * This is necessary since all the other tests use SetCurrentDictionary() which doesn't reflect + * user behaviour. + */ + +var onSpellCheck = + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + script = SpecialPowers.loadChromeScript(function() { + const Ci = Components.interfaces; + var chromeWin = browserElement.ownerDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow) + .QueryInterface(Ci.nsIDOMChromeWindow); + var contextMenu = chromeWin.document.getElementById("contentAreaContextMenu"); + contextMenu.addEventListener("popupshown", + () => sendAsyncMessage("popupshown"), false); + + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); + + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Ci.mozISpellCheckingEngine); + + // Install de-DE dictionary. + de_DE = dir.clone(); + de_DE.append("de-DE"); + hunspell.addDirectory(de_DE); + + addMessageListener("hidepopup", function() { + var state = contextMenu.state; + + // Select Language from the menu. Take a look at + // toolkit/modules/InlineSpellChecker.jsm to see how the menu works. + + contextMenu.ownerDocument.getElementById("spell-check-dictionary-en-US") + .doCommand(); + contextMenu.hidePopup(); + + return state; + }); + addMessageListener("destroy", () => hunspell.removeDirectory(de_DE)); + addMessageListener("contextMenu-not-null", () => contextMenu != null); + addMessageListener("de_DE-exists", () => de_DE.exists()); + }); + is(script.sendSyncMessage("contextMenu-not-null")[0][0], true, + "Got context menu XUL"); + is(script.sendSyncMessage("de_DE-exists")[0][0], true, + "true expected (de_DE directory should exist)"); + script.addMessageListener("popupshown", handlePopup); + + elem_de = document.getElementById('de-DE'); + editor_de = SpecialPowers.wrap(elem_de) + .QueryInterface(Ci.nsIDOMNSEditableElement).editor; + editor_de.setSpellcheckUserOverride(true); + + onSpellCheck(elem_de, function () { + var inlineSpellChecker = editor_de.getInlineSpellChecker(true); + var spellchecker = inlineSpellChecker.spellChecker; + try { + var currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + // Check that the German dictionary is loaded and that the spell check has worked. + is(currentDictonary, "de-DE", "expected de-DE"); + is(getMisspelledWords(editor_de), "today" + "is" + "a" + "good" + "day", "some misspelled words expected: today is a good day"); + + // Focus again, just to be sure that the context-click won't trigger another spell check. + elem_de.focus(); + + // Make sure all spell checking action is done before right-click to select the en-US dictionary. + onSpellCheck(elem_de, function () { + synthesizeMouse(elem_de, 2, 2, { type : "contextmenu", button : 2 }, window); + }); + }); +}); + +function handlePopup() { + var state = script.sendSyncMessage("hidepopup")[0][0]; + is(state, "open", "checking if popup is open"); + + onSpellCheck(elem_de, function () { + var inlineSpellChecker = editor_de.getInlineSpellChecker(true); + var spellchecker = inlineSpellChecker.spellChecker; + try { + currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + // Check that the English dictionary is loaded and that the spell check has worked. + is(currentDictonary, "en-US", "expected en-US"); + is(getMisspelledWords(editor_de), "heute" + "ist" + "ein" + "guter", "some misspelled words expected: heute ist ein guter"); + + // Remove the fake de_DE dictionary again. + script.sendSyncMessage("destroy"); + + // This will clear the content preferences and reset "spellchecker.dictionary". + spellchecker.SetCurrentDictionary(""); + SimpleTest.finish(); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug1219928.html b/editor/composer/test/test_bug1219928.html new file mode 100644 index 000000000..2f646f00f --- /dev/null +++ b/editor/composer/test/test_bug1219928.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1219928 +--> +<head> + <title>Test for Bug 1219928</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1219928">Mozilla Bug 1219928</a> +<p id="display"></p> + +<div contenteditable id="en-US" lang="en-US"> +<p>And here a missspelled word</p> +<style> +<!-- and here another onnee in a style comment --> +</style> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1219928 **/ +/* Very simple test to check that <style> blocks are skipped in the spell check */ + +var spellchecker; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", + window); + + var elem = document.getElementById('en-US'); + elem.focus(); + + onSpellCheck(elem, function () { + var Ci = SpecialPowers.Ci; + var editingSession = SpecialPowers.wrap(window) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIEditingSession); + var editor = editingSession.getEditorForWindow(window); + var selcon = editor.selectionController; + var sel = selcon.getSelection(selcon.SELECTION_SPELLCHECK); + + is(sel.toString(), "missspelled", "one misspelled word expected: missspelled"); + + spellchecker = SpecialPowers.Cc['@mozilla.org/editor/editorspellchecker;1'] + .createInstance(Ci.nsIEditorSpellCheck); + var filterContractId = "@mozilla.org/editor/txtsrvfilter;1"; + spellchecker.setFilter(SpecialPowers.Cc[filterContractId] + .createInstance(Ci.nsITextServicesFilter)); + spellchecker.InitSpellChecker(editor, false, spellCheckStarted); + }); +}); + +function spellCheckStarted() { + var misspelledWord = spellchecker.GetNextMisspelledWord(); + is(misspelledWord, "missspelled", "first misspelled word expected: missspelled"); + + // Without the fix, the next misspelled word was 'onnee', so we check that we don't get it. + misspelledWord = spellchecker.GetNextMisspelledWord(); + isnot(misspelledWord, "onnee", "second misspelled word should not be: onnee"); + + spellchecker = ""; + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug1266815.html b/editor/composer/test/test_bug1266815.html new file mode 100644 index 000000000..a2ab0b048 --- /dev/null +++ b/editor/composer/test/test_bug1266815.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<script type="text/javascript"> +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; +const Cu = SpecialPowers.Cu; + +const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); + +const HELPERAPP_DIALOG_CID = + SpecialPowers.wrap(SpecialPowers.Components) + .ID(Cc["@mozilla.org/helperapplauncherdialog;1"].number); +const HELPERAPP_DIALOG_CONTRACT_ID = "@mozilla.org/helperapplauncherdialog;1"; +const MOCK_HELPERAPP_DIALOG_CID = + SpecialPowers.wrap(SpecialPowers.Components) + .ID("{391832c8-5232-4676-b838-cc8ad373f3d8}"); + +var registrar = SpecialPowers.wrap(Components).manager + .QueryInterface(Ci.nsIComponentRegistrar); + +var helperAppDlgPromise = new Promise(function(resolve) { + var mockHelperAppService; + + function HelperAppLauncherDialog() { + } + + HelperAppLauncherDialog.prototype = { + show: function(aLauncher, aWindowContext, aReason) { + ok(true, "Whether showing Dialog"); + resolve(); + registrar.unregisterFactory(MOCK_HELPERAPP_DIALOG_CID, + mockHelperAppService); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]) + }; + + mockHelperAppService = XPCOMUtils._getFactory(HelperAppLauncherDialog); + registrar.registerFactory(MOCK_HELPERAPP_DIALOG_CID, "", + HELPERAPP_DIALOG_CONTRACT_ID, + mockHelperAppService); +}); + +add_task(function*() { + let promise = new Promise(function(resolve) { + let iframe = document.createElement("iframe"); + iframe.onload = function() { + is(iframe.contentDocument.getElementById("edit").innerText, "abc", + "load iframe source"); + resolve(); + }; + iframe.id = "testframe"; + iframe.src = "data:text/html,<div id=edit contenteditable=true>abc</div>"; + document.body.appendChild(iframe); + }); + + yield promise; + + let iframe = document.getElementById("testframe"); + let docShell = SpecialPowers.wrap(iframe.contentWindow) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + ok(docShell.hasEditingSession, "Should have editing session"); + + document.getElementById("testframe").src = + "data:application/octet-stream,TESTCONTENT"; + + yield helperAppDlgPromise; + + ok(docShell.hasEditingSession, "Should have editing session"); +}); +</script> +</body> +</html> diff --git a/editor/composer/test/test_bug338427.html b/editor/composer/test/test_bug338427.html new file mode 100644 index 000000000..f16194b3d --- /dev/null +++ b/editor/composer/test/test_bug338427.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=338427 +--> +<head> + <title>Test for Bug 338427</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=338427">Mozilla Bug 338427</a> +<p id="display"></p> +<div id="content"> +<textarea id="editor" lang="testing-XX" spellcheck="true"></textarea> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 338427 **/ +function init() { + var onSpellCheck = + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck; + var textarea = document.getElementById("editor"); + var editor = SpecialPowers.wrap(textarea).editor; + var spellchecker = editor.getInlineSpellChecker(true); + spellchecker.enableRealTimeSpell = true; + textarea.focus(); + + onSpellCheck(textarea, function () { + var list = {}, count = {}; + spellchecker.spellChecker.GetDictionaryList(list, count); + ok(count.value > 0, "At least one dictionary should be present"); + + var lang = list.value[0]; + spellchecker.spellChecker.SetCurrentDictionary(lang); + + onSpellCheck(textarea, function () { + try { + var dictionary = + spellchecker.spellChecker.GetCurrentDictionary(); + } catch(e) {} + is(dictionary, lang, "Unexpected spell check dictionary"); + + // This will clear the content preferences and reset "spellchecker.dictionary". + spellchecker.spellChecker.SetCurrentDictionary(""); + SimpleTest.finish(); + }); + }); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(init); + +</script> +</pre> +</body> +</html> + diff --git a/editor/composer/test/test_bug348497.html b/editor/composer/test/test_bug348497.html new file mode 100644 index 000000000..9483b727a --- /dev/null +++ b/editor/composer/test/test_bug348497.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=348497 +--> +<head> + <title>Test for Bug 348497</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=348497">Mozilla Bug 348497</a> +<p id="display"></p> +<div id="content"> + This page should not crash Mozilla<br> + <iframe id="testIframe"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 348497 **/ +function doe() { + document.getElementById('testIframe').style.display = 'block'; + document.getElementById('testIframe').contentDocument.designMode = 'on'; +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doe); +addLoadEvent(function() { ok(true, "enabling designmode on an iframe onload does not crash Mozilla")}); +addLoadEvent(SimpleTest.finish); + +</script> +</pre> +</body> +</html> + diff --git a/editor/composer/test/test_bug384147.html b/editor/composer/test/test_bug384147.html new file mode 100644 index 000000000..35f0e533e --- /dev/null +++ b/editor/composer/test/test_bug384147.html @@ -0,0 +1,204 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=384147 +--> +<head> + <title>Test for Bug 384147</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384147">Mozilla Bug 384147</a> +<p id="display"></p> +<div id="content" style="display: block"> +<div contentEditable id="editor"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript;version=1.7"> + +/** Test for Bug 384147 **/ + +SimpleTest.waitForExplicitFinish(); + +var editor = document.getElementById("editor"); + +editor.innerHTML = "<ol><li>Item 1</li><li>Item 2</li><ol><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>"; +editor.focus(); + +// If executed directly, a race condition exists that will cause execCommand +// to fail occasionally (but often). Defer test execution to page load. +addLoadEvent(function() { + + var sel = window.getSelection(); + + // Test the effect that the tab key has on list items. Each test is + // documented with the initial state of the list on the left, and the + // expected state of the list on the right. {\t} indicates the list item + // that will be indented. {\st} indicates that a shift-tab will be simulated + // on that list item, outdenting it. + // + // Note: any test failing will likely result in all following tests failing + // as well, since each test depends on the document being in a given state. + // Unfortunately, due to the problems getting document focus and key events + // to fire consistently, it's difficult to reset state between tests. + // If there are test failures here, only debug the first test failure. + + // *** test 1 *** + // 1. Item 1 1. Item 1 + // 2. {\t}Item 2 1. Item 2 + // 1. Item 3 2. Item 3 + // * Item 4 * Item 4 + // * Item 5 * Item 5 + sel.removeAllRanges(); + sel.selectAllChildren(editor.getElementsByTagName("li")[1]); + document.execCommand("indent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>", + "html output doesn't match expected value in test 1"); + + // *** test 2 *** + // 1. Item 1 1. Item 1 + // 1. Item 2 1. Item 2 + // 2. {\t}Item 3 1. Item 3 + // * Item 4 * Item 4 + // * Item 5 * Item 5 + sel.removeAllRanges(); + sel.selectAllChildren(editor.getElementsByTagName("li")[2]); + document.execCommand("indent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><ol><li>Item 3</li></ol></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>", + "html output doesn't match expected value in test 2"); + + // *** test 3 *** + // 1. Item 1 1. Item 1 + // 1. Item 2 1. Item 2 + // 1. {\st}Item 3 2. Item 3 + // * Item 4 * Item 4 + // * Item 5 * Item 5 + document.execCommand("outdent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>", + "html output doesn't match expected value in test 3"); + + // *** test 4 *** + // 1. Item 1 1. Item 1 + // 1. Item 2 1. Item 2 + // 2. {\st}Item 3 2. Item 3 + // * Item 4 * Item 4 + // * Item 5 * Item 5 + document.execCommand("outdent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>", + "html output doesn't match expected value in test 4"); + + // *** test 5 *** + // 1. Item 1 1. Item 1 + // 1. {\st}Item 2 2. Item 2 + // 2. Item 3 3. Item 3 + // * Item 4 * Item 4 + // * Item 5 * Item 5 + sel.removeAllRanges(); + sel.selectAllChildren(editor.getElementsByTagName("li")[1]); + document.execCommand("outdent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>", + "html output doesn't match expected value in test 5"); + + // *** test 6 *** + // 1. Item 1 1. Item 1 + // 2. {\t}Item 2 1. Item 2 + // 3. Item 3 2. Item 3 + // * Item 4 * Item 4 + // * Item 5 * Item 5 + document.execCommand("indent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>", + "html output doesn't match expected value in test 6"); + + // *** test 7 *** + // 1. Item 1 1. Item 1 + // 1. Item 2 1. Item 2 + // 2. {\t}Item 3 2. Item 3 + // * Item 4 * Item 4 + // * Item 5 * Item 5 + sel.removeAllRanges(); + sel.selectAllChildren(editor.getElementsByTagName("li")[2]); + document.execCommand("indent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>", + "html output doesn't match expected value in test 7"); + + // That covers the basics of merging lists on indent and outdent. + // We also want to check that ul / ol lists won't be merged together, + // since they're different types of lists. + // *** test 8 *** + // 1. Item 1 1. Item 1 + // 1. Item 2 1. Item 2 + // 2. Item 3 2. Item 3 + // * {\t}Item 4 * Item 4 + // * Item 5 * Item 5 + sel.removeAllRanges(); + sel.selectAllChildren(editor.getElementsByTagName("li")[3]); + document.execCommand("indent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li></ul><li>Item 5</li></ul>", + "html output doesn't match expected value in test 8"); + + // Better test merging with <ul> rather than <ol> too. + // *** test 9 *** + // 1. Item 1 1. Item 1 + // 1. Item 2 1. Item 2 + // 2. Item 3 2. Item 3 + // * Item 4 * Item 4 + // * {\t}Item 5 * Item 5 + sel.removeAllRanges(); + sel.selectAllChildren(editor.getElementsByTagName("li")[4]); + document.execCommand("indent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>", + "html output doesn't match expected value in test 9"); + + // Same test as test 8, but with outdent rather than indent. + // *** test 10 *** + // 1. Item 1 1. Item 1 + // 1. Item 2 1. Item 2 + // 2. Item 3 2. Item 3 + // * {\st}Item 4 * Item 4 + // * Item 5 * Item 5 + sel.removeAllRanges(); + sel.selectAllChildren(editor.getElementsByTagName("li")[3]); + document.execCommand("outdent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>", + "html output doesn't match expected value in test 10"); + + // Test indenting multiple items at once. Hold down "shift" and select + // upwards to get all the <ol> items and the first <ul> item. + // *** test 11 *** + // 1. Item 1 1. Item 1 + // 1. {\t}Item 2 1. Item 2 + // 2. {\t}Item 3 2. Item 3 + // * {\t}Item 4 * Item 4 + // * Item 5 * Item 5 + sel.removeAllRanges(); + var range = document.createRange(); + range.setStart(editor.getElementsByTagName("li")[1], 0); + range.setEnd(editor.getElementsByTagName("li")[3], editor.getElementsByTagName("li")[3].childNodes.length); + sel.addRange(range); + document.execCommand("indent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><ol><li>Item 2</li><li>Item 3</li></ol></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>", + "html output doesn't match expected value in test 11"); + + // Test outdenting multiple items at once. Selection is already ready... + // *** test 12 *** + // 1. Item 1 1. Item 1 + // 1. {\st}Item 2 1. Item 2 + // 2. {\st}Item 3 2. Item 3 + // * {\st}Item 4 * Item 4 + // * Item 5 * Item 5 + document.execCommand("outdent", false, null); + ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>", + "html output doesn't match expected value in test 12"); + + SimpleTest.finish(); +}); + + + +</script> +</pre> +</body> +</html> + diff --git a/editor/composer/test/test_bug389350.html b/editor/composer/test/test_bug389350.html new file mode 100644 index 000000000..bf1d514ad --- /dev/null +++ b/editor/composer/test/test_bug389350.html @@ -0,0 +1,33 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=389350 +--> +<head> +<title>Test for Bug 389350</title> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<script type="text/javascript"> + +function runTest() { + var e = document.getElementById("edit"); + e.contentDocument.designMode='on'; + e.style.display='block'; + e.focus(); + sendString('abc'); + var expected = "<head></head><body>abc</body>"; + var result = e.contentDocument.documentElement.innerHTML; + is(result, expected, "iframe with designmode on had incorrect content"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(runTest); +</script> + +</head> +<body id="body"> +<iframe id="edit" width="200" height="100" style="display: none;" src=""> +</body> +</html> diff --git a/editor/composer/test/test_bug434998.xul b/editor/composer/test/test_bug434998.xul new file mode 100644 index 000000000..4a384ac1c --- /dev/null +++ b/editor/composer/test/test_bug434998.xul @@ -0,0 +1,109 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" + type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=434998 +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Mozilla Bug 434998" onload="runTest();"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=434998" + target="_blank">Mozilla Bug 434998</a> + <p/> + <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="editor" + type="content-primary" + editortype="html" + style="width: 400px; height: 100px; border: thin solid black"/> + <p/> + <pre id="test"> + </pre> + </body> + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function EditorContentListener(aEditor) + { + this.init(aEditor); + } + + EditorContentListener.prototype = { + init : function(aEditor) + { + this.mEditor = aEditor; + }, + + QueryInterface : function(aIID) + { + if (aIID.equals(Components.interfaces.nsIWebProgressListener) || + aIID.equals(Components.interfaces.nsISupportsWeakReference) || + aIID.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + }, + + onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus) + { + if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) + { + var editor = this.mEditor.getEditor(this.mEditor.contentWindow); + if (editor) { + // Should not throw + var threw = false; + try { + this.mEditor.contentDocument.execCommand("bold", false, null); + } catch (e) { + threw = true; + } + ok(!threw, "The execCommand API should work on <xul:editor>"); + progress.removeProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL); + SimpleTest.finish(); + } + } + }, + + + onProgressChange : function(aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) + { + }, + + onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags) + { + }, + + onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) + { + }, + + onSecurityChange : function(aWebProgress, aRequest, aState) + { + }, + + mEditor: null + }; + + var progress, progressListener; + + function runTest() { + var newEditorElement = document.getElementById("editor"); + newEditorElement.makeEditable("html", true); + var docShell = newEditorElement.boxObject.docShell; + progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress); + progressListener = new EditorContentListener(newEditorElement); + progress.addProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL); + newEditorElement.setAttribute("src", "data:text/html,"); + } +]]> +</script> +</window> diff --git a/editor/composer/test/test_bug519928.html b/editor/composer/test/test_bug519928.html new file mode 100644 index 000000000..3b1a9ba17 --- /dev/null +++ b/editor/composer/test/test_bug519928.html @@ -0,0 +1,123 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=519928 +--> +<head> + <title>Test for Bug 519928</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=519928">Mozilla Bug 519928</a> +<p id="display"></p> +<div id="content"> +<iframe id="load-frame"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var iframe = document.getElementById("load-frame"); + +function enableJS() { allowJS(true, iframe); } +function disableJS() { allowJS(false, iframe); } +function allowJS(allow, frame) { + SpecialPowers.wrap(frame.contentWindow) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShell) + .allowJavascript = allow; +} + +function expectJSAllowed(allowed, testCondition, callback) { + window.ICanRunMyJS = false; + var self_ = window; + testCondition(); + + var doc = iframe.contentDocument; + doc.body.innerHTML = "<iframe></iframe>"; + var innerFrame = doc.querySelector("iframe"); + innerFrame.addEventListener("load", function() { + innerFrame.removeEventListener("load", arguments.callee, false); + + var msg = "The inner iframe should" + (allowed ? "" : " not") + " be able to run Javascript"; + is(self_.ICanRunMyJS, allowed, msg); + callback(); + }, false); + var iframeSrc = "data:text/html,<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>"; + innerFrame.src = iframeSrc; +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var enterDesignMode = function() { document.designMode = "on"; }; + var leaveDesignMode = function() { document.designMode = "off"; }; + expectJSAllowed(false, disableJS, function() { + expectJSAllowed(true, enableJS, function() { + expectJSAllowed(true, enterDesignMode, function() { + expectJSAllowed(true, leaveDesignMode, function() { + expectJSAllowed(false, disableJS, function() { + expectJSAllowed(false, enterDesignMode, function() { + expectJSAllowed(false, leaveDesignMode, function() { + expectJSAllowed(true, enableJS, function() { + enterDesignMode = function() { iframe.contentDocument.designMode = "on"; }; + leaveDesignMode = function() { iframe.contentDocument.designMode = "off"; }; + expectJSAllowed(false, disableJS, function() { + expectJSAllowed(true, enableJS, function() { + expectJSAllowed(true, enterDesignMode, function() { + expectJSAllowed(true, leaveDesignMode, function() { + expectJSAllowed(false, disableJS, function() { + expectJSAllowed(false, enterDesignMode, function() { + expectJSAllowed(false, leaveDesignMode, function() { + expectJSAllowed(true, enableJS, function() { + testDocumentDisabledJS(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); +}); + +function testDocumentDisabledJS() { + window.ICanRunMyJS = false; + var self_ = window; + // Ensure design modes are disabled + document.designMode = "off"; + iframe.contentDocument.designMode = "off"; + + // Javascript enabled on the main iframe + enableJS(); + + var doc = iframe.contentDocument; + doc.body.innerHTML = "<iframe></iframe>"; + var innerFrame = doc.querySelector("iframe"); + + // Javascript disabled on the innerFrame. + allowJS(false, innerFrame); + + innerFrame.addEventListener("load", function() { + innerFrame.removeEventListener("load", arguments.callee, false); + + var msg = "The inner iframe should not be able to run Javascript"; + is(self_.ICanRunMyJS, false, msg); + SimpleTest.finish(); + }, false); + var iframeSrc = "data:text/html,<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>"; + innerFrame.src = iframeSrc; +} + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug678842.html b/editor/composer/test/test_bug678842.html new file mode 100644 index 000000000..226b25ee2 --- /dev/null +++ b/editor/composer/test/test_bug678842.html @@ -0,0 +1,105 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=678842 +--> +<head> + <title>Test for Bug 678842</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=678842">Mozilla Bug 678842</a> +<p id="display"></p> +<iframe id="content"></iframe> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 678842 **/ +SimpleTest.waitForExplicitFinish(); +var content = document.getElementById('content'); +// load a subframe containing an editor with a defined unknown lang. At first +// load, it will set dictionary to en-US. At second load, it will return current +// dictionary. So, we can check, dictionary is correctly remembered between +// loads. + +var firstLoad = true; +var script; + +var loadListener = function(evt) { + if (firstLoad) { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); + + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); + + // Install en-GB dictionary. + en_GB = dir.clone(); + en_GB.append("en-GB"); + hunspell.addDirectory(en_GB); + + addMessageListener("en_GB-exists", () => en_GB.exists()); + addMessageListener("destroy", () => hunspell.removeDirectory(en_GB)); + }); + is(script.sendSyncMessage("en_GB-exists")[0][0], true, + "true expected (en-GB directory should exist)"); + } + + var doc = evt.target.contentDocument; + var elem = doc.getElementById('textarea'); + var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; + editor.setSpellcheckUserOverride(true); + var inlineSpellChecker = editor.getInlineSpellChecker(true); + + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(elem, function () { + var spellchecker = inlineSpellChecker.spellChecker; + try { + var currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + if (!currentDictonary) { + spellchecker.SetCurrentDictionary('en-US'); + } + + if (firstLoad) { + firstLoad = false; + + // First time around, the dictionary defaults to the locale. + is (currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US"); + + // Select en-GB. + spellchecker.SetCurrentDictionary("en-GB"); + + content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=false'; + } else { + is (currentDictonary, "en-GB", "unexpected lang " + currentDictonary + " instead of en-GB"); + content.removeEventListener('load', loadListener, false); + + // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests. + script.sendSyncMessage("destroy"); + + // This will clear the content preferences and reset "spellchecker.dictionary". + spellchecker.SetCurrentDictionary(""); + SimpleTest.finish(); + } + }); +} + +content.addEventListener('load', loadListener, false); + +content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=true'; + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug697981.html b/editor/composer/test/test_bug697981.html new file mode 100644 index 000000000..f9417acb0 --- /dev/null +++ b/editor/composer/test/test_bug697981.html @@ -0,0 +1,133 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=697981 +--> +<head> + <title>Test for Bug 697981</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=697981">Mozilla Bug 697981</a> +<p id="display"></p> +</div> + +<textarea id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</textarea> +<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function getMisspelledWords(editor) { + return editor.selectionController.getSelection(SpecialPowers.Ci.nsISelectionController.SELECTION_SPELLCHECK).toString(); +} + +var elem_de; +var editor_de; +var script; + +var onSpellCheck = + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck; + +/** Test for Bug 697981 **/ +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); + + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); + + // Install de-DE dictionary. + var de_DE = dir.clone(); + de_DE.append("de-DE"); + hunspell.addDirectory(de_DE); + + addMessageListener("de_DE-exists", () => de_DE.exists()); + addMessageListener("destroy", () => hunspell.removeDirectory(de_DE)); + }); + is(script.sendSyncMessage("de_DE-exists")[0][0], true, + "true expected (de_DE directory should exist)"); + + document.getElementById('de-DE').focus(); +}); + +function deFocus() { + elem_de = document.getElementById('de-DE'); + editor_de = SpecialPowers.wrap(elem_de).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; + editor_de.setSpellcheckUserOverride(true); + var inlineSpellChecker = editor_de.getInlineSpellChecker(true); + + onSpellCheck(elem_de, function () { + var spellchecker = inlineSpellChecker.spellChecker; + try { + var currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + // Check that the German dictionary is loaded and that the spell check has worked. + is(currentDictonary, "de-DE", "expected de-DE"); + is(getMisspelledWords(editor_de), "German", "one misspelled word expected: German"); + + // Now focus the other textarea, which requires English spelling. + document.getElementById('en-US').focus(); + }); +} + +function enFocus() { + var elem_en = document.getElementById('en-US'); + var editor_en = SpecialPowers.wrap(elem_en).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; + editor_en.setSpellcheckUserOverride(true); + var inlineSpellChecker = editor_en.getInlineSpellChecker(true); + + onSpellCheck(elem_en, function () { + var spellchecker = inlineSpellChecker.spellChecker; + try { + currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + // Check that the English dictionary is loaded and that the spell check has worked. + is(currentDictonary, "en-US", "expected en-US"); + is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword"); + + // So far all was boring. The important thing is whether the spell check result + // in the de-DE editor is still the same. After losing focus, no spell check + // updates should take place there. + is(getMisspelledWords(editor_de), "German", "one misspelled word expected: German"); + + // Remove the fake de_DE dictionary again. + script.sendSyncMessage("destroy"); + + // Focus again, so the spelling gets updated, but before we need to kill the focus handler. + elem_de.onfocus = null; + elem_de.blur(); + elem_de.focus(); + + // After removal, the de_DE editor should refresh the spelling with en-US. + onSpellCheck(elem_de, function () { + spellchecker = inlineSpellChecker.spellChecker; + try { + currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + // Check that the default English dictionary is loaded and that the spell check has worked. + is(currentDictonary, "en-US", "expected en-US"); + is(getMisspelledWords(editor_de), "heute" + "ist" + "ein" + "guter", + "some misspelled words expected: heute ist ein guter"); + + SimpleTest.finish(); + }); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug717433.html b/editor/composer/test/test_bug717433.html new file mode 100644 index 000000000..59ad9b247 --- /dev/null +++ b/editor/composer/test/test_bug717433.html @@ -0,0 +1,107 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=717433 +--> +<head> + <title>Test for Bug 717433</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717433">Mozilla Bug 717433</a> +<p id="display"></p> +<iframe id="content"></iframe> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 717433 **/ +SimpleTest.waitForExplicitFinish(); +var content = document.getElementById('content'); +// Load a subframe containing an editor with language "en". At first +// load, it will set the dictionary to en-GB or en-US. We set the other one. +// At second load, it will return the current dictionary. We can check that the +// dictionary is correctly remembered between loads. + +var firstLoad = true; +var expected = ""; +var script; + +var loadListener = function(evt) { + + if (firstLoad) { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); + + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); + + // Install en-GB dictionary. + var en_GB = dir.clone(); + en_GB.append("en-GB"); + hunspell.addDirectory(en_GB); + + addMessageListener("en_GB-exists", () => en_GB.exists()); + addMessageListener("destroy", () => hunspell.removeDirectory(en_GB)); + }); + is(script.sendSyncMessage("en_GB-exists")[0][0], true, + "true expected (en-GB directory should exist)"); + } + + var doc = evt.target.contentDocument; + var elem = doc.getElementById('textarea'); + var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; + editor.setSpellcheckUserOverride(true); + var inlineSpellChecker = editor.getInlineSpellChecker(true); + + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(elem, function () { + var spellchecker = inlineSpellChecker.spellChecker; + try { + var currentDictonary = spellchecker.GetCurrentDictionary(); + } catch(e) {} + + if (firstLoad) { + firstLoad = false; + + // First time around, we get a random dictionary based on the language "en". + if (currentDictonary == "en-GB") { + spellchecker.SetCurrentDictionary("en-US"); + expected = "en-US"; + } else if (currentDictonary == "en-US") { + spellchecker.SetCurrentDictionary("en-GB"); + expected = "en-GB"; + } else { + is(true, false, "Neither en-US nor en-GB are current"); + } + content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug717433_subframe.html?firstload=false'; + } else { + is(currentDictonary, expected, expected + " expected"); + content.removeEventListener('load', loadListener, false); + + // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests. + script.sendSyncMessage("destroy"); + + // This will clear the content preferences and reset "spellchecker.dictionary". + spellchecker.SetCurrentDictionary(""); + SimpleTest.finish(); + } + }); +} + +content.addEventListener('load', loadListener, false); + +content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug717433_subframe.html?firstload=true'; + +</script> +</pre> +</body> +</html> diff --git a/editor/composer/test/test_bug738440.html b/editor/composer/test/test_bug738440.html new file mode 100644 index 000000000..a021906cf --- /dev/null +++ b/editor/composer/test/test_bug738440.html @@ -0,0 +1,37 @@ +<!doctype html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=738440 +--> +<title>Test for Bug 738440</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=738440">Mozilla Bug 738440</a> +<div contenteditable></div> +<script> + +/** Test for Bug 738440 **/ +document.execCommand("stylewithcss", false, "true"); +is(document.queryCommandState("stylewithcss"), true, + "setting stylewithcss to true should cause its state to be true"); +is(document.queryCommandState("usecss"), false, + "usecss state should always be false"); + +document.execCommand("stylewithcss", false, "false"); +is(document.queryCommandState("stylewithcss"), false, + "setting stylewithcss to false should cause its state to be false"); +is(document.queryCommandState("usecss"), false, + "usecss state should always be false"); + +document.execCommand("usecss", false, "true"); +is(document.queryCommandState("stylewithcss"), false, + "setting usecss to true should cause stylewithcss state to be false"); +is(document.queryCommandState("usecss"), false, + "usecss state should always be false"); + +document.execCommand("usecss", false, "false"); +is(document.queryCommandState("stylewithcss"), true, + "setting usecss to false should cause stylewithcss state to be true"); +is(document.queryCommandState("usecss"), false, + "usecss state should always be false"); + +</script> |