Bug 1155493 - Part 1: Add CaretStateChangedEvent and corresponding utility function. r=roc, sr=smaug
☠☠ backed out by cb33de12c0b5 ☠ ☠
authorMorris Tseng <mtseng@mozilla.com>
Tue, 19 May 2015 20:59:00 -0400
changeset 245483 896beb5088a7085564f48cceb70ba664ef82acf0
parent 245482 7e403c08e5395f100b8afeb18e4b92c46b3edfbe
child 245484 58b7c1eaf3c82d3c546bef8f85ee36222421da57
push id28806
push userphilringnalda@gmail.com
push dateTue, 26 May 2015 02:10:16 +0000
treeherdermozilla-central@4362d9251296 [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
@@ -720,16 +720,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.