Bug 1155493 - Part 1: Add CaretStateChangedEvent and corresponding utility function. r=roc, sr=smaug
authorMorris Tseng <mtseng@mozilla.com>
Tue, 19 May 2015 20:59:00 -0400
changeset 246313 c816a43492118415d01f7dc44148c720eb7dbb21
parent 246312 3fa23f765209c4dff4f662832ff3ec54498cba4d
child 246314 970957a8ad2a3e2e1a6b32a86999e0f38ead860f
push id28826
push userryanvm@gmail.com
push dateFri, 29 May 2015 20:58:36 +0000
treeherdermozilla-central@45a4d6336c73 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, smaug
bugs1155493
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1155493 - Part 1: Add CaretStateChangedEvent and corresponding utility function. r=roc, sr=smaug
dom/webidl/CaretStateChangedEvent.webidl
dom/webidl/moz.build
layout/base/AccessibleCaretManager.cpp
layout/base/AccessibleCaretManager.h
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CaretStateChangedEvent.webidl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+enum CaretChangedReason {
+  "visibilitychange",
+  "updateposition",
+  "longpressonemptycontent",
+  "taponcaret",
+  "presscaret",
+  "releasecaret"
+};
+
+dictionary CaretStateChangedEventInit : EventInit {
+  boolean collapsed = true;
+  DOMRectReadOnly? boundingClientRect = null;
+  CaretChangedReason reason = "visibilitychange";
+  boolean caretVisible = false;
+  boolean selectionVisible = false;
+};
+
+[Constructor(DOMString type, optional CaretStateChangedEventInit eventInit),
+ ChromeOnly]
+interface CaretStateChangedEvent : Event {
+  readonly attribute boolean collapsed;
+  readonly attribute DOMRectReadOnly? boundingClientRect;
+  readonly attribute CaretChangedReason reason;
+  readonly attribute boolean caretVisible;
+  readonly attribute boolean selectionVisible;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -722,16 +722,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'AutocompleteErrorEvent.webidl',
     'BlobEvent.webidl',
     'CallEvent.webidl',
     'CallGroupErrorEvent.webidl',
     'CameraClosedEvent.webidl',
     'CameraConfigurationEvent.webidl',
     'CameraFacesDetectedEvent.webidl',
     'CameraStateChangeEvent.webidl',
+    'CaretStateChangedEvent.webidl',
     'CFStateChangeEvent.webidl',
     'CloseEvent.webidl',
     'CSSFontFaceLoadEvent.webidl',
     'DataErrorEvent.webidl',
     'DataStoreChangeEvent.webidl',
     'DeviceLightEvent.webidl',
     'DeviceOrientationEvent.webidl',
     'DeviceProximityEvent.webidl',
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -861,9 +861,80 @@ AccessibleCaretManager::LaunchCaretTimeo
 void
 AccessibleCaretManager::CancelCaretTimeoutTimer()
 {
   if (mCaretTimeoutTimer) {
     mCaretTimeoutTimer->Cancel();
   }
 }
 
+void
+AccessibleCaretManager::DispatchCaretStateChangedEvent(CaretChangedReason aReason) const
+{
+  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+  // Holding PresShell to prevent AccessibleCaretManager to be destroyed.
+  nsCOMPtr<nsIPresShell> presShell = mPresShell;
+  // XXX: Do we need to flush layout?
+  presShell->FlushPendingNotifications(Flush_Layout);
+  if (presShell->IsDestroying()) {
+    return;
+  }
+
+  Selection* sel = GetSelection();
+  if (!sel) {
+    return;
+  }
+
+  nsIDocument* doc = mPresShell->GetDocument();
+  MOZ_ASSERT(doc);
+
+  CaretStateChangedEventInit init;
+  init.mBubbles = true;
+
+  const nsRange* range = sel->GetAnchorFocusRange();
+  nsINode* commonAncestorNode = nullptr;
+  if (range) {
+    commonAncestorNode = range->GetCommonAncestor();
+  }
+
+  if (!commonAncestorNode) {
+    commonAncestorNode = sel->GetFrameSelection()->GetAncestorLimiter();
+  }
+
+  nsRefPtr<DOMRect> domRect = new DOMRect(ToSupports(doc));
+  nsRect rect = nsContentUtils::GetSelectionBoundingRect(sel);
+
+  nsIFrame* commonAncestorFrame = nullptr;
+  nsIFrame* rootFrame = mPresShell->GetRootFrame();
+
+  if (commonAncestorNode && commonAncestorNode->IsContent()) {
+    commonAncestorFrame = commonAncestorNode->AsContent()->GetPrimaryFrame();
+  }
+
+  if (commonAncestorFrame && rootFrame) {
+    nsLayoutUtils::TransformRect(rootFrame, commonAncestorFrame, rect);
+    nsRect clampedRect = nsLayoutUtils::ClampRectToScrollFrames(commonAncestorFrame,
+                                                                rect);
+    nsLayoutUtils::TransformRect(commonAncestorFrame, rootFrame, clampedRect);
+    domRect->SetLayoutRect(clampedRect);
+    init.mSelectionVisible = !clampedRect.IsEmpty();
+    init.mBoundingClientRect = domRect;
+  } else {
+    domRect->SetLayoutRect(rect);
+    init.mSelectionVisible = true;
+  }
+
+  init.mBoundingClientRect = domRect;
+  init.mReason = aReason;
+  init.mCollapsed = sel->IsCollapsed();
+  init.mCaretVisible = mFirstCaret->IsLogicallyVisible() ||
+                       mSecondCaret->IsLogicallyVisible();
+
+  nsRefPtr<CaretStateChangedEvent> event =
+    CaretStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozcaretstatechanged"), init);
+
+  event->SetTrusted(true);
+  event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
+  bool ret;
+  doc->DispatchEvent(event, &ret);
+}
+
 } // namespace mozilla
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -8,16 +8,17 @@
 #define AccessibleCaretManager_h
 
 #include "nsCOMPtr.h"
 #include "nsCoord.h"
 #include "nsIFrame.h"
 #include "nsISelectionListener.h"
 #include "nsRefPtr.h"
 #include "nsWeakReference.h"
+#include "mozilla/dom/CaretStateChangedEvent.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 
 class nsFrameSelection;
 class nsIContent;
 class nsIPresShell;
 struct nsPoint;
@@ -127,16 +128,20 @@ protected:
   nsresult DragCaretInternal(const nsPoint& aPoint);
   nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
   void ClearMaintainedSelection() const;
 
   dom::Selection* GetSelection() const;
   already_AddRefed<nsFrameSelection> GetFrameSelection() const;
   nsIContent* GetFocusedContent() const;
 
+  // This function will call FlushPendingNotifications. So caller must ensure
+  // everything exists after calling this method.
+  void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason) const;
+
   // If we're dragging the first caret, we do not want to drag it over the
   // previous character of the second caret. Same as the second caret. So we
   // check if content offset exceeds the previous/next character of second/first
   // caret base the active caret.
   bool CompareRangeWithContentOffset(nsIFrame::ContentOffsets& aOffsets);
 
   // Timeout in milliseconds to hide the AccessibleCaret under cursor mode while
   // no one touches it.