mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			215 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
############################
 | 
						|
Editor module specific rules
 | 
						|
############################
 | 
						|
 | 
						|
The editor module has not been maintained aggressively about a decade. Therefore, this module needs
 | 
						|
to be treated as a young module or in a transition period to align the behavior to the other
 | 
						|
browsers and take modern C++ style.
 | 
						|
 | 
						|
Undoubtedly, this editor module is under rewritten for modern and optimized for current specs.
 | 
						|
Additionally, this module does really complicated things which may cause security issues.
 | 
						|
Therefore, there are specific rules:
 | 
						|
 | 
						|
Treat other browsers behavior as standard if the behavior is reasonable
 | 
						|
=======================================================================
 | 
						|
 | 
						|
The editing behavior is not standardized since as you see too many lines in the editor classes, the
 | 
						|
number of cases which need to handle edge cases is crazy and that makes it impossible to standardize.
 | 
						|
Additionally, our editor behavior is not so stable. Some behaviors were aligned to Internet Explorer,
 | 
						|
some other behaviors were not for making "better" UX for users of email composer and HTML composer
 | 
						|
which were in SeaMonkey, and the other browser engines (Blink and WebKit) have same roots but the
 | 
						|
behavior is different from both IE and Gecko.
 | 
						|
 | 
						|
Therefore, there were no reference behavior.
 | 
						|
 | 
						|
In these days, compatibility between browsers becomes more important, and fortunately, the behavior
 | 
						|
of Blink (Chrome/Chromium) which has the biggest market share is more reasonable than ours in a lot
 | 
						|
of cases. Therefore, if we get web-compat issue reports, we should align the behavior to Blink in
 | 
						|
theory.
 | 
						|
 | 
						|
However, if Blink's behavior is also odd, this is the worst case. In this case, we should try to
 | 
						|
align the behavior to WebKit if and only if WebKit's behavior is different from Blink and
 | 
						|
reasonable, or doing something "better" for hiding the issue from web-apps and file an issue to the
 | 
						|
Editing Working Group with creating a "tentative" web-platform test.
 | 
						|
 | 
						|
Don't make methods of editor classes public if they are used only by helper classes
 | 
						|
===================================================================================
 | 
						|
 | 
						|
Although this is a silly rule. Of course, APIs of editor classes need to be public for the other
 | 
						|
modules. However, the other methods which are used only by helper classes in the editor module --the
 | 
						|
methods may be crashed if called by the other modules because editor classes store and guarantee the
 | 
						|
colleagues (e.g., ``Selection``) when it starts to handle an edit action (edit command or
 | 
						|
operation)--  does not want to do it for the performance reason. Therefore, such methods are now
 | 
						|
declared as protected methods and the caller classes are registered as friends.
 | 
						|
 | 
						|
For solving this issue, we could split the editor classes one is exported and the other is not
 | 
						|
exposed, and make the former to proxies and own the latter.  However, this approach might cause
 | 
						|
performance regressions and requires a lot of line changes (at least each method definition and
 | 
						|
warning messages at the caller sides).  Tracked in
 | 
						|
`bug 1555916 <https://bugzilla.mozilla.org/show_bug.cgi?id=1555916>`__.
 | 
						|
 | 
						|
Steps to handle one editor command or operation
 | 
						|
===============================================
 | 
						|
 | 
						|
One edit command or operation is called "edit action" in the editor module.  Handling it starts
 | 
						|
when an XPCOM method or a public method which is named as ``*AsAction``. Those methods create
 | 
						|
``AutoEditActionDataSetter`` in the stack first, then, call one of ``CanHandle()``,
 | 
						|
``CanHandleAndMaybeDispatchBeforeInputEvent()`` or ``CanHandleAndFlushPendingNotifications()``.
 | 
						|
If ``CanHandleAndMaybeDispatchBeforeInputEvent()`` causes dispatching ``beforeinput`` event and if
 | 
						|
the event is consumed by the web app, it returns ``NS_ERROR_EDITOR_ACTION_CANCELED``. In this case,
 | 
						|
the method can do anything due to the ``beforeinput`` event definition.
 | 
						|
 | 
						|
At this time, ``AutoEditActionDataSetter`` stores ``Selection`` etc which are required for handling
 | 
						|
the edit action in it and set ``EditorBase::mEditActionData`` to its address. Then all methods of
 | 
						|
editor can access the objects via the pointer (typically wrapped in inline methods) and the lifetime
 | 
						|
of the objects are guaranteed.
 | 
						|
 | 
						|
Then, the methods call one or more edit-sub action handlers.  E.g., when user types a character
 | 
						|
with a non-collapsed selection range, editor needs to delete the selected content first and insert
 | 
						|
the character there. For implementing this behavior, "insert text" edit action handler needs to call
 | 
						|
"delete selection" sub-action handler and "insert text" sub-action handler. The sub-edit action
 | 
						|
handlers are named as ``*AsSubAction``.
 | 
						|
 | 
						|
The callers of edit sub-action handlers or the handlers themselves create ``AutoPlaceholderBatch``
 | 
						|
in the stack. This creates a placeholder transaction to make all transactions undoable with one
 | 
						|
"undo" command.
 | 
						|
 | 
						|
Then, each edit sub-action handler creates ``AutoEditSubActionNotifier`` in the stack and if it's
 | 
						|
the topmost edit sub-action handling, ``OnStartToHandleTopLevelEditSubAction()`` is called at the
 | 
						|
creation and ``OnEndHandlingTopLevelEditSubAction()`` is called at the destruction. The latter will
 | 
						|
clean up the modified range, e.g., remove unnecessary empty nodes.
 | 
						|
 | 
						|
Finally, the edit sub-actions does something while ``AutoEditSubActionNotifier`` is alive. Helper
 | 
						|
methods of edit sub-action handlers are typically named as ``*WithTransaction`` because they are
 | 
						|
done with transaction classes for making everything undoable.
 | 
						|
 | 
						|
Don't update Selection immediately
 | 
						|
==================================
 | 
						|
 | 
						|
Changing the ranges of ``Selection`` is expensive (due ot validating new range, notifying new
 | 
						|
selected or unselected frames, notifying selection listeners, etc), and retrieving current
 | 
						|
``Selection`` ranges at staring to handle something makes the code statefull which is harder to
 | 
						|
debug when you investigate a bug. Therefore, each method should return new caret position or
 | 
						|
update ranges given as in/out parameter of ``AutoRangeArray``.  ``Result<CaretPoint, nsresult>``
 | 
						|
is a good result type for the former, and the latter is useful style if the method needs to keep
 | 
						|
``Selection`` similar to given ranges, e.g., when paragraphs around selection are changed to
 | 
						|
different type of blocks. Finally, edit sub-action handler methods should update ``Selection``
 | 
						|
before destroying ``AutoEditSubActionNotifier`` whose post-processing requires ``Selection``.
 | 
						|
 | 
						|
Don't add new things into OnEndHandlingTopLevelEditSubAction()
 | 
						|
==============================================================
 | 
						|
 | 
						|
When the topmost edit sub-action is handled, ``OnEndHandlingTopLevelEditSubAction`` is called and
 | 
						|
it cleans up something in (or around) the modified range. However, this "post-processing" approach
 | 
						|
makes it harder to change the behavior for fixing web-compat issues. For example, it deletes empty
 | 
						|
nodes in the range, but if only some empty nodes are inserted intentionally, it doesn't have the
 | 
						|
details and may unexpectedly delete necessary empty nodes.
 | 
						|
 | 
						|
Instead, new things should be done immediately at or after modifying the DOM tree, and if it
 | 
						|
requires to disable the post-processing, add new ``bool`` flag to
 | 
						|
``EditorBase::TopLevelEditSubActionData`` and when it's set, make
 | 
						|
``OnEndHandlingTopLevelEditSubAction`` stop doing something.
 | 
						|
 | 
						|
Don't use NS_WARN_IF for checking NS_FAILED, isErr() and Failed()
 | 
						|
=================================================================
 | 
						|
 | 
						|
The warning messages like ``NS_FAILED(rv)`` does not help except the line number, and in the cases
 | 
						|
of that we get web-compat reports, somewhere in the editor modules may get unexpected result. For
 | 
						|
saving the investigation time of web-compat issues, each failure should warn which method call
 | 
						|
failed, for example:
 | 
						|
 | 
						|
.. code:: cpp
 | 
						|
 | 
						|
  nsresult rv = DoSomething();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("HTMLEditor::DoSomething() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
These warnings will let you know the stack of failure in debug build. In other words, when you
 | 
						|
investigate a web-compat issue in editor, you should do the steps to reproduce in debug build first.
 | 
						|
Then, you'd see failure point stack in the terminal.
 | 
						|
 | 
						|
Return NS_ERROR_EDITOR_DESTROYED when editor gets destroyed
 | 
						|
===========================================================
 | 
						|
 | 
						|
The most critical error while an editor class method is running is what the editor instance is
 | 
						|
destroyed by the web app. This can be checked with a call of ``EditorBase::Destroyed()`` and
 | 
						|
if it returns ``true``, methods should return ``NS_ERROR_EDITOR_DESTROYED`` with stopping handling
 | 
						|
anything. Then, all callers which handle the error result properly will stop handling too.
 | 
						|
Finally, public methods should return ``EditorBase::ToGenericNSResult(rv)`` instead of exposing
 | 
						|
an internal error of the editor module.
 | 
						|
 | 
						|
Note that destroying the editor is intentional thing for the web app. Thus we should not throw
 | 
						|
exception for this failure reason. Therefore, the public methods shouldn't return error.
 | 
						|
 | 
						|
When you make a method return ``NS_ERROR_EDITOR_DESTROYED`` properly, you should mark the method
 | 
						|
as ``[[nodiscard]]``. In other words, if you see ``[[nodiscard]]`` in method definition and it
 | 
						|
returns ``nsresult`` or ``Result<*, nsresult>``, the method callers do not need to check
 | 
						|
``Destroyed()`` by themselves.
 | 
						|
 | 
						|
Use reference instead of pointer as far as possible
 | 
						|
===================================================
 | 
						|
 | 
						|
When you create or redesign a method, it should take references instead of pointers if they take.
 | 
						|
This rule forces that the caller to do null-check and this avoids a maybe unexpected case like:
 | 
						|
 | 
						|
.. code:: cpp
 | 
						|
 | 
						|
  inline bool IsBRElement(const nsINode* aNode) {
 | 
						|
    return aNode && aNode->IsHTMLElement(nsGkAtoms::br);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoSomethingExceptIfBRElement(const nsINode* aNode) {
 | 
						|
    if (IsBRElement(aNode)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Do something for non-BR element node.
 | 
						|
  }
 | 
						|
 | 
						|
In this case, ``DoSomethingExceptIfBRElement`` expects that ``aNode`` is never ``nullptr`` but it
 | 
						|
could be at least in build time. Using reference fixes this mistake at build time.
 | 
						|
 | 
						|
Use ``EditorUtils`` or ``HTMLEditUtils`` for stateless methods
 | 
						|
==============================================================
 | 
						|
 | 
						|
When you create a new static method to the editor classes or a new inline method in cpp file which
 | 
						|
defines the editor classes, please check if it's a common method which may be used from other
 | 
						|
places in the editor module.  If it's possible to be used only in ``HTMLEditor`` or its helper
 | 
						|
classes, the method should be in ``HTMLEditUtils``.  If it's possible be used in ``EditorBase`` or
 | 
						|
``TextEditor`` or their helper classes, it should be in ``EditorUtils``.
 | 
						|
 | 
						|
Don't use bool argument
 | 
						|
=======================
 | 
						|
 | 
						|
If you create a new method which take one or more ``bool`` arguments, use ``enum class`` instead
 | 
						|
since ``true`` or ``false`` in the caller side is not easy to read. For example, you must not
 | 
						|
be able to understand what this example mean:
 | 
						|
 | 
						|
.. code:: cpp
 | 
						|
 | 
						|
  if (IsEmpty(aNode, true)) {
 | 
						|
 | 
						|
For avoiding this issue, you should create new ``enum class`` for each.  E.g.,
 | 
						|
 | 
						|
.. code:: cpp
 | 
						|
 | 
						|
  if (IsEmpty(aNode, TreatSingleBR::AsVisible)) {
 | 
						|
 | 
						|
Basically, both ``enum class`` name and its value names explains what it means fluently. However, if
 | 
						|
it's impossible, use ``No`` and ``Yes`` for the value like:
 | 
						|
 | 
						|
.. code:: cpp
 | 
						|
 | 
						|
  if (DoSomething(aNode, OnlyIfEmpty::Yes)) {
 | 
						|
 | 
						|
Don't use out parameters
 | 
						|
========================
 | 
						|
 | 
						|
In most cases, editor methods meet error of low level APIs, thus editor methods usually return error
 | 
						|
code. On the other hand, a lot of code need to return computed things, e.g., new caret position,
 | 
						|
whether it's handled, ignored or canceled, a target node looked for, etc. We used ``nsresult`` for
 | 
						|
the return value type and out parameters for the other results, but it makes callers scattering a
 | 
						|
lot of auto variables and reusing them makes the code harder to understand.
 | 
						|
 | 
						|
Now we can use ``mozilla::Result<Foo, nsresult>`` instead.
 |