gecko-dev/editor/composer/nsEditingSession.cpp
2017-01-05 15:31:56 +08:00

1393 lines
45 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/FlushType.h" // for FlushType::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(mozilla::FlushType::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;
}