fune/editor/composer/nsEditingSession.cpp
Cristina Horotan 5f4356e527 Backed out 9 changesets (bug 1810141) for several test failures on a CLOSED TREE
Backed out changeset 8781a0d1254d (bug 1810141)
Backed out changeset 131037295784 (bug 1810141)
Backed out changeset 3852fbe290f4 (bug 1810141)
Backed out changeset 118f131a524a (bug 1810141)
Backed out changeset ab5d76846e10 (bug 1810141)
Backed out changeset dce3aa683445 (bug 1810141)
Backed out changeset 4dc41d90dbb3 (bug 1810141)
Backed out changeset 50b57ba1a061 (bug 1810141)
Backed out changeset 569de94781e4 (bug 1810141)
2023-02-13 16:05:30 +02:00

1299 lines
43 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=78: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <string.h> // for nullptr, strcmp
#include "imgIContainer.h" // for imgIContainer, etc
#include "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater
#include "mozilla/FlushType.h" // for FlushType::Frames
#include "mozilla/HTMLEditor.h" // for HTMLEditor
#include "mozilla/mozalloc.h" // for operator new
#include "mozilla/PresShell.h" // for PresShell
#include "nsAString.h"
#include "nsBaseCommandController.h" // for nsBaseCommandController
#include "nsCommandManager.h" // for nsCommandManager
#include "nsComponentManagerUtils.h" // for do_CreateInstance
#include "nsContentUtils.h"
#include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc
#include "nsDocShell.h" // for nsDocShell
#include "nsEditingSession.h"
#include "nsError.h" // for NS_ERROR_FAILURE, NS_OK, etc
#include "nsIChannel.h" // for nsIChannel
#include "nsIContentViewer.h" // for nsIContentViewer
#include "nsIControllers.h" // for nsIControllers
#include "nsID.h" // for NS_GET_IID, etc
#include "nsHTMLDocument.h" // for nsHTMLDocument
#include "nsIDocShell.h" // for nsIDocShell
#include "mozilla/dom/Document.h" // for Document
#include "nsIEditor.h" // for nsIEditor
#include "nsIInterfaceRequestorUtils.h" // for do_GetInterface
#include "nsIRefreshURI.h" // for nsIRefreshURI
#include "nsIRequest.h" // for nsIRequest
#include "nsITimer.h" // for nsITimer, etc
#include "nsIWeakReference.h" // for nsISupportsWeakReference, etc
#include "nsIWebNavigation.h" // for nsIWebNavigation
#include "nsIWebProgress.h" // for nsIWebProgress, etc
#include "nsLiteralString.h" // for NS_LITERAL_STRING
#include "nsPIDOMWindow.h" // for nsPIDOMWindow
#include "nsPresContext.h" // for nsPresContext
#include "nsReadableUtils.h" // for AppendUTF16toUTF8
#include "nsStringFwd.h" // for nsString
#include "mozilla/dom/BrowsingContext.h" // for BrowsingContext
#include "mozilla/dom/Selection.h" // for AutoHideSelectionChanges, etc
#include "mozilla/dom/WindowContext.h" // for WindowContext
#include "nsFrameSelection.h" // for nsFrameSelection
#include "nsBaseCommandController.h" // for nsBaseCommandController
#include "mozilla/dom/LoadURIOptionsBinding.h"
class nsISupports;
class nsIURI;
using namespace mozilla;
using namespace mozilla::dom;
/*---------------------------------------------------------------------------
nsEditingSession
----------------------------------------------------------------------------*/
nsEditingSession::nsEditingSession()
: mDoneSetup(false),
mCanCreateEditor(false),
mInteractive(false),
mMakeWholeDocumentEditable(true),
mDisabledJSAndPlugins(false),
mScriptsEnabled(true),
mPluginsEnabled(true),
mProgressListenerRegistered(false),
mImageAnimationMode(0),
mEditorFlags(0),
mEditorStatus(eEditorOK),
mBaseCommandControllerId(0),
mDocStateControllerId(0),
mHTMLCommandControllerId(0) {}
/*---------------------------------------------------------------------------
~nsEditingSession
----------------------------------------------------------------------------*/
nsEditingSession::~nsEditingSession() {
// Must cancel previous timer?
if (mLoadBlankDocTimer) mLoadBlankDocTimer->Cancel();
}
NS_IMPL_ISUPPORTS(nsEditingSession, nsIEditingSession, nsIWebProgressListener,
nsISupportsWeakReference)
/*---------------------------------------------------------------------------
MakeWindowEditable
aEditorType string, "html" "htmlsimple" "text" "textsimple"
void makeWindowEditable(in nsIDOMWindow aWindow, in string aEditorType,
in boolean aDoAfterUriLoad,
in boolean aMakeWholeDocumentEditable,
in boolean aInteractive);
----------------------------------------------------------------------------*/
#define DEFAULT_EDITOR_TYPE "html"
NS_IMETHODIMP
nsEditingSession::MakeWindowEditable(mozIDOMWindowProxy* aWindow,
const char* aEditorType,
bool aDoAfterUriLoad,
bool aMakeWholeDocumentEditable,
bool aInteractive) {
mEditorType.Truncate();
mEditorFlags = 0;
NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
auto* window = nsPIDOMWindowOuter::From(aWindow);
// disable plugins
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
mDocShell = do_GetWeakReference(docShell);
mInteractive = aInteractive;
mMakeWholeDocumentEditable = aMakeWholeDocumentEditable;
nsresult rv;
if (!mInteractive) {
rv = DisableJSAndPlugins(window->GetCurrentInnerWindow());
NS_ENSURE_SUCCESS(rv, rv);
}
// Always remove existing editor
TearDownEditorOnWindow(aWindow);
// Tells embedder that startup is in progress
mEditorStatus = eEditorCreationInProgress;
// temporary to set editor type here. we will need different classes soon.
if (!aEditorType) aEditorType = DEFAULT_EDITOR_TYPE;
mEditorType = aEditorType;
// if all this does is setup listeners and I don't need listeners,
// can't this step be ignored?? (based on aDoAfterURILoad)
rv = PrepareForEditing(window);
NS_ENSURE_SUCCESS(rv, rv);
// set the flag on the docShell to say that it's editable
rv = docShell->MakeEditable(aDoAfterUriLoad);
NS_ENSURE_SUCCESS(rv, rv);
// Setup commands common to plaintext and html editors,
// including the document creation observers
// the first is an editing controller
rv = SetupEditorCommandController(
nsBaseCommandController::CreateEditingController, aWindow,
static_cast<nsIEditingSession*>(this), &mBaseCommandControllerId);
NS_ENSURE_SUCCESS(rv, rv);
// The second is a controller to monitor doc state,
// such as creation and "dirty flag"
rv = SetupEditorCommandController(
nsBaseCommandController::CreateHTMLEditorDocStateController, aWindow,
static_cast<nsIEditingSession*>(this), &mDocStateControllerId);
NS_ENSURE_SUCCESS(rv, rv);
// aDoAfterUriLoad can be false only when making an existing window editable
if (!aDoAfterUriLoad) {
rv = SetupEditorOnWindow(MOZ_KnownLive(*window));
// mEditorStatus is set to the error reason
// Since this is used only when editing an existing page,
// it IS ok to destroy current editor
if (NS_FAILED(rv)) {
TearDownEditorOnWindow(aWindow);
}
}
return rv;
}
nsresult nsEditingSession::DisableJSAndPlugins(nsPIDOMWindowInner* aWindow) {
WindowContext* wc = aWindow->GetWindowContext();
BrowsingContext* bc = wc->GetBrowsingContext();
mScriptsEnabled = wc->GetAllowJavascript();
MOZ_TRY(wc->SetAllowJavascript(false));
// Disable plugins in this document:
mPluginsEnabled = bc->GetAllowPlugins();
MOZ_TRY(bc->SetAllowPlugins(false));
mDisabledJSAndPlugins = true;
return NS_OK;
}
nsresult nsEditingSession::RestoreJSAndPlugins(nsPIDOMWindowInner* aWindow) {
if (!mDisabledJSAndPlugins) {
return NS_OK;
}
mDisabledJSAndPlugins = false;
if (NS_WARN_IF(!aWindow)) {
// DetachFromWindow may call this method with nullptr.
return NS_ERROR_FAILURE;
}
WindowContext* wc = aWindow->GetWindowContext();
BrowsingContext* bc = wc->GetBrowsingContext();
MOZ_TRY(wc->SetAllowJavascript(mScriptsEnabled));
// Disable plugins in this document:
return bc->SetAllowPlugins(mPluginsEnabled);
}
/*---------------------------------------------------------------------------
WindowIsEditable
boolean windowIsEditable (in nsIDOMWindow aWindow);
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::WindowIsEditable(mozIDOMWindowProxy* aWindow,
bool* outIsEditable) {
NS_ENSURE_STATE(aWindow);
nsCOMPtr<nsIDocShell> docShell =
nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
NS_ENSURE_STATE(docShell);
return docShell->GetEditable(outIsEditable);
}
bool IsSupportedTextType(const nsAString& aMIMEType) {
// These are MIME types that are automatically parsed as "text/plain"
// and thus we can edit them as plaintext
// Note: in older versions, we attempted to convert the mimetype of
// the network channel for these and "text/xml" to "text/plain",
// but further investigation reveals that strategy doesn't work
static constexpr nsLiteralString sSupportedTextTypes[] = {
u"text/plain"_ns,
u"text/css"_ns,
u"text/rdf"_ns,
u"text/xsl"_ns,
u"text/javascript"_ns, // obsolete type
u"text/ecmascript"_ns, // obsolete type
u"application/javascript"_ns,
u"application/ecmascript"_ns,
u"application/x-javascript"_ns, // obsolete type
u"text/xul"_ns // obsolete type
};
for (const nsLiteralString& supportedTextType : sSupportedTextTypes) {
if (aMIMEType.Equals(supportedTextType)) {
return true;
}
}
return false;
}
nsresult nsEditingSession::SetupEditorOnWindow(nsPIDOMWindowOuter& aWindow) {
mDoneSetup = true;
// MIME CHECKING
// must get the content type
// Note: the doc gets this from the network channel during StartPageLoad,
// so we don't have to get it from there ourselves
nsAutoString mimeType;
// then lets check the mime type
if (RefPtr<Document> doc = aWindow.GetDoc()) {
doc->GetContentType(mimeType);
if (IsSupportedTextType(mimeType)) {
mEditorType.AssignLiteral("text");
mimeType.AssignLiteral("text/plain");
} else if (!doc->IsHTMLOrXHTML()) {
// Neither an acceptable text or html type.
mEditorStatus = eEditorErrorCantEditMimeType;
// Turn editor into HTML -- we will load blank page later
mEditorType.AssignLiteral("html");
mimeType.AssignLiteral("text/html");
}
// Flush out frame construction to make sure that the subframe's
// presshell is set up if it needs to be.
doc->FlushPendingNotifications(mozilla::FlushType::Frames);
if (mMakeWholeDocumentEditable) {
doc->SetEditableFlag(true);
// Enable usage of the execCommand API
doc->SetEditingState(Document::EditingState::eDesignMode);
}
}
bool needHTMLController = false;
if (mEditorType.EqualsLiteral("textmail")) {
mEditorFlags = nsIEditor::eEditorPlaintextMask |
nsIEditor::eEditorEnableWrapHackMask |
nsIEditor::eEditorMailMask;
} else if (mEditorType.EqualsLiteral("text")) {
mEditorFlags =
nsIEditor::eEditorPlaintextMask | nsIEditor::eEditorEnableWrapHackMask;
} else if (mEditorType.EqualsLiteral("htmlmail")) {
if (mimeType.EqualsLiteral("text/html")) {
needHTMLController = true;
mEditorFlags = nsIEditor::eEditorMailMask;
} else {
// Set the flags back to textplain.
mEditorFlags = nsIEditor::eEditorPlaintextMask |
nsIEditor::eEditorEnableWrapHackMask;
}
} else {
// Defaulted to html
needHTMLController = true;
}
if (mInteractive) {
mEditorFlags |= nsIEditor::eEditorAllowInteraction;
}
// make the UI state maintainer
RefPtr<ComposerCommandsUpdater> commandsUpdater =
new ComposerCommandsUpdater();
mComposerCommandsUpdater = commandsUpdater;
// now init the state maintainer
// This allows notification of error state
// even if we don't create an editor
commandsUpdater->Init(aWindow);
if (mEditorStatus != eEditorCreationInProgress) {
commandsUpdater->OnHTMLEditorCreated();
// At this point we have made a final decision that we don't support
// editing the current document. This is an internal failure state, but
// we return NS_OK to avoid throwing an exception from the designMode
// setter for web compatibility. The document editing APIs will tell the
// developer if editing has been disabled because we're in a document type
// that doesn't support editing.
return NS_OK;
}
// Create editor and do other things
// only if we haven't found some error above,
const RefPtr<nsDocShell> docShell = nsDocShell::Cast(aWindow.GetDocShell());
if (NS_WARN_IF(!docShell)) {
return NS_ERROR_FAILURE;
}
const RefPtr<PresShell> presShell = docShell->GetPresShell();
if (NS_WARN_IF(!presShell)) {
return NS_ERROR_FAILURE;
}
if (!mInteractive) {
// Disable animation of images in this document:
nsPresContext* presContext = presShell->GetPresContext();
NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
mImageAnimationMode = presContext->ImageAnimationMode();
presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
}
// Hide selection changes during initialization, in order to hide this
// from web pages.
RefPtr<nsFrameSelection> fs = presShell->FrameSelection();
NS_ENSURE_TRUE(fs, NS_ERROR_FAILURE);
AutoHideSelectionChanges hideSelectionChanges(fs);
nsCOMPtr<nsIContentViewer> contentViewer;
nsresult rv = docShell->GetContentViewer(getter_AddRefs(contentViewer));
if (NS_FAILED(rv) || NS_WARN_IF(!contentViewer)) {
NS_WARNING("nsDocShell::GetContentViewer() failed");
return rv;
}
const RefPtr<Document> doc = contentViewer->GetDocument();
if (NS_WARN_IF(!doc)) {
return NS_ERROR_FAILURE;
}
// create and set editor
// Try to reuse an existing editor
nsCOMPtr<nsIEditor> editor = do_QueryReferent(mExistingEditor);
RefPtr<HTMLEditor> htmlEditor = HTMLEditor::GetFrom(editor);
MOZ_ASSERT_IF(editor, htmlEditor);
if (htmlEditor) {
htmlEditor->PreDestroy();
} else {
htmlEditor = new HTMLEditor(*doc);
mExistingEditor =
do_GetWeakReference(static_cast<nsIEditor*>(htmlEditor.get()));
}
// set the editor on the docShell. The docShell now owns it.
rv = docShell->SetHTMLEditor(htmlEditor);
NS_ENSURE_SUCCESS(rv, rv);
// setup the HTML editor command controller
if (needHTMLController) {
// The third controller takes an nsIEditor as the context
rv = SetupEditorCommandController(
nsBaseCommandController::CreateHTMLEditorController, &aWindow,
static_cast<nsIEditor*>(htmlEditor), &mHTMLCommandControllerId);
NS_ENSURE_SUCCESS(rv, rv);
}
// Set mimetype on editor
rv = htmlEditor->SetContentsMIMEType(mimeType);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(docShell->HasContentViewer());
MOZ_ASSERT(contentViewer->GetDocument());
MOZ_DIAGNOSTIC_ASSERT(commandsUpdater == mComposerCommandsUpdater);
if (MOZ_UNLIKELY(commandsUpdater != mComposerCommandsUpdater)) {
commandsUpdater = mComposerCommandsUpdater;
}
rv = htmlEditor->Init(*doc, *commandsUpdater, mEditorFlags);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<Selection> selection = htmlEditor->GetSelection();
if (NS_WARN_IF(!selection)) {
return NS_ERROR_FAILURE;
}
// Set context on all controllers to be the editor
rv = SetEditorOnControllers(aWindow, htmlEditor);
NS_ENSURE_SUCCESS(rv, rv);
// Everything went fine!
mEditorStatus = eEditorOK;
// This will trigger documentCreation notification
return htmlEditor->PostCreate();
}
// Removes all listeners and controllers from aWindow and aEditor.
void nsEditingSession::RemoveListenersAndControllers(
nsPIDOMWindowOuter* aWindow, HTMLEditor* aHTMLEditor) {
if (!mComposerCommandsUpdater || !aHTMLEditor) {
return;
}
// Remove all the listeners
RefPtr<ComposerCommandsUpdater> composertCommandsUpdater =
std::move(mComposerCommandsUpdater);
MOZ_ASSERT(!mComposerCommandsUpdater);
aHTMLEditor->Detach(*composertCommandsUpdater);
// Remove editor controllers from the window now that we're not
// editing in that window any more.
RemoveEditorControllers(aWindow);
}
/*---------------------------------------------------------------------------
TearDownEditorOnWindow
void tearDownEditorOnWindow (in nsIDOMWindow aWindow);
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::TearDownEditorOnWindow(mozIDOMWindowProxy* aWindow) {
if (!mDoneSetup) {
return NS_OK;
}
NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
// Kill any existing reload timer
if (mLoadBlankDocTimer) {
mLoadBlankDocTimer->Cancel();
mLoadBlankDocTimer = nullptr;
}
mDoneSetup = false;
// Check if we're turning off editing (from contentEditable or designMode).
auto* window = nsPIDOMWindowOuter::From(aWindow);
RefPtr<Document> doc = window->GetDoc();
bool stopEditing = doc && doc->IsEditingOn();
if (stopEditing) {
RemoveWebProgressListener(window);
}
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
NS_ENSURE_STATE(docShell);
RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor();
if (stopEditing) {
doc->TearingDownEditor();
}
if (mComposerCommandsUpdater && htmlEditor) {
// Null out the editor on the controllers first to prevent their weak
// references from pointing to a destroyed editor.
SetEditorOnControllers(*window, nullptr);
}
// Null out the editor on the docShell to trigger PreDestroy which
// needs to happen before document state listeners are removed below.
docShell->SetEditor(nullptr);
RemoveListenersAndControllers(window, htmlEditor);
if (stopEditing) {
// Make things the way they were before we started editing.
RestoreJSAndPlugins(window->GetCurrentInnerWindow());
RestoreAnimationMode(window);
if (mMakeWholeDocumentEditable) {
doc->SetEditableFlag(false);
doc->SetEditingState(Document::EditingState::eOff);
}
}
return NS_OK;
}
/*---------------------------------------------------------------------------
GetEditorForFrame
nsIEditor getEditorForFrame (in nsIDOMWindow aWindow);
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::GetEditorForWindow(mozIDOMWindowProxy* aWindow,
nsIEditor** outEditor) {
if (NS_WARN_IF(!aWindow)) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIEditor> editor = GetHTMLEditorForWindow(aWindow);
editor.forget(outEditor);
return NS_OK;
}
/*---------------------------------------------------------------------------
OnStateChange
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::OnStateChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, uint32_t aStateFlags,
nsresult aStatus) {
#ifdef NOISY_DOC_LOADING
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
if (channel) {
nsAutoCString contentType;
channel->GetContentType(contentType);
if (!contentType.IsEmpty()) {
printf(" ++++++ MIMETYPE = %s\n", contentType.get());
}
}
#endif
//
// A Request has started...
//
if (aStateFlags & nsIWebProgressListener::STATE_START) {
#ifdef NOISY_DOC_LOADING
{
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
if (channel) {
nsCOMPtr<nsIURI> uri;
channel->GetURI(getter_AddRefs(uri));
if (uri) {
nsCString spec;
uri->GetSpec(spec);
printf(" **** STATE_START: CHANNEL URI=%s, flags=%x\n", spec.get(),
aStateFlags);
}
} else {
printf(" STATE_START: NO CHANNEL flags=%x\n", aStateFlags);
}
}
#endif
// Page level notification...
if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) {
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
StartPageLoad(channel);
#ifdef NOISY_DOC_LOADING
printf("STATE_START & STATE_IS_NETWORK flags=%x\n", aStateFlags);
#endif
}
// Document level notification...
if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT &&
!(aStateFlags & nsIWebProgressListener::STATE_RESTORING)) {
#ifdef NOISY_DOC_LOADING
printf("STATE_START & STATE_IS_DOCUMENT flags=%x\n", aStateFlags);
#endif
bool progressIsForTargetDocument =
IsProgressForTargetDocument(aWebProgress);
if (progressIsForTargetDocument) {
nsCOMPtr<mozIDOMWindowProxy> window;
aWebProgress->GetDOMWindow(getter_AddRefs(window));
auto* piWindow = nsPIDOMWindowOuter::From(window);
RefPtr<Document> doc = piWindow->GetDoc();
nsHTMLDocument* htmlDoc =
doc && doc->IsHTMLOrXHTML() ? doc->AsHTMLDocument() : nullptr;
if (htmlDoc && doc->IsWriting()) {
nsAutoString designMode;
htmlDoc->GetDesignMode(designMode);
if (designMode.EqualsLiteral("on")) {
// This notification is for data coming in through
// document.open/write/close(), ignore it.
return NS_OK;
}
}
mCanCreateEditor = true;
StartDocumentLoad(aWebProgress, progressIsForTargetDocument);
}
}
}
//
// A Request is being processed
//
else if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) {
if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
// document transfer started
}
}
//
// Got a redirection
//
else if (aStateFlags & nsIWebProgressListener::STATE_REDIRECTING) {
if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
// got a redirect
}
}
//
// A network or document Request has finished...
//
else if (aStateFlags & nsIWebProgressListener::STATE_STOP) {
#ifdef NOISY_DOC_LOADING
{
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
if (channel) {
nsCOMPtr<nsIURI> uri;
channel->GetURI(getter_AddRefs(uri));
if (uri) {
nsCString spec;
uri->GetSpec(spec);
printf(" **** STATE_STOP: CHANNEL URI=%s, flags=%x\n", spec.get(),
aStateFlags);
}
} else {
printf(" STATE_STOP: NO CHANNEL flags=%x\n", aStateFlags);
}
}
#endif
// Document level notification...
if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
EndDocumentLoad(aWebProgress, channel, aStatus,
IsProgressForTargetDocument(aWebProgress));
#ifdef NOISY_DOC_LOADING
printf("STATE_STOP & STATE_IS_DOCUMENT flags=%x\n", aStateFlags);
#endif
}
// Page level notification...
if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) {
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
(void)EndPageLoad(aWebProgress, channel, aStatus);
#ifdef NOISY_DOC_LOADING
printf("STATE_STOP & STATE_IS_NETWORK flags=%x\n", aStateFlags);
#endif
}
}
return NS_OK;
}
/*---------------------------------------------------------------------------
OnProgressChange
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::OnProgressChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
int32_t aCurSelfProgress,
int32_t aMaxSelfProgress,
int32_t aCurTotalProgress,
int32_t aMaxTotalProgress) {
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
return NS_OK;
}
/*---------------------------------------------------------------------------
OnLocationChange
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::OnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, nsIURI* aURI,
uint32_t aFlags) {
nsCOMPtr<mozIDOMWindowProxy> domWindow;
nsresult rv = aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
NS_ENSURE_SUCCESS(rv, rv);
auto* piWindow = nsPIDOMWindowOuter::From(domWindow);
RefPtr<Document> doc = piWindow->GetDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
doc->SetDocumentURI(aURI);
// Notify the location-changed observer that
// the document URL has changed
nsIDocShell* docShell = piWindow->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
RefPtr<nsCommandManager> commandManager = docShell->GetCommandManager();
commandManager->CommandStatusChanged("obs_documentLocationChanged");
return NS_OK;
}
/*---------------------------------------------------------------------------
OnStatusChange
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::OnStatusChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, nsresult aStatus,
const char16_t* aMessage) {
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
return NS_OK;
}
/*---------------------------------------------------------------------------
OnSecurityChange
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::OnSecurityChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, uint32_t aState) {
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
return NS_OK;
}
/*---------------------------------------------------------------------------
OnContentBlockingEvent
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
uint32_t aEvent) {
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
return NS_OK;
}
/*---------------------------------------------------------------------------
IsProgressForTargetDocument
Check that this notification is for our document.
----------------------------------------------------------------------------*/
bool nsEditingSession::IsProgressForTargetDocument(
nsIWebProgress* aWebProgress) {
nsCOMPtr<nsIWebProgress> editedWebProgress = do_QueryReferent(mDocShell);
return editedWebProgress == aWebProgress;
}
/*---------------------------------------------------------------------------
GetEditorStatus
Called during GetCommandStateParams("obs_documentCreated"...)
to determine if editor was created and document
was loaded successfully
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsEditingSession::GetEditorStatus(uint32_t* aStatus) {
NS_ENSURE_ARG_POINTER(aStatus);
*aStatus = mEditorStatus;
return NS_OK;
}
/*---------------------------------------------------------------------------
StartDocumentLoad
Called on start of load in a single frame
----------------------------------------------------------------------------*/
nsresult nsEditingSession::StartDocumentLoad(nsIWebProgress* aWebProgress,
bool aIsToBeMadeEditable) {
#ifdef NOISY_DOC_LOADING
printf("======= StartDocumentLoad ========\n");
#endif
NS_ENSURE_ARG_POINTER(aWebProgress);
if (aIsToBeMadeEditable) {
mEditorStatus = eEditorCreationInProgress;
}
return NS_OK;
}
/*---------------------------------------------------------------------------
EndDocumentLoad
Called on end of load in a single frame
----------------------------------------------------------------------------*/
nsresult nsEditingSession::EndDocumentLoad(nsIWebProgress* aWebProgress,
nsIChannel* aChannel,
nsresult aStatus,
bool aIsToBeMadeEditable) {
NS_ENSURE_ARG_POINTER(aWebProgress);
#ifdef NOISY_DOC_LOADING
printf("======= EndDocumentLoad ========\n");
printf("with status %d, ", aStatus);
nsCOMPtr<nsIURI> uri;
nsCString spec;
if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) {
uri->GetSpec(spec);
printf(" uri %s\n", spec.get());
}
#endif
// We want to call the base class EndDocumentLoad,
// but avoid some of the stuff
// that nsDocShell does (need to refactor).
// OK, time to make an editor on this document
nsCOMPtr<mozIDOMWindowProxy> domWindow;
aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
// Set the error state -- we will create an editor
// anyway and load empty doc later
if (aIsToBeMadeEditable && aStatus == NS_ERROR_FILE_NOT_FOUND) {
mEditorStatus = eEditorErrorFileNotFound;
}
auto* window = nsPIDOMWindowOuter::From(domWindow);
nsIDocShell* docShell = window->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); // better error handling?
// cancel refresh from meta tags
// we need to make sure that all pages in editor (whether editable or not)
// can't refresh contents being edited
nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell);
if (refreshURI) {
refreshURI->CancelRefreshURITimers();
}
nsresult rv = NS_OK;
// did someone set the flag to make this shell editable?
if (aIsToBeMadeEditable && mCanCreateEditor) {
bool makeEditable;
docShell->GetEditable(&makeEditable);
if (makeEditable) {
// To keep pre Gecko 1.9 behavior, setup editor always when
// mMakeWholeDocumentEditable.
bool needsSetup = false;
if (mMakeWholeDocumentEditable) {
needsSetup = true;
} else {
// do we already have an editor here?
needsSetup = !docShell->GetHTMLEditor();
}
if (needsSetup) {
mCanCreateEditor = false;
rv = SetupEditorOnWindow(MOZ_KnownLive(*window));
if (NS_FAILED(rv)) {
// If we had an error, setup timer to load a blank page later
if (mLoadBlankDocTimer) {
// Must cancel previous timer?
mLoadBlankDocTimer->Cancel();
mLoadBlankDocTimer = nullptr;
}
rv = NS_NewTimerWithFuncCallback(getter_AddRefs(mLoadBlankDocTimer),
nsEditingSession::TimerCallback,
static_cast<void*>(mDocShell.get()),
10, nsITimer::TYPE_ONE_SHOT,
"nsEditingSession::EndDocumentLoad");
NS_ENSURE_SUCCESS(rv, rv);
mEditorStatus = eEditorCreationInProgress;
}
}
}
}
return rv;
}
void nsEditingSession::TimerCallback(nsITimer* aTimer, void* aClosure) {
nsCOMPtr<nsIDocShell> docShell =
do_QueryReferent(static_cast<nsIWeakReference*>(aClosure));
if (docShell) {
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
if (webNav) {
LoadURIOptions loadURIOptions;
loadURIOptions.mTriggeringPrincipal =
nsContentUtils::GetSystemPrincipal();
webNav->LoadURI(u"about:blank"_ns, loadURIOptions);
}
}
}
/*---------------------------------------------------------------------------
StartPageLoad
Called on start load of the entire page (incl. subframes)
----------------------------------------------------------------------------*/
nsresult nsEditingSession::StartPageLoad(nsIChannel* aChannel) {
#ifdef NOISY_DOC_LOADING
printf("======= StartPageLoad ========\n");
#endif
return NS_OK;
}
/*---------------------------------------------------------------------------
EndPageLoad
Called on end load of the entire page (incl. subframes)
----------------------------------------------------------------------------*/
nsresult nsEditingSession::EndPageLoad(nsIWebProgress* aWebProgress,
nsIChannel* aChannel, nsresult aStatus) {
#ifdef NOISY_DOC_LOADING
printf("======= EndPageLoad ========\n");
printf(" with status %d, ", aStatus);
nsCOMPtr<nsIURI> uri;
nsCString spec;
if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) {
uri->GetSpec(spec);
printf("uri %s\n", spec.get());
}
nsAutoCString contentType;
aChannel->GetContentType(contentType);
if (!contentType.IsEmpty()) {
printf(" flags = %d, status = %d, MIMETYPE = %s\n", mEditorFlags,
mEditorStatus, contentType.get());
}
#endif
// Set the error state -- we will create an editor anyway
// and load empty doc later
if (aStatus == NS_ERROR_FILE_NOT_FOUND) {
mEditorStatus = eEditorErrorFileNotFound;
}
nsCOMPtr<mozIDOMWindowProxy> domWindow;
aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
nsIDocShell* docShell =
domWindow ? nsPIDOMWindowOuter::From(domWindow)->GetDocShell() : nullptr;
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
// cancel refresh from meta tags
// we need to make sure that all pages in editor (whether editable or not)
// can't refresh contents being edited
nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell);
if (refreshURI) {
refreshURI->CancelRefreshURITimers();
}
#if 0
// Shouldn't we do this when we want to edit sub-frames?
return MakeWindowEditable(domWindow, "html", false, mInteractive);
#else
return NS_OK;
#endif
}
/*---------------------------------------------------------------------------
PrepareForEditing
Set up this editing session for one or more editors
----------------------------------------------------------------------------*/
nsresult nsEditingSession::PrepareForEditing(nsPIDOMWindowOuter* aWindow) {
if (mProgressListenerRegistered) {
return NS_OK;
}
nsIDocShell* docShell = aWindow ? aWindow->GetDocShell() : nullptr;
// register callback
nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE);
nsresult rv = webProgress->AddProgressListener(
this, (nsIWebProgress::NOTIFY_STATE_NETWORK |
nsIWebProgress::NOTIFY_STATE_DOCUMENT |
nsIWebProgress::NOTIFY_LOCATION));
mProgressListenerRegistered = NS_SUCCEEDED(rv);
return rv;
}
/*---------------------------------------------------------------------------
SetupEditorCommandController
Create a command controller, append to controllers,
get and return the controller ID, and set the context
----------------------------------------------------------------------------*/
nsresult nsEditingSession::SetupEditorCommandController(
nsEditingSession::ControllerCreatorFn aControllerCreatorFn,
mozIDOMWindowProxy* aWindow, nsISupports* aContext,
uint32_t* aControllerId) {
NS_ENSURE_ARG_POINTER(aControllerCreatorFn);
NS_ENSURE_ARG_POINTER(aWindow);
NS_ENSURE_ARG_POINTER(aContext);
NS_ENSURE_ARG_POINTER(aControllerId);
auto* piWindow = nsPIDOMWindowOuter::From(aWindow);
MOZ_ASSERT(piWindow);
nsCOMPtr<nsIControllers> controllers;
nsresult rv = piWindow->GetControllers(getter_AddRefs(controllers));
NS_ENSURE_SUCCESS(rv, rv);
// We only have to create each singleton controller once
// We know this has happened once we have a controllerId value
if (!*aControllerId) {
RefPtr<nsBaseCommandController> commandController = aControllerCreatorFn();
NS_ENSURE_TRUE(commandController, NS_ERROR_FAILURE);
// We must insert at head of the list to be sure our
// controller is found before other implementations
// (e.g., not-implemented versions by browser)
rv = controllers->InsertControllerAt(0, commandController);
NS_ENSURE_SUCCESS(rv, rv);
// Remember the ID for the controller
rv = controllers->GetControllerId(commandController, aControllerId);
NS_ENSURE_SUCCESS(rv, rv);
}
// Set the context
return SetContextOnControllerById(controllers, aContext, *aControllerId);
}
nsresult nsEditingSession::SetEditorOnControllers(nsPIDOMWindowOuter& aWindow,
HTMLEditor* aEditor) {
nsCOMPtr<nsIControllers> controllers;
nsresult rv = aWindow.GetControllers(getter_AddRefs(controllers));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupports> editorAsISupports = static_cast<nsIEditor*>(aEditor);
if (mBaseCommandControllerId) {
rv = SetContextOnControllerById(controllers, editorAsISupports,
mBaseCommandControllerId);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mDocStateControllerId) {
rv = SetContextOnControllerById(controllers, editorAsISupports,
mDocStateControllerId);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mHTMLCommandControllerId) {
rv = SetContextOnControllerById(controllers, editorAsISupports,
mHTMLCommandControllerId);
}
return rv;
}
nsresult nsEditingSession::SetContextOnControllerById(
nsIControllers* aControllers, nsISupports* aContext, uint32_t aID) {
NS_ENSURE_ARG_POINTER(aControllers);
// aContext can be null (when destroying editor)
nsCOMPtr<nsIController> controller;
aControllers->GetControllerById(aID, getter_AddRefs(controller));
// ok with nil controller
nsCOMPtr<nsIControllerContext> editorController =
do_QueryInterface(controller);
NS_ENSURE_TRUE(editorController, NS_ERROR_FAILURE);
return editorController->SetCommandContext(aContext);
}
void nsEditingSession::RemoveEditorControllers(nsPIDOMWindowOuter* aWindow) {
// Remove editor controllers from the aWindow, call when we're
// tearing down/detaching editor.
nsCOMPtr<nsIControllers> controllers;
if (aWindow) {
aWindow->GetControllers(getter_AddRefs(controllers));
}
if (controllers) {
nsCOMPtr<nsIController> controller;
if (mBaseCommandControllerId) {
controllers->GetControllerById(mBaseCommandControllerId,
getter_AddRefs(controller));
if (controller) {
controllers->RemoveController(controller);
}
}
if (mDocStateControllerId) {
controllers->GetControllerById(mDocStateControllerId,
getter_AddRefs(controller));
if (controller) {
controllers->RemoveController(controller);
}
}
if (mHTMLCommandControllerId) {
controllers->GetControllerById(mHTMLCommandControllerId,
getter_AddRefs(controller));
if (controller) {
controllers->RemoveController(controller);
}
}
}
// Clear IDs to trigger creation of new controllers.
mBaseCommandControllerId = 0;
mDocStateControllerId = 0;
mHTMLCommandControllerId = 0;
}
void nsEditingSession::RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow) {
nsIDocShell* docShell = aWindow ? aWindow->GetDocShell() : nullptr;
nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
if (webProgress) {
webProgress->RemoveProgressListener(this);
mProgressListenerRegistered = false;
}
}
void nsEditingSession::RestoreAnimationMode(nsPIDOMWindowOuter* aWindow) {
if (mInteractive) {
return;
}
nsCOMPtr<nsIDocShell> docShell = aWindow ? aWindow->GetDocShell() : nullptr;
NS_ENSURE_TRUE_VOID(docShell);
RefPtr<PresShell> presShell = docShell->GetPresShell();
if (NS_WARN_IF(!presShell)) {
return;
}
nsPresContext* presContext = presShell->GetPresContext();
NS_ENSURE_TRUE_VOID(presContext);
presContext->SetImageAnimationMode(mImageAnimationMode);
}
nsresult nsEditingSession::DetachFromWindow(nsPIDOMWindowOuter* aWindow) {
NS_ENSURE_TRUE(mDoneSetup, NS_OK);
NS_ASSERTION(mComposerCommandsUpdater,
"mComposerCommandsUpdater should exist.");
// Kill any existing reload timer
if (mLoadBlankDocTimer) {
mLoadBlankDocTimer->Cancel();
mLoadBlankDocTimer = nullptr;
}
// Remove controllers, webprogress listener, and otherwise
// make things the way they were before we started editing.
RemoveEditorControllers(aWindow);
RemoveWebProgressListener(aWindow);
RestoreJSAndPlugins(aWindow->GetCurrentInnerWindow());
RestoreAnimationMode(aWindow);
// Kill our weak reference to our original window, in case
// it changes on restore, or otherwise dies.
mDocShell = nullptr;
return NS_OK;
}
nsresult nsEditingSession::ReattachToWindow(nsPIDOMWindowOuter* aWindow) {
NS_ENSURE_TRUE(mDoneSetup, NS_OK);
NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
NS_ASSERTION(mComposerCommandsUpdater,
"mComposerCommandsUpdater should exist.");
// Imitate nsEditorDocShell::MakeEditable() to reattach the
// old editor to the window.
nsresult rv;
nsIDocShell* docShell = aWindow->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
mDocShell = do_GetWeakReference(docShell);
// Disable plugins.
if (!mInteractive) {
rv = DisableJSAndPlugins(aWindow->GetCurrentInnerWindow());
NS_ENSURE_SUCCESS(rv, rv);
}
// Tells embedder that startup is in progress.
mEditorStatus = eEditorCreationInProgress;
// Adds back web progress listener.
rv = PrepareForEditing(aWindow);
NS_ENSURE_SUCCESS(rv, rv);
// Setup the command controllers again.
rv = SetupEditorCommandController(
nsBaseCommandController::CreateEditingController, aWindow,
static_cast<nsIEditingSession*>(this), &mBaseCommandControllerId);
NS_ENSURE_SUCCESS(rv, rv);
rv = SetupEditorCommandController(
nsBaseCommandController::CreateHTMLEditorDocStateController, aWindow,
static_cast<nsIEditingSession*>(this), &mDocStateControllerId);
NS_ENSURE_SUCCESS(rv, rv);
if (mComposerCommandsUpdater) {
mComposerCommandsUpdater->Init(*aWindow);
}
// Get editor
RefPtr<HTMLEditor> htmlEditor = GetHTMLEditorForWindow(aWindow);
if (NS_WARN_IF(!htmlEditor)) {
return NS_ERROR_FAILURE;
}
if (!mInteractive) {
// Disable animation of images in this document:
RefPtr<PresShell> presShell = docShell->GetPresShell();
if (NS_WARN_IF(!presShell)) {
return NS_ERROR_FAILURE;
}
nsPresContext* presContext = presShell->GetPresContext();
NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
mImageAnimationMode = presContext->ImageAnimationMode();
presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
}
// The third controller takes an nsIEditor as the context
rv = SetupEditorCommandController(
nsBaseCommandController::CreateHTMLEditorController, aWindow,
static_cast<nsIEditor*>(htmlEditor.get()), &mHTMLCommandControllerId);
NS_ENSURE_SUCCESS(rv, rv);
// Set context on all controllers to be the editor
rv = SetEditorOnControllers(*aWindow, htmlEditor);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef DEBUG
{
bool isEditable;
rv = WindowIsEditable(aWindow, &isEditable);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(isEditable,
"Window is not editable after reattaching editor.");
}
#endif // DEBUG
return NS_OK;
}
HTMLEditor* nsIEditingSession::GetHTMLEditorForWindow(
mozIDOMWindowProxy* aWindow) {
if (NS_WARN_IF(!aWindow)) {
return nullptr;
}
nsCOMPtr<nsIDocShell> docShell =
nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
if (NS_WARN_IF(!docShell)) {
return nullptr;
}
return docShell->GetHTMLEditor();
}