Backed out changeset f85447f6f56d (bug 1891145) for causing mochitest failures @...
[gecko.git] / editor / composer / nsEditingSession.cpp
blob6d09c3ba9d06c95e395b2662bd4b21db0912368a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <string.h> // for nullptr, strcmp
9 #include "imgIContainer.h" // for imgIContainer, etc
10 #include "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater
11 #include "mozilla/FlushType.h" // for FlushType::Frames
12 #include "mozilla/HTMLEditor.h" // for HTMLEditor
13 #include "mozilla/mozalloc.h" // for operator new
14 #include "mozilla/PresShell.h" // for PresShell
15 #include "mozilla/Try.h" // for MOZ_TRY
16 #include "nsAString.h"
17 #include "nsBaseCommandController.h" // for nsBaseCommandController
18 #include "nsCommandManager.h" // for nsCommandManager
19 #include "nsComponentManagerUtils.h" // for do_CreateInstance
20 #include "nsContentUtils.h"
21 #include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc
22 #include "nsDocShell.h" // for nsDocShell
23 #include "nsEditingSession.h"
24 #include "nsError.h" // for NS_ERROR_FAILURE, NS_OK, etc
25 #include "nsIChannel.h" // for nsIChannel
26 #include "nsIDocumentViewer.h" // for nsIDocumentViewer
27 #include "nsIControllers.h" // for nsIControllers
28 #include "nsID.h" // for NS_GET_IID, etc
29 #include "nsHTMLDocument.h" // for nsHTMLDocument
30 #include "nsIDocShell.h" // for nsIDocShell
31 #include "mozilla/dom/Document.h" // for Document
32 #include "nsIEditor.h" // for nsIEditor
33 #include "nsIInterfaceRequestorUtils.h" // for do_GetInterface
34 #include "nsIRefreshURI.h" // for nsIRefreshURI
35 #include "nsIRequest.h" // for nsIRequest
36 #include "nsITimer.h" // for nsITimer, etc
37 #include "nsIWeakReference.h" // for nsISupportsWeakReference, etc
38 #include "nsIWebNavigation.h" // for nsIWebNavigation
39 #include "nsIWebProgress.h" // for nsIWebProgress, etc
40 #include "nsLiteralString.h" // for NS_LITERAL_STRING
41 #include "nsPIDOMWindow.h" // for nsPIDOMWindow
42 #include "nsPresContext.h" // for nsPresContext
43 #include "nsReadableUtils.h" // for AppendUTF16toUTF8
44 #include "nsStringFwd.h" // for nsString
45 #include "mozilla/dom/BrowsingContext.h" // for BrowsingContext
46 #include "mozilla/dom/Selection.h" // for AutoHideSelectionChanges, etc
47 #include "mozilla/dom/WindowContext.h" // for WindowContext
48 #include "nsFrameSelection.h" // for nsFrameSelection
49 #include "nsBaseCommandController.h" // for nsBaseCommandController
50 #include "mozilla/dom/LoadURIOptionsBinding.h"
52 class nsISupports;
53 class nsIURI;
55 using namespace mozilla;
56 using namespace mozilla::dom;
58 /*---------------------------------------------------------------------------
60 nsEditingSession
62 ----------------------------------------------------------------------------*/
63 nsEditingSession::nsEditingSession()
64 : mDoneSetup(false),
65 mCanCreateEditor(false),
66 mInteractive(false),
67 mMakeWholeDocumentEditable(true),
68 mDisabledJS(false),
69 mScriptsEnabled(true),
70 mProgressListenerRegistered(false),
71 mImageAnimationMode(0),
72 mEditorFlags(0),
73 mEditorStatus(eEditorOK),
74 mBaseCommandControllerId(0),
75 mDocStateControllerId(0),
76 mHTMLCommandControllerId(0) {}
78 /*---------------------------------------------------------------------------
80 ~nsEditingSession
82 ----------------------------------------------------------------------------*/
83 nsEditingSession::~nsEditingSession() {
84 // Must cancel previous timer?
85 if (mLoadBlankDocTimer) mLoadBlankDocTimer->Cancel();
88 NS_IMPL_ISUPPORTS(nsEditingSession, nsIEditingSession, nsIWebProgressListener,
89 nsISupportsWeakReference)
91 /*---------------------------------------------------------------------------
93 MakeWindowEditable
95 aEditorType string, "html" "htmlsimple" "text" "textsimple"
96 void makeWindowEditable(in nsIDOMWindow aWindow, in string aEditorType,
97 in boolean aDoAfterUriLoad,
98 in boolean aMakeWholeDocumentEditable,
99 in boolean aInteractive);
100 ----------------------------------------------------------------------------*/
101 #define DEFAULT_EDITOR_TYPE "html"
103 NS_IMETHODIMP
104 nsEditingSession::MakeWindowEditable(mozIDOMWindowProxy* aWindow,
105 const char* aEditorType,
106 bool aDoAfterUriLoad,
107 bool aMakeWholeDocumentEditable,
108 bool aInteractive) {
109 mEditorType.Truncate();
110 mEditorFlags = 0;
112 NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
113 auto* window = nsPIDOMWindowOuter::From(aWindow);
115 // disable plugins
116 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
117 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
118 mDocShell = do_GetWeakReference(docShell);
120 mInteractive = aInteractive;
121 mMakeWholeDocumentEditable = aMakeWholeDocumentEditable;
123 nsresult rv;
124 if (!mInteractive) {
125 rv = DisableJS(window->GetCurrentInnerWindow());
126 NS_ENSURE_SUCCESS(rv, rv);
129 // Always remove existing editor
130 TearDownEditorOnWindow(aWindow);
132 // Tells embedder that startup is in progress
133 mEditorStatus = eEditorCreationInProgress;
135 // temporary to set editor type here. we will need different classes soon.
136 if (!aEditorType) aEditorType = DEFAULT_EDITOR_TYPE;
137 mEditorType = aEditorType;
139 // if all this does is setup listeners and I don't need listeners,
140 // can't this step be ignored?? (based on aDoAfterURILoad)
141 rv = PrepareForEditing(window);
142 NS_ENSURE_SUCCESS(rv, rv);
144 // set the flag on the docShell to say that it's editable
145 rv = docShell->MakeEditable(aDoAfterUriLoad);
146 NS_ENSURE_SUCCESS(rv, rv);
148 // Setup commands common to plaintext and html editors,
149 // including the document creation observers
150 // the first is an editing controller
151 rv = SetupEditorCommandController(
152 nsBaseCommandController::CreateEditingController, aWindow,
153 static_cast<nsIEditingSession*>(this), &mBaseCommandControllerId);
154 NS_ENSURE_SUCCESS(rv, rv);
156 // The second is a controller to monitor doc state,
157 // such as creation and "dirty flag"
158 rv = SetupEditorCommandController(
159 nsBaseCommandController::CreateHTMLEditorDocStateController, aWindow,
160 static_cast<nsIEditingSession*>(this), &mDocStateControllerId);
161 NS_ENSURE_SUCCESS(rv, rv);
163 // aDoAfterUriLoad can be false only when making an existing window editable
164 if (!aDoAfterUriLoad) {
165 rv = SetupEditorOnWindow(MOZ_KnownLive(*window));
167 // mEditorStatus is set to the error reason
168 // Since this is used only when editing an existing page,
169 // it IS ok to destroy current editor
170 if (NS_FAILED(rv)) {
171 TearDownEditorOnWindow(aWindow);
174 return rv;
177 nsresult nsEditingSession::DisableJS(nsPIDOMWindowInner* aWindow) {
178 WindowContext* wc = aWindow->GetWindowContext();
180 mScriptsEnabled = wc->GetAllowJavascript();
181 MOZ_TRY(wc->SetAllowJavascript(false));
182 mDisabledJS = true;
183 return NS_OK;
186 nsresult nsEditingSession::RestoreJS(nsPIDOMWindowInner* aWindow) {
187 if (!mDisabledJS) {
188 return NS_OK;
191 mDisabledJS = false;
193 if (NS_WARN_IF(!aWindow)) {
194 // DetachFromWindow may call this method with nullptr.
195 return NS_ERROR_FAILURE;
198 WindowContext* wc = aWindow->GetWindowContext();
199 return wc->SetAllowJavascript(mScriptsEnabled);
202 /*---------------------------------------------------------------------------
204 WindowIsEditable
206 boolean windowIsEditable (in nsIDOMWindow aWindow);
207 ----------------------------------------------------------------------------*/
208 NS_IMETHODIMP
209 nsEditingSession::WindowIsEditable(mozIDOMWindowProxy* aWindow,
210 bool* outIsEditable) {
211 NS_ENSURE_STATE(aWindow);
212 nsCOMPtr<nsIDocShell> docShell =
213 nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
214 NS_ENSURE_STATE(docShell);
216 return docShell->GetEditable(outIsEditable);
219 bool IsSupportedTextType(const nsAString& aMIMEType) {
220 // These are MIME types that are automatically parsed as "text/plain"
221 // and thus we can edit them as plaintext
222 // Note: in older versions, we attempted to convert the mimetype of
223 // the network channel for these and "text/xml" to "text/plain",
224 // but further investigation reveals that strategy doesn't work
225 static constexpr nsLiteralString sSupportedTextTypes[] = {
226 u"text/plain"_ns,
227 u"text/css"_ns,
228 u"text/rdf"_ns,
229 u"text/xsl"_ns,
230 u"text/javascript"_ns, // obsolete type
231 u"text/ecmascript"_ns, // obsolete type
232 u"application/javascript"_ns,
233 u"application/ecmascript"_ns,
234 u"application/x-javascript"_ns, // obsolete type
235 u"text/xul"_ns // obsolete type
238 for (const nsLiteralString& supportedTextType : sSupportedTextTypes) {
239 if (aMIMEType.Equals(supportedTextType)) {
240 return true;
244 return false;
247 nsresult nsEditingSession::SetupEditorOnWindow(nsPIDOMWindowOuter& aWindow) {
248 mDoneSetup = true;
250 // MIME CHECKING
251 // must get the content type
252 // Note: the doc gets this from the network channel during StartPageLoad,
253 // so we don't have to get it from there ourselves
254 nsAutoString mimeType;
256 // then lets check the mime type
257 if (RefPtr<Document> doc = aWindow.GetDoc()) {
258 doc->GetContentType(mimeType);
260 if (IsSupportedTextType(mimeType)) {
261 mEditorType.AssignLiteral("text");
262 mimeType.AssignLiteral("text/plain");
263 } else if (!doc->IsHTMLOrXHTML()) {
264 // Neither an acceptable text or html type.
265 mEditorStatus = eEditorErrorCantEditMimeType;
267 // Turn editor into HTML -- we will load blank page later
268 mEditorType.AssignLiteral("html");
269 mimeType.AssignLiteral("text/html");
272 // Flush out frame construction to make sure that the subframe's
273 // presshell is set up if it needs to be.
274 doc->FlushPendingNotifications(mozilla::FlushType::Frames);
275 if (mMakeWholeDocumentEditable) {
276 doc->SetEditableFlag(true);
277 // Enable usage of the execCommand API
278 doc->SetEditingState(Document::EditingState::eDesignMode);
281 bool needHTMLController = false;
283 if (mEditorType.EqualsLiteral("textmail")) {
284 mEditorFlags = nsIEditor::eEditorPlaintextMask |
285 nsIEditor::eEditorEnableWrapHackMask |
286 nsIEditor::eEditorMailMask;
287 } else if (mEditorType.EqualsLiteral("text")) {
288 mEditorFlags =
289 nsIEditor::eEditorPlaintextMask | nsIEditor::eEditorEnableWrapHackMask;
290 } else if (mEditorType.EqualsLiteral("htmlmail")) {
291 if (mimeType.EqualsLiteral("text/html")) {
292 needHTMLController = true;
293 mEditorFlags = nsIEditor::eEditorMailMask;
294 } else {
295 // Set the flags back to textplain.
296 mEditorFlags = nsIEditor::eEditorPlaintextMask |
297 nsIEditor::eEditorEnableWrapHackMask;
299 } else {
300 // Defaulted to html
301 needHTMLController = true;
304 if (mInteractive) {
305 mEditorFlags |= nsIEditor::eEditorAllowInteraction;
308 // make the UI state maintainer
309 RefPtr<ComposerCommandsUpdater> commandsUpdater =
310 new ComposerCommandsUpdater();
311 mComposerCommandsUpdater = commandsUpdater;
313 // now init the state maintainer
314 // This allows notification of error state
315 // even if we don't create an editor
316 commandsUpdater->Init(aWindow);
318 if (mEditorStatus != eEditorCreationInProgress) {
319 commandsUpdater->OnHTMLEditorCreated();
321 // At this point we have made a final decision that we don't support
322 // editing the current document. This is an internal failure state, but
323 // we return NS_OK to avoid throwing an exception from the designMode
324 // setter for web compatibility. The document editing APIs will tell the
325 // developer if editing has been disabled because we're in a document type
326 // that doesn't support editing.
327 return NS_OK;
330 // Create editor and do other things
331 // only if we haven't found some error above,
332 const RefPtr<nsDocShell> docShell = nsDocShell::Cast(aWindow.GetDocShell());
333 if (NS_WARN_IF(!docShell)) {
334 return NS_ERROR_FAILURE;
336 const RefPtr<PresShell> presShell = docShell->GetPresShell();
337 if (NS_WARN_IF(!presShell)) {
338 return NS_ERROR_FAILURE;
341 if (!mInteractive) {
342 // Disable animation of images in this document:
343 nsPresContext* presContext = presShell->GetPresContext();
344 NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
346 mImageAnimationMode = presContext->ImageAnimationMode();
347 presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
350 // Hide selection changes during initialization, in order to hide this
351 // from web pages.
352 RefPtr<nsFrameSelection> fs = presShell->FrameSelection();
353 NS_ENSURE_TRUE(fs, NS_ERROR_FAILURE);
354 AutoHideSelectionChanges hideSelectionChanges(fs);
356 nsCOMPtr<nsIDocumentViewer> viewer;
357 nsresult rv = docShell->GetDocViewer(getter_AddRefs(viewer));
358 if (NS_FAILED(rv) || NS_WARN_IF(!viewer)) {
359 NS_WARNING("nsDocShell::GetDocViewer() failed");
360 return rv;
363 const RefPtr<Document> doc = viewer->GetDocument();
364 if (NS_WARN_IF(!doc)) {
365 return NS_ERROR_FAILURE;
368 // create and set editor
369 // Try to reuse an existing editor
370 nsCOMPtr<nsIEditor> editor = do_QueryReferent(mExistingEditor);
371 RefPtr<HTMLEditor> htmlEditor = HTMLEditor::GetFrom(editor);
372 MOZ_ASSERT_IF(editor, htmlEditor);
373 if (htmlEditor) {
374 htmlEditor->PreDestroy();
375 } else {
376 htmlEditor = new HTMLEditor(*doc);
377 mExistingEditor =
378 do_GetWeakReference(static_cast<nsIEditor*>(htmlEditor.get()));
380 // set the editor on the docShell. The docShell now owns it.
381 rv = docShell->SetHTMLEditor(htmlEditor);
382 NS_ENSURE_SUCCESS(rv, rv);
384 // setup the HTML editor command controller
385 if (needHTMLController) {
386 // The third controller takes an nsIEditor as the context
387 rv = SetupEditorCommandController(
388 nsBaseCommandController::CreateHTMLEditorController, &aWindow,
389 static_cast<nsIEditor*>(htmlEditor), &mHTMLCommandControllerId);
390 NS_ENSURE_SUCCESS(rv, rv);
393 // Set mimetype on editor
394 rv = htmlEditor->SetContentsMIMEType(mimeType);
395 NS_ENSURE_SUCCESS(rv, rv);
397 MOZ_ASSERT(docShell->HasDocumentViewer());
398 MOZ_ASSERT(viewer->GetDocument());
400 MOZ_DIAGNOSTIC_ASSERT(commandsUpdater == mComposerCommandsUpdater);
401 if (MOZ_UNLIKELY(commandsUpdater != mComposerCommandsUpdater)) {
402 commandsUpdater = mComposerCommandsUpdater;
404 rv = htmlEditor->Init(*doc, *commandsUpdater, mEditorFlags);
405 NS_ENSURE_SUCCESS(rv, rv);
407 RefPtr<Selection> selection = htmlEditor->GetSelection();
408 if (NS_WARN_IF(!selection)) {
409 return NS_ERROR_FAILURE;
412 // Set context on all controllers to be the editor
413 rv = SetEditorOnControllers(aWindow, htmlEditor);
414 NS_ENSURE_SUCCESS(rv, rv);
416 // Everything went fine!
417 mEditorStatus = eEditorOK;
419 // This will trigger documentCreation notification
420 return htmlEditor->PostCreate();
423 // Removes all listeners and controllers from aWindow and aEditor.
424 void nsEditingSession::RemoveListenersAndControllers(
425 nsPIDOMWindowOuter* aWindow, HTMLEditor* aHTMLEditor) {
426 if (!mComposerCommandsUpdater || !aHTMLEditor) {
427 return;
430 // Remove all the listeners
431 RefPtr<ComposerCommandsUpdater> composertCommandsUpdater =
432 std::move(mComposerCommandsUpdater);
433 MOZ_ASSERT(!mComposerCommandsUpdater);
434 aHTMLEditor->Detach(*composertCommandsUpdater);
436 // Remove editor controllers from the window now that we're not
437 // editing in that window any more.
438 RemoveEditorControllers(aWindow);
441 /*---------------------------------------------------------------------------
443 TearDownEditorOnWindow
445 void tearDownEditorOnWindow (in nsIDOMWindow aWindow);
446 ----------------------------------------------------------------------------*/
447 NS_IMETHODIMP
448 nsEditingSession::TearDownEditorOnWindow(mozIDOMWindowProxy* aWindow) {
449 if (!mDoneSetup) {
450 return NS_OK;
453 NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
455 // Kill any existing reload timer
456 if (mLoadBlankDocTimer) {
457 mLoadBlankDocTimer->Cancel();
458 mLoadBlankDocTimer = nullptr;
461 mDoneSetup = false;
463 // Check if we're turning off editing (from contentEditable or designMode).
464 auto* window = nsPIDOMWindowOuter::From(aWindow);
466 RefPtr<Document> doc = window->GetDoc();
467 bool stopEditing = doc && doc->IsEditingOn();
468 if (stopEditing) {
469 RemoveWebProgressListener(window);
472 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
473 NS_ENSURE_STATE(docShell);
475 RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor();
476 if (stopEditing) {
477 doc->TearingDownEditor();
480 if (mComposerCommandsUpdater && htmlEditor) {
481 // Null out the editor on the controllers first to prevent their weak
482 // references from pointing to a destroyed editor.
483 SetEditorOnControllers(*window, nullptr);
486 // Null out the editor on the docShell to trigger PreDestroy which
487 // needs to happen before document state listeners are removed below.
488 docShell->SetEditor(nullptr);
490 RemoveListenersAndControllers(window, htmlEditor);
492 if (stopEditing) {
493 // Make things the way they were before we started editing.
494 RestoreJS(window->GetCurrentInnerWindow());
495 RestoreAnimationMode(window);
497 if (mMakeWholeDocumentEditable) {
498 doc->SetEditableFlag(false);
499 doc->SetEditingState(Document::EditingState::eOff);
503 return NS_OK;
506 /*---------------------------------------------------------------------------
508 GetEditorForFrame
510 nsIEditor getEditorForFrame (in nsIDOMWindow aWindow);
511 ----------------------------------------------------------------------------*/
512 NS_IMETHODIMP
513 nsEditingSession::GetEditorForWindow(mozIDOMWindowProxy* aWindow,
514 nsIEditor** outEditor) {
515 if (NS_WARN_IF(!aWindow)) {
516 return NS_ERROR_INVALID_ARG;
518 nsCOMPtr<nsIEditor> editor = GetHTMLEditorForWindow(aWindow);
519 editor.forget(outEditor);
520 return NS_OK;
523 /*---------------------------------------------------------------------------
525 OnStateChange
527 ----------------------------------------------------------------------------*/
528 NS_IMETHODIMP
529 nsEditingSession::OnStateChange(nsIWebProgress* aWebProgress,
530 nsIRequest* aRequest, uint32_t aStateFlags,
531 nsresult aStatus) {
532 #ifdef NOISY_DOC_LOADING
533 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
534 if (channel) {
535 nsAutoCString contentType;
536 channel->GetContentType(contentType);
537 if (!contentType.IsEmpty()) {
538 printf(" ++++++ MIMETYPE = %s\n", contentType.get());
541 #endif
544 // A Request has started...
546 if (aStateFlags & nsIWebProgressListener::STATE_START) {
547 #ifdef NOISY_DOC_LOADING
549 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
550 if (channel) {
551 nsCOMPtr<nsIURI> uri;
552 channel->GetURI(getter_AddRefs(uri));
553 if (uri) {
554 nsCString spec;
555 uri->GetSpec(spec);
556 printf(" **** STATE_START: CHANNEL URI=%s, flags=%x\n", spec.get(),
557 aStateFlags);
559 } else {
560 printf(" STATE_START: NO CHANNEL flags=%x\n", aStateFlags);
563 #endif
564 // Page level notification...
565 if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) {
566 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
567 StartPageLoad(channel);
568 #ifdef NOISY_DOC_LOADING
569 printf("STATE_START & STATE_IS_NETWORK flags=%x\n", aStateFlags);
570 #endif
573 // Document level notification...
574 if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT &&
575 !(aStateFlags & nsIWebProgressListener::STATE_RESTORING)) {
576 #ifdef NOISY_DOC_LOADING
577 printf("STATE_START & STATE_IS_DOCUMENT flags=%x\n", aStateFlags);
578 #endif
580 bool progressIsForTargetDocument =
581 IsProgressForTargetDocument(aWebProgress);
583 if (progressIsForTargetDocument) {
584 nsCOMPtr<mozIDOMWindowProxy> window;
585 aWebProgress->GetDOMWindow(getter_AddRefs(window));
587 auto* piWindow = nsPIDOMWindowOuter::From(window);
588 RefPtr<Document> doc = piWindow->GetDoc();
589 nsHTMLDocument* htmlDoc =
590 doc && doc->IsHTMLOrXHTML() ? doc->AsHTMLDocument() : nullptr;
591 if (htmlDoc && doc->IsWriting()) {
592 nsAutoString designMode;
593 htmlDoc->GetDesignMode(designMode);
595 if (designMode.EqualsLiteral("on")) {
596 // This notification is for data coming in through
597 // document.open/write/close(), ignore it.
599 return NS_OK;
603 mCanCreateEditor = true;
604 StartDocumentLoad(aWebProgress, progressIsForTargetDocument);
609 // A Request is being processed
611 else if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) {
612 if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
613 // document transfer started
617 // Got a redirection
619 else if (aStateFlags & nsIWebProgressListener::STATE_REDIRECTING) {
620 if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
621 // got a redirect
625 // A network or document Request has finished...
627 else if (aStateFlags & nsIWebProgressListener::STATE_STOP) {
628 #ifdef NOISY_DOC_LOADING
630 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
631 if (channel) {
632 nsCOMPtr<nsIURI> uri;
633 channel->GetURI(getter_AddRefs(uri));
634 if (uri) {
635 nsCString spec;
636 uri->GetSpec(spec);
637 printf(" **** STATE_STOP: CHANNEL URI=%s, flags=%x\n", spec.get(),
638 aStateFlags);
640 } else {
641 printf(" STATE_STOP: NO CHANNEL flags=%x\n", aStateFlags);
644 #endif
646 // Document level notification...
647 if (aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
648 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
649 EndDocumentLoad(aWebProgress, channel, aStatus,
650 IsProgressForTargetDocument(aWebProgress));
651 #ifdef NOISY_DOC_LOADING
652 printf("STATE_STOP & STATE_IS_DOCUMENT flags=%x\n", aStateFlags);
653 #endif
656 // Page level notification...
657 if (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) {
658 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
659 (void)EndPageLoad(aWebProgress, channel, aStatus);
660 #ifdef NOISY_DOC_LOADING
661 printf("STATE_STOP & STATE_IS_NETWORK flags=%x\n", aStateFlags);
662 #endif
666 return NS_OK;
669 /*---------------------------------------------------------------------------
671 OnProgressChange
673 ----------------------------------------------------------------------------*/
674 NS_IMETHODIMP
675 nsEditingSession::OnProgressChange(nsIWebProgress* aWebProgress,
676 nsIRequest* aRequest,
677 int32_t aCurSelfProgress,
678 int32_t aMaxSelfProgress,
679 int32_t aCurTotalProgress,
680 int32_t aMaxTotalProgress) {
681 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
682 return NS_OK;
685 /*---------------------------------------------------------------------------
687 OnLocationChange
689 ----------------------------------------------------------------------------*/
690 NS_IMETHODIMP
691 nsEditingSession::OnLocationChange(nsIWebProgress* aWebProgress,
692 nsIRequest* aRequest, nsIURI* aURI,
693 uint32_t aFlags) {
694 nsCOMPtr<mozIDOMWindowProxy> domWindow;
695 nsresult rv = aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
696 NS_ENSURE_SUCCESS(rv, rv);
698 auto* piWindow = nsPIDOMWindowOuter::From(domWindow);
700 RefPtr<Document> doc = piWindow->GetDoc();
701 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
703 doc->SetDocumentURI(aURI);
705 // Notify the location-changed observer that
706 // the document URL has changed
707 nsIDocShell* docShell = piWindow->GetDocShell();
708 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
710 RefPtr<nsCommandManager> commandManager = docShell->GetCommandManager();
711 commandManager->CommandStatusChanged("obs_documentLocationChanged");
712 return NS_OK;
715 /*---------------------------------------------------------------------------
717 OnStatusChange
719 ----------------------------------------------------------------------------*/
720 NS_IMETHODIMP
721 nsEditingSession::OnStatusChange(nsIWebProgress* aWebProgress,
722 nsIRequest* aRequest, nsresult aStatus,
723 const char16_t* aMessage) {
724 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
725 return NS_OK;
728 /*---------------------------------------------------------------------------
730 OnSecurityChange
732 ----------------------------------------------------------------------------*/
733 NS_IMETHODIMP
734 nsEditingSession::OnSecurityChange(nsIWebProgress* aWebProgress,
735 nsIRequest* aRequest, uint32_t aState) {
736 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
737 return NS_OK;
740 /*---------------------------------------------------------------------------
742 OnContentBlockingEvent
744 ----------------------------------------------------------------------------*/
745 NS_IMETHODIMP
746 nsEditingSession::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
747 nsIRequest* aRequest,
748 uint32_t aEvent) {
749 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
750 return NS_OK;
753 /*---------------------------------------------------------------------------
755 IsProgressForTargetDocument
757 Check that this notification is for our document.
758 ----------------------------------------------------------------------------*/
760 bool nsEditingSession::IsProgressForTargetDocument(
761 nsIWebProgress* aWebProgress) {
762 nsCOMPtr<nsIWebProgress> editedWebProgress = do_QueryReferent(mDocShell);
763 return editedWebProgress == aWebProgress;
766 /*---------------------------------------------------------------------------
768 GetEditorStatus
770 Called during GetCommandStateParams("obs_documentCreated"...)
771 to determine if editor was created and document
772 was loaded successfully
773 ----------------------------------------------------------------------------*/
774 NS_IMETHODIMP
775 nsEditingSession::GetEditorStatus(uint32_t* aStatus) {
776 NS_ENSURE_ARG_POINTER(aStatus);
777 *aStatus = mEditorStatus;
778 return NS_OK;
781 /*---------------------------------------------------------------------------
783 StartDocumentLoad
785 Called on start of load in a single frame
786 ----------------------------------------------------------------------------*/
787 nsresult nsEditingSession::StartDocumentLoad(nsIWebProgress* aWebProgress,
788 bool aIsToBeMadeEditable) {
789 #ifdef NOISY_DOC_LOADING
790 printf("======= StartDocumentLoad ========\n");
791 #endif
793 NS_ENSURE_ARG_POINTER(aWebProgress);
795 if (aIsToBeMadeEditable) {
796 mEditorStatus = eEditorCreationInProgress;
799 return NS_OK;
802 /*---------------------------------------------------------------------------
804 EndDocumentLoad
806 Called on end of load in a single frame
807 ----------------------------------------------------------------------------*/
808 nsresult nsEditingSession::EndDocumentLoad(nsIWebProgress* aWebProgress,
809 nsIChannel* aChannel,
810 nsresult aStatus,
811 bool aIsToBeMadeEditable) {
812 NS_ENSURE_ARG_POINTER(aWebProgress);
814 #ifdef NOISY_DOC_LOADING
815 printf("======= EndDocumentLoad ========\n");
816 printf("with status %d, ", aStatus);
817 nsCOMPtr<nsIURI> uri;
818 nsCString spec;
819 if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) {
820 uri->GetSpec(spec);
821 printf(" uri %s\n", spec.get());
823 #endif
825 // We want to call the base class EndDocumentLoad,
826 // but avoid some of the stuff
827 // that nsDocShell does (need to refactor).
829 // OK, time to make an editor on this document
830 nsCOMPtr<mozIDOMWindowProxy> domWindow;
831 aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
832 NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
834 // Set the error state -- we will create an editor
835 // anyway and load empty doc later
836 if (aIsToBeMadeEditable && aStatus == NS_ERROR_FILE_NOT_FOUND) {
837 mEditorStatus = eEditorErrorFileNotFound;
840 auto* window = nsPIDOMWindowOuter::From(domWindow);
841 nsIDocShell* docShell = window->GetDocShell();
842 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); // better error handling?
844 // cancel refresh from meta tags
845 // we need to make sure that all pages in editor (whether editable or not)
846 // can't refresh contents being edited
847 nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell);
848 if (refreshURI) {
849 refreshURI->CancelRefreshURITimers();
852 nsresult rv = NS_OK;
854 // did someone set the flag to make this shell editable?
855 if (aIsToBeMadeEditable && mCanCreateEditor) {
856 bool makeEditable;
857 docShell->GetEditable(&makeEditable);
859 if (makeEditable) {
860 // To keep pre Gecko 1.9 behavior, setup editor always when
861 // mMakeWholeDocumentEditable.
862 bool needsSetup = false;
863 if (mMakeWholeDocumentEditable) {
864 needsSetup = true;
865 } else {
866 // do we already have an editor here?
867 needsSetup = !docShell->GetHTMLEditor();
870 if (needsSetup) {
871 mCanCreateEditor = false;
872 rv = SetupEditorOnWindow(MOZ_KnownLive(*window));
873 if (NS_FAILED(rv)) {
874 // If we had an error, setup timer to load a blank page later
875 if (mLoadBlankDocTimer) {
876 // Must cancel previous timer?
877 mLoadBlankDocTimer->Cancel();
878 mLoadBlankDocTimer = nullptr;
881 rv = NS_NewTimerWithFuncCallback(getter_AddRefs(mLoadBlankDocTimer),
882 nsEditingSession::TimerCallback,
883 static_cast<void*>(mDocShell.get()),
884 10, nsITimer::TYPE_ONE_SHOT,
885 "nsEditingSession::EndDocumentLoad");
886 NS_ENSURE_SUCCESS(rv, rv);
888 mEditorStatus = eEditorCreationInProgress;
893 return rv;
896 void nsEditingSession::TimerCallback(nsITimer* aTimer, void* aClosure) {
897 nsCOMPtr<nsIDocShell> docShell =
898 do_QueryReferent(static_cast<nsIWeakReference*>(aClosure));
899 if (docShell) {
900 nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
901 if (webNav) {
902 LoadURIOptions loadURIOptions;
903 loadURIOptions.mTriggeringPrincipal =
904 nsContentUtils::GetSystemPrincipal();
905 nsCOMPtr<nsIURI> uri;
906 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns));
907 webNav->LoadURI(uri, loadURIOptions);
912 /*---------------------------------------------------------------------------
914 StartPageLoad
916 Called on start load of the entire page (incl. subframes)
917 ----------------------------------------------------------------------------*/
918 nsresult nsEditingSession::StartPageLoad(nsIChannel* aChannel) {
919 #ifdef NOISY_DOC_LOADING
920 printf("======= StartPageLoad ========\n");
921 #endif
922 return NS_OK;
925 /*---------------------------------------------------------------------------
927 EndPageLoad
929 Called on end load of the entire page (incl. subframes)
930 ----------------------------------------------------------------------------*/
931 nsresult nsEditingSession::EndPageLoad(nsIWebProgress* aWebProgress,
932 nsIChannel* aChannel, nsresult aStatus) {
933 #ifdef NOISY_DOC_LOADING
934 printf("======= EndPageLoad ========\n");
935 printf(" with status %d, ", aStatus);
936 nsCOMPtr<nsIURI> uri;
937 nsCString spec;
938 if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(uri)))) {
939 uri->GetSpec(spec);
940 printf("uri %s\n", spec.get());
943 nsAutoCString contentType;
944 aChannel->GetContentType(contentType);
945 if (!contentType.IsEmpty()) {
946 printf(" flags = %d, status = %d, MIMETYPE = %s\n", mEditorFlags,
947 mEditorStatus, contentType.get());
949 #endif
951 // Set the error state -- we will create an editor anyway
952 // and load empty doc later
953 if (aStatus == NS_ERROR_FILE_NOT_FOUND) {
954 mEditorStatus = eEditorErrorFileNotFound;
957 nsCOMPtr<mozIDOMWindowProxy> domWindow;
958 aWebProgress->GetDOMWindow(getter_AddRefs(domWindow));
960 nsIDocShell* docShell =
961 domWindow ? nsPIDOMWindowOuter::From(domWindow)->GetDocShell() : nullptr;
962 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
964 // cancel refresh from meta tags
965 // we need to make sure that all pages in editor (whether editable or not)
966 // can't refresh contents being edited
967 nsCOMPtr<nsIRefreshURI> refreshURI = do_QueryInterface(docShell);
968 if (refreshURI) {
969 refreshURI->CancelRefreshURITimers();
972 #if 0
973 // Shouldn't we do this when we want to edit sub-frames?
974 return MakeWindowEditable(domWindow, "html", false, mInteractive);
975 #else
976 return NS_OK;
977 #endif
980 /*---------------------------------------------------------------------------
982 PrepareForEditing
984 Set up this editing session for one or more editors
985 ----------------------------------------------------------------------------*/
986 nsresult nsEditingSession::PrepareForEditing(nsPIDOMWindowOuter* aWindow) {
987 if (mProgressListenerRegistered) {
988 return NS_OK;
991 nsIDocShell* docShell = aWindow ? aWindow->GetDocShell() : nullptr;
993 // register callback
994 nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
995 NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE);
997 nsresult rv = webProgress->AddProgressListener(
998 this, (nsIWebProgress::NOTIFY_STATE_NETWORK |
999 nsIWebProgress::NOTIFY_STATE_DOCUMENT |
1000 nsIWebProgress::NOTIFY_LOCATION));
1002 mProgressListenerRegistered = NS_SUCCEEDED(rv);
1004 return rv;
1007 /*---------------------------------------------------------------------------
1009 SetupEditorCommandController
1011 Create a command controller, append to controllers,
1012 get and return the controller ID, and set the context
1013 ----------------------------------------------------------------------------*/
1014 nsresult nsEditingSession::SetupEditorCommandController(
1015 nsEditingSession::ControllerCreatorFn aControllerCreatorFn,
1016 mozIDOMWindowProxy* aWindow, nsISupports* aContext,
1017 uint32_t* aControllerId) {
1018 NS_ENSURE_ARG_POINTER(aControllerCreatorFn);
1019 NS_ENSURE_ARG_POINTER(aWindow);
1020 NS_ENSURE_ARG_POINTER(aContext);
1021 NS_ENSURE_ARG_POINTER(aControllerId);
1023 auto* piWindow = nsPIDOMWindowOuter::From(aWindow);
1024 MOZ_ASSERT(piWindow);
1026 nsCOMPtr<nsIControllers> controllers;
1027 nsresult rv = piWindow->GetControllers(getter_AddRefs(controllers));
1028 NS_ENSURE_SUCCESS(rv, rv);
1030 // We only have to create each singleton controller once
1031 // We know this has happened once we have a controllerId value
1032 if (!*aControllerId) {
1033 RefPtr<nsBaseCommandController> commandController = aControllerCreatorFn();
1034 NS_ENSURE_TRUE(commandController, NS_ERROR_FAILURE);
1036 // We must insert at head of the list to be sure our
1037 // controller is found before other implementations
1038 // (e.g., not-implemented versions by browser)
1039 rv = controllers->InsertControllerAt(0, commandController);
1040 NS_ENSURE_SUCCESS(rv, rv);
1042 // Remember the ID for the controller
1043 rv = controllers->GetControllerId(commandController, aControllerId);
1044 NS_ENSURE_SUCCESS(rv, rv);
1047 // Set the context
1048 return SetContextOnControllerById(controllers, aContext, *aControllerId);
1051 nsresult nsEditingSession::SetEditorOnControllers(nsPIDOMWindowOuter& aWindow,
1052 HTMLEditor* aEditor) {
1053 nsCOMPtr<nsIControllers> controllers;
1054 nsresult rv = aWindow.GetControllers(getter_AddRefs(controllers));
1055 NS_ENSURE_SUCCESS(rv, rv);
1057 nsCOMPtr<nsISupports> editorAsISupports = static_cast<nsIEditor*>(aEditor);
1058 if (mBaseCommandControllerId) {
1059 rv = SetContextOnControllerById(controllers, editorAsISupports,
1060 mBaseCommandControllerId);
1061 NS_ENSURE_SUCCESS(rv, rv);
1064 if (mDocStateControllerId) {
1065 rv = SetContextOnControllerById(controllers, editorAsISupports,
1066 mDocStateControllerId);
1067 NS_ENSURE_SUCCESS(rv, rv);
1070 if (mHTMLCommandControllerId) {
1071 rv = SetContextOnControllerById(controllers, editorAsISupports,
1072 mHTMLCommandControllerId);
1075 return rv;
1078 nsresult nsEditingSession::SetContextOnControllerById(
1079 nsIControllers* aControllers, nsISupports* aContext, uint32_t aID) {
1080 NS_ENSURE_ARG_POINTER(aControllers);
1082 // aContext can be null (when destroying editor)
1083 nsCOMPtr<nsIController> controller;
1084 aControllers->GetControllerById(aID, getter_AddRefs(controller));
1086 // ok with nil controller
1087 nsCOMPtr<nsIControllerContext> editorController =
1088 do_QueryInterface(controller);
1089 NS_ENSURE_TRUE(editorController, NS_ERROR_FAILURE);
1091 return editorController->SetCommandContext(aContext);
1094 void nsEditingSession::RemoveEditorControllers(nsPIDOMWindowOuter* aWindow) {
1095 // Remove editor controllers from the aWindow, call when we're
1096 // tearing down/detaching editor.
1098 nsCOMPtr<nsIControllers> controllers;
1099 if (aWindow) {
1100 aWindow->GetControllers(getter_AddRefs(controllers));
1103 if (controllers) {
1104 nsCOMPtr<nsIController> controller;
1105 if (mBaseCommandControllerId) {
1106 controllers->GetControllerById(mBaseCommandControllerId,
1107 getter_AddRefs(controller));
1108 if (controller) {
1109 controllers->RemoveController(controller);
1113 if (mDocStateControllerId) {
1114 controllers->GetControllerById(mDocStateControllerId,
1115 getter_AddRefs(controller));
1116 if (controller) {
1117 controllers->RemoveController(controller);
1121 if (mHTMLCommandControllerId) {
1122 controllers->GetControllerById(mHTMLCommandControllerId,
1123 getter_AddRefs(controller));
1124 if (controller) {
1125 controllers->RemoveController(controller);
1130 // Clear IDs to trigger creation of new controllers.
1131 mBaseCommandControllerId = 0;
1132 mDocStateControllerId = 0;
1133 mHTMLCommandControllerId = 0;
1136 void nsEditingSession::RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow) {
1137 nsIDocShell* docShell = aWindow ? aWindow->GetDocShell() : nullptr;
1138 nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
1139 if (webProgress) {
1140 webProgress->RemoveProgressListener(this);
1141 mProgressListenerRegistered = false;
1145 void nsEditingSession::RestoreAnimationMode(nsPIDOMWindowOuter* aWindow) {
1146 if (mInteractive) {
1147 return;
1150 nsCOMPtr<nsIDocShell> docShell = aWindow ? aWindow->GetDocShell() : nullptr;
1151 NS_ENSURE_TRUE_VOID(docShell);
1152 RefPtr<PresShell> presShell = docShell->GetPresShell();
1153 if (NS_WARN_IF(!presShell)) {
1154 return;
1156 nsPresContext* presContext = presShell->GetPresContext();
1157 NS_ENSURE_TRUE_VOID(presContext);
1159 presContext->SetImageAnimationMode(mImageAnimationMode);
1162 nsresult nsEditingSession::DetachFromWindow(nsPIDOMWindowOuter* aWindow) {
1163 NS_ENSURE_TRUE(mDoneSetup, NS_OK);
1165 NS_ASSERTION(mComposerCommandsUpdater,
1166 "mComposerCommandsUpdater should exist.");
1168 // Kill any existing reload timer
1169 if (mLoadBlankDocTimer) {
1170 mLoadBlankDocTimer->Cancel();
1171 mLoadBlankDocTimer = nullptr;
1174 // Remove controllers, webprogress listener, and otherwise
1175 // make things the way they were before we started editing.
1176 RemoveEditorControllers(aWindow);
1177 RemoveWebProgressListener(aWindow);
1178 RestoreJS(aWindow->GetCurrentInnerWindow());
1179 RestoreAnimationMode(aWindow);
1181 // Kill our weak reference to our original window, in case
1182 // it changes on restore, or otherwise dies.
1183 mDocShell = nullptr;
1185 return NS_OK;
1188 nsresult nsEditingSession::ReattachToWindow(nsPIDOMWindowOuter* aWindow) {
1189 NS_ENSURE_TRUE(mDoneSetup, NS_OK);
1190 NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
1192 NS_ASSERTION(mComposerCommandsUpdater,
1193 "mComposerCommandsUpdater should exist.");
1195 // Imitate nsEditorDocShell::MakeEditable() to reattach the
1196 // old editor to the window.
1197 nsresult rv;
1199 nsIDocShell* docShell = aWindow->GetDocShell();
1200 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
1201 mDocShell = do_GetWeakReference(docShell);
1203 // Disable JS.
1204 if (!mInteractive) {
1205 rv = DisableJS(aWindow->GetCurrentInnerWindow());
1206 NS_ENSURE_SUCCESS(rv, rv);
1209 // Tells embedder that startup is in progress.
1210 mEditorStatus = eEditorCreationInProgress;
1212 // Adds back web progress listener.
1213 rv = PrepareForEditing(aWindow);
1214 NS_ENSURE_SUCCESS(rv, rv);
1216 // Setup the command controllers again.
1217 rv = SetupEditorCommandController(
1218 nsBaseCommandController::CreateEditingController, aWindow,
1219 static_cast<nsIEditingSession*>(this), &mBaseCommandControllerId);
1220 NS_ENSURE_SUCCESS(rv, rv);
1222 rv = SetupEditorCommandController(
1223 nsBaseCommandController::CreateHTMLEditorDocStateController, aWindow,
1224 static_cast<nsIEditingSession*>(this), &mDocStateControllerId);
1225 NS_ENSURE_SUCCESS(rv, rv);
1227 if (mComposerCommandsUpdater) {
1228 mComposerCommandsUpdater->Init(*aWindow);
1231 // Get editor
1232 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditorForWindow(aWindow);
1233 if (NS_WARN_IF(!htmlEditor)) {
1234 return NS_ERROR_FAILURE;
1237 if (!mInteractive) {
1238 // Disable animation of images in this document:
1239 RefPtr<PresShell> presShell = docShell->GetPresShell();
1240 if (NS_WARN_IF(!presShell)) {
1241 return NS_ERROR_FAILURE;
1243 nsPresContext* presContext = presShell->GetPresContext();
1244 NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
1246 mImageAnimationMode = presContext->ImageAnimationMode();
1247 presContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
1250 // The third controller takes an nsIEditor as the context
1251 rv = SetupEditorCommandController(
1252 nsBaseCommandController::CreateHTMLEditorController, aWindow,
1253 static_cast<nsIEditor*>(htmlEditor.get()), &mHTMLCommandControllerId);
1254 NS_ENSURE_SUCCESS(rv, rv);
1256 // Set context on all controllers to be the editor
1257 rv = SetEditorOnControllers(*aWindow, htmlEditor);
1258 NS_ENSURE_SUCCESS(rv, rv);
1260 #ifdef DEBUG
1262 bool isEditable;
1263 rv = WindowIsEditable(aWindow, &isEditable);
1264 NS_ENSURE_SUCCESS(rv, rv);
1265 NS_ASSERTION(isEditable,
1266 "Window is not editable after reattaching editor.");
1268 #endif // DEBUG
1270 return NS_OK;
1273 HTMLEditor* nsIEditingSession::GetHTMLEditorForWindow(
1274 mozIDOMWindowProxy* aWindow) {
1275 if (NS_WARN_IF(!aWindow)) {
1276 return nullptr;
1279 nsCOMPtr<nsIDocShell> docShell =
1280 nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
1281 if (NS_WARN_IF(!docShell)) {
1282 return nullptr;
1285 return docShell->GetHTMLEditor();