Global Metrics

path: .metrics.nom.closures
old: 0.0
new: 2.0

path: .metrics.nom.functions
old: 20.0
new: 17.0

path: .metrics.nom.total
old: 20.0
new: 19.0

path: .metrics.cognitive.average
old: 0.0
new: 0.5789473684210527

path: .metrics.cognitive.sum
old: 0.0
new: 11.0

path: .metrics.mi.mi_sei
old: -14.269474730390726
new: -8.526717464579328

path: .metrics.mi.mi_visual_studio
old: 5.930942612387386
new: 14.106869054552885

path: .metrics.mi.mi_original
old: 10.14191186718243
new: 24.122746083285435

path: .metrics.nargs.average
old: 1.15
new: 0.42105263157894735

path: .metrics.nargs.sum
old: 23.0
new: 8.0

path: .metrics.cyclomatic.sum
old: 34.0
new: 38.0

path: .metrics.cyclomatic.average
old: 1.0303030303030305
new: 1.4074074074074074

path: .metrics.nexits.average
old: 0.3
new: 0.5789473684210527

path: .metrics.nexits.sum
old: 6.0
new: 11.0

path: .metrics.halstead.purity_ratio
old: 1.8127404273924628
new: 1.6786057373752006

path: .metrics.halstead.n2
old: 280.0
new: 197.0

path: .metrics.halstead.N2
old: 579.0
new: 390.0

path: .metrics.halstead.time
old: 10379.413172541495
new: 11255.96663236292

path: .metrics.halstead.volume
old: 10629.328942316191
new: 7580.941629317933

path: .metrics.halstead.vocabulary
old: 297.0
new: 224.0

path: .metrics.halstead.length
old: 1294.0
new: 971.0

path: .metrics.halstead.N1
old: 715.0
new: 581.0

path: .metrics.halstead.n1
old: 17.0
new: 27.0

path: .metrics.halstead.estimated_program_length
old: 2345.686113045847
new: 1629.92617099132

path: .metrics.halstead.difficulty
old: 17.576785714285716
new: 26.725888324873097

path: .metrics.halstead.effort
old: 186829.43710574697
new: 202607.39938253257

path: .metrics.halstead.level
old: 0.05689322361068779
new: 0.03741690408357075

path: .metrics.halstead.bugs
old: 1.0893705126318372
new: 1.1498705381538254

path: .metrics.loc.ploc
old: 249.0
new: 199.0

path: .metrics.loc.blank
old: 101.0
new: 44.0

path: .metrics.loc.sloc
old: 646.0
new: 287.0

path: .metrics.loc.lloc
old: 20.0
new: 33.0

path: .metrics.loc.cloc
old: 296.0
new: 44.0

Spaces Data

Minimal test - lines (21, 285)

path: .spaces[1].metrics.nom.functions
old: 0.0
new: 17.0

path: .spaces[1].metrics.nom.closures
old: 0.0
new: 2.0

path: .spaces[1].metrics.nom.total
old: 0.0
new: 19.0

path: .spaces[1].metrics.halstead.N1
old: 0.0
new: 580.0

path: .spaces[1].metrics.halstead.purity_ratio
old: null
new: 1.6148536670216989

path: .spaces[1].metrics.halstead.n1
old: 0.0
new: 27.0

path: .spaces[1].metrics.halstead.N2
old: 1.0
new: 379.0

path: .spaces[1].metrics.halstead.effort
old: 0.0
new: 202224.6805192158

path: .spaces[1].metrics.halstead.estimated_program_length
old: null
new: 1548.6446666738093

path: .spaces[1].metrics.halstead.n2
old: 1.0
new: 188.0

path: .spaces[1].metrics.halstead.bugs
old: 0.0
new: 1.1484220362343256

path: .spaces[1].metrics.halstead.difficulty
old: 0.0
new: 27.215425531914892

path: .spaces[1].metrics.halstead.volume
old: 0.0
new: 7430.516942756292

path: .spaces[1].metrics.halstead.time
old: 0.0
new: 11234.704473289768

path: .spaces[1].metrics.halstead.length
old: 1.0
new: 959.0

path: .spaces[1].metrics.halstead.vocabulary
old: 1.0
new: 215.0

path: .spaces[1].metrics.halstead.level
old: null
new: 0.03674386787843252

path: .spaces[1].metrics.nexits.average
old: null
new: 0.5789473684210527

path: .spaces[1].metrics.nexits.sum
old: 0.0
new: 11.0

path: .spaces[1].metrics.cyclomatic.average
old: 1.0
new: 1.44

path: .spaces[1].metrics.cyclomatic.sum
old: 1.0
new: 36.0

path: .spaces[1].metrics.loc.lloc
old: 0.0
new: 33.0

path: .spaces[1].metrics.loc.sloc
old: 1.0
new: 265.0

path: .spaces[1].metrics.loc.ploc
old: 1.0
new: 187.0

path: .spaces[1].metrics.loc.blank
old: 0.0
new: 40.0

path: .spaces[1].metrics.loc.cloc
old: 0.0
new: 38.0

path: .spaces[1].metrics.nargs.sum
old: 0.0
new: 8.0

path: .spaces[1].metrics.nargs.average
old: null
new: 0.42105263157894735

path: .spaces[1].metrics.cognitive.average
old: null
new: 0.5789473684210527

path: .spaces[1].metrics.cognitive.sum
old: 0.0
new: 11.0

path: .spaces[1].metrics.mi.mi_sei
old: null
new: -6.877175798631306

path: .spaces[1].metrics.mi.mi_original
old: null
new: 25.9789531251527

path: .spaces[1].metrics.mi.mi_visual_studio
old: null
new: 15.19237024862731

Code

namespace mozilla {

class TextComposition;

namespace dom {
class BrowserChild;
}

namespace widget {

class GeckoEditableSupport final
    : public TextEventDispatcherListener,
      public java::GeckoEditableChild::Natives {
  /*
      Rules for managing IME between Gecko and Java:

      * Gecko controls the text content, and Java shadows the Gecko text
         through text updates
      * Gecko and Java maintain separate selections, and synchronize when
         needed through selection updates and set-selection events
      * Java controls the composition, and Gecko shadows the Java
         composition through update composition events
  */

  using EditableBase = java::GeckoEditableChild::Natives;
  using EditableClient = java::SessionTextInput::EditableClient;
  using EditableListener = java::SessionTextInput::EditableListener;

  struct IMETextChange final {
    int32_t mStart, mOldEnd, mNewEnd;

    IMETextChange() : mStart(-1), mOldEnd(-1), mNewEnd(-1) {}

    explicit IMETextChange(const IMENotification& aIMENotification)
        : mStart(aIMENotification.mTextChangeData.mStartOffset),
          mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset),
          mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset) {
      MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
                 "IMETextChange initialized with wrong notification");
      MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(),
                 "The text change notification isn't initialized");
      MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(),
                 "The text change notification is out of range");
    }

    bool IsEmpty() const { return mStart < 0; }
  };

  enum FlushChangesFlag {
    // Not retrying.
    FLUSH_FLAG_NONE,
    // Retrying due to IME text changes during flush.
    FLUSH_FLAG_RETRY,
    // Retrying due to IME sync exceptions during flush.
    FLUSH_FLAG_RECOVER
  };

  enum RemoveCompositionFlag { CANCEL_IME_COMPOSITION, COMMIT_IME_COMPOSITION };

  const bool mIsRemote;
  jni::NativeWeakPtr mWindow;  // Parent only
  RefPtr mDispatcher;
  java::GeckoEditableChild::GlobalRef mEditable;
  bool mEditableAttached;
  InputContext mInputContext;
  AutoTArray, 4> mIMEKeyEvents;
  AutoTArray mIMETextChanges;
  RefPtr mIMERanges;
  RefPtr mDisposeRunnable;
  int32_t mIMEMaskEventsCount;         // Mask events when > 0.
  int32_t mIMEFocusCount;              // We are focused when > 0.
  bool mIMEDelaySynchronizeReply;      // We reply asynchronously when true.
  int32_t mIMEActiveSynchronizeCount;  // The number of replies being delayed.
  int32_t mIMEActiveCompositionCount;  // The number of compositions expected.
  uint32_t mDisposeBlockCount;
  bool mIMESelectionChanged;
  bool mIMETextChangedDuringFlush;
  bool mIMEMonitorCursor;

  nsIWidget* GetWidget() const;
  nsWindow* GetNsWindow() const;

  nsresult BeginInputTransaction(TextEventDispatcher* aDispatcher) {
    if (mIsRemote) {
      return aDispatcher->BeginInputTransaction(this);
    } else {
      return aDispatcher->BeginNativeInputTransaction();
    }
  }

  virtual ~GeckoEditableSupport() {}

  RefPtr GetComposition() const;
  bool RemoveComposition(RemoveCompositionFlag aFlag = COMMIT_IME_COMPOSITION);
  void SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg);
  void AddIMETextChange(const IMETextChange& aChange);
  void PostFlushIMEChanges();
  void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
  void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
  void AsyncNotifyIME(int32_t aNotification);
  void UpdateCompositionRects();
  bool DoReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText);
  bool DoUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags);
  void OnNotifyIMEOfCompositionEventHandled();
  void NotifyIMEContext(const InputContext& aContext,
                        const InputContextAction& aAction);

 public:
  template 
  static void OnNativeCall(Functor&& aCall) {
    struct IMEEvent : nsAppShell::LambdaEvent {
      explicit IMEEvent(Functor&& l)
          : nsAppShell::LambdaEvent(std::move(l)) {}

      bool IsUIEvent() const override {
        using GES = GeckoEditableSupport;
        if (this->lambda.IsTarget(&GES::OnKeyEvent) ||
            this->lambda.IsTarget(&GES::OnImeReplaceText) ||
            this->lambda.IsTarget(&GES::OnImeUpdateComposition)) {
          return true;
        }
        return false;
      }

      void Run() override {
        if (NS_WARN_IF(!this->lambda.GetNativeObject())) {
          // Ignore stale calls after disposal.
          jni::GetGeckoThreadEnv()->ExceptionClear();
          return;
        }
        nsAppShell::LambdaEvent::Run();
      }
    };
    nsAppShell::PostEvent(mozilla::MakeUnique(std::move(aCall)));
  }

  static void SetOnBrowserChild(dom::BrowserChild* aBrowserChild);

  // Constructor for main process GeckoEditableChild.
  GeckoEditableSupport(jni::NativeWeakPtr aWindow,
                       java::GeckoEditableChild::Param aEditableChild)
      : mIsRemote(!aWindow.IsAttached()),
        mWindow(aWindow),
        mEditable(aEditableChild),
        mEditableAttached(!mIsRemote),
        mIMERanges(new TextRangeArray()),
        mIMEMaskEventsCount(1),  // Mask IME events since there's no focus yet
        mIMEFocusCount(0),
        mIMEDelaySynchronizeReply(false),
        mIMEActiveSynchronizeCount(0),
        mDisposeBlockCount(0),
        mIMESelectionChanged(false),
        mIMETextChangedDuringFlush(false),
        mIMEMonitorCursor(false) {}

  // Constructor for content process GeckoEditableChild.
  explicit GeckoEditableSupport(java::GeckoEditableChild::Param aEditableChild)
      : GeckoEditableSupport(nullptr, aEditableChild) {}

  NS_DECL_ISUPPORTS

  // TextEventDispatcherListener methods
  NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
                       const IMENotification& aNotification) override;

  NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;

  NS_IMETHOD_(void)
  OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;

  NS_IMETHOD_(void)
  WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
                            WidgetKeyboardEvent& aKeyboardEvent,
                            uint32_t aIndexOfKeypress, void* aData) override;

  void SetInputContext(const InputContext& aContext,
                       const InputContextAction& aAction);

  InputContext GetInputContext();

  bool HasIMEFocus() const { return mIMEFocusCount != 0; }

  void AddBlocker() { mDisposeBlockCount++; }

  void ReleaseBlocker() {
    mDisposeBlockCount--;

    if (!mDisposeBlockCount && mDisposeRunnable) {
      if (HasIMEFocus()) {
        // If we have IME focus, GeckoEditableChild is already attached again.
        // So disposer is unnecessary.
        mDisposeRunnable = nullptr;
        return;
      }

      RefPtr self(this);
      RefPtr disposer = std::move(mDisposeRunnable);

      nsAppShell::PostEvent(
          [self = std::move(self), disposer = std::move(disposer)] {
            self->mEditableAttached = false;
            disposer->Run();
          });
    }
  }

  bool IsGeckoEditableUsed() const { return mDisposeBlockCount != 0; }

  // GeckoEditableChild methods
  using EditableBase::AttachNative;
  using EditableBase::DisposeNative;

  const java::GeckoEditableChild::Ref& GetJavaEditable() { return mEditable; }

  void OnWeakNonIntrusiveDetach(already_AddRefed aDisposer) {
    RefPtr self(this);
    nsAppShell::PostEvent(
        [self = std::move(self), disposer = RefPtr(aDisposer)] {
          if (self->IsGeckoEditableUsed()) {
            // Current calling stack uses GeckoEditableChild, so we should
            // not dispose it now.
            self->mDisposeRunnable = disposer;
            return;
          }
          self->mEditableAttached = false;
          disposer->Run();
        });
  }

  // Transfer to a new parent.
  void TransferParent(jni::Object::Param aEditableParent);

  // Handle an Android KeyEvent.
  void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
                  int32_t aMetaState, int32_t aKeyPressMetaState, int64_t aTime,
                  int32_t aDomPrintableKeyValue, int32_t aRepeatCount,
                  int32_t aFlags, bool aIsSynthesizedImeKey,
                  jni::Object::Param originalEvent);

  // Synchronize Gecko thread with the InputConnection thread.
  void OnImeSynchronize();

  // Replace a range of text with new text.
  void OnImeReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText);

  // Add styling for a range within the active composition.
  void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd,
                                int32_t aRangeType, int32_t aRangeStyle,
                                int32_t aRangeLineStyle, bool aRangeBoldLine,
                                int32_t aRangeForeColor,
                                int32_t aRangeBackColor,
                                int32_t aRangeLineColor);

  // Update styling for the active composition using previous-added ranges.
  void OnImeUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags);

  // Set cursor mode whether IME requests
  void OnImeRequestCursorUpdates(int aRequestMode);

  // Commit current composition to sync Gecko text state with Java.
  void OnImeRequestCommit();
};

}  // namespace widget
}  // namespace mozilla