tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

XULResizerElement.cpp (12030B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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/. */
      6 
      7 #include "mozilla/dom/XULResizerElement.h"
      8 
      9 #include "mozilla/EventDispatcher.h"
     10 #include "mozilla/MouseEvents.h"
     11 #include "mozilla/PresShell.h"
     12 #include "mozilla/dom/Document.h"
     13 #include "mozilla/dom/DocumentInlines.h"
     14 #include "mozilla/dom/XULResizerElementBinding.h"
     15 #include "nsContentUtils.h"
     16 #include "nsDOMCSSDeclaration.h"
     17 #include "nsIFrame.h"
     18 #include "nsLayoutUtils.h"
     19 #include "nsPresContext.h"
     20 #include "nsStyledElement.h"
     21 
     22 namespace mozilla::dom {
     23 
     24 nsXULElement* NS_NewXULResizerElement(
     25    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
     26  RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
     27  auto* nim = nodeInfo->NodeInfoManager();
     28  return new (nim) XULResizerElement(nodeInfo.forget());
     29 }
     30 
     31 static bool GetEventPoint(const WidgetGUIEvent* aEvent,
     32                          LayoutDeviceIntPoint& aPoint) {
     33  NS_ENSURE_TRUE(aEvent, false);
     34 
     35  const WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
     36  if (touchEvent) {
     37    // return false if there is more than one touch on the page, or if
     38    // we can't find a touch point
     39    if (touchEvent->mTouches.Length() != 1) {
     40      return false;
     41    }
     42 
     43    const dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0);
     44    if (!touch) {
     45      return false;
     46    }
     47    aPoint = touch->mRefPoint;
     48  } else {
     49    aPoint = aEvent->mRefPoint;
     50  }
     51  return true;
     52 }
     53 
     54 JSObject* XULResizerElement::WrapNode(JSContext* aCx,
     55                                      JS::Handle<JSObject*> aGivenProto) {
     56  return XULResizerElement_Binding::Wrap(aCx, this, aGivenProto);
     57 }
     58 
     59 XULResizerElement::Direction XULResizerElement::GetDirection() {
     60  static const mozilla::dom::Element::AttrValuesArray strings[] = {
     61      // clang-format off
     62     nsGkAtoms::topleft,    nsGkAtoms::top,    nsGkAtoms::topright,
     63     nsGkAtoms::left,                          nsGkAtoms::right,
     64     nsGkAtoms::bottomleft, nsGkAtoms::bottom, nsGkAtoms::bottomright,
     65     nsGkAtoms::bottomstart,                   nsGkAtoms::bottomend,
     66     nullptr
     67      // clang-format on
     68  };
     69 
     70  static const Direction directions[] = {
     71      // clang-format off
     72     {-1, -1}, {0, -1}, {1, -1},
     73     {-1,  0},          {1,  0},
     74     {-1,  1}, {0,  1}, {1,  1},
     75     {-1,  1},          {1,  1}  // clang-format on
     76  };
     77 
     78  const auto* frame = GetPrimaryFrame();
     79  if (!frame) {
     80    return directions[0];  // default: topleft
     81  }
     82 
     83  int32_t index =
     84      FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::dir, strings, eCaseMatters);
     85  if (index < 0) {
     86    return directions[0];  // default: topleft
     87  }
     88 
     89  if (index >= 8) {
     90    // Directions 8 and higher are RTL-aware directions and should reverse the
     91    // horizontal component if RTL.
     92    auto wm = frame->GetWritingMode();
     93    if (wm.IsPhysicalRTL()) {
     94      Direction direction = directions[index];
     95      direction.mHorizontal *= -1;
     96      return direction;
     97    }
     98  }
     99 
    100  return directions[index];
    101 }
    102 
    103 nsresult XULResizerElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
    104  if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
    105    PostHandleEventInternal(aVisitor);
    106  }
    107  return nsXULElement::PostHandleEvent(aVisitor);
    108 }
    109 
    110 Maybe<nsSize> XULResizerElement::GetCurrentSize() const {
    111  nsIContent* contentToResize = GetContentToResize();
    112  if (!contentToResize) {
    113    return Nothing();
    114  }
    115  nsIFrame* frame = contentToResize->GetPrimaryFrame();
    116  if (!frame) {
    117    return Nothing();
    118  }
    119  return Some(frame->StylePosition()->mBoxSizing == StyleBoxSizing::Content
    120                  ? frame->GetContentRect().Size()
    121                  : frame->GetRect().Size());
    122 }
    123 
    124 void XULResizerElement::PostHandleEventInternal(
    125    EventChainPostVisitor& aVisitor) {
    126  bool doDefault = true;
    127  const WidgetEvent& event = *aVisitor.mEvent;
    128  switch (event.mMessage) {
    129    case eTouchStart:
    130    case eMouseDown: {
    131      if (event.mClass == eTouchEventClass ||
    132          (event.mClass == eMouseEventClass &&
    133           event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
    134        auto size = GetCurrentSize();
    135        if (!size) {
    136          break;  // don't do anything if there's nothing to resize
    137        }
    138        // cache the content rectangle for the frame to resize
    139        mMouseDownSize = *size;
    140 
    141        // remember current mouse coordinates
    142        auto* guiEvent = event.AsGUIEvent();
    143        if (!GetEventPoint(guiEvent, mMouseDownPoint)) {
    144          break;
    145        }
    146        mTrackingMouseMove = true;
    147        PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
    148        doDefault = false;
    149      }
    150    } break;
    151 
    152    case eTouchMove:
    153    case eMouseMove: {
    154      if (mTrackingMouseMove) {
    155        nsCOMPtr<nsIContent> contentToResize = GetContentToResize();
    156        if (!contentToResize) {
    157          break;  // don't do anything if there's nothing to resize
    158        }
    159        nsIFrame* frame = contentToResize->GetPrimaryFrame();
    160        if (!frame) {
    161          break;
    162        }
    163 
    164        // both MouseMove and direction are negative when pointing to the
    165        // top and left, and positive when pointing to the bottom and right
    166 
    167        // retrieve the offset of the mousemove event relative to the mousedown.
    168        // The difference is how much the resize needs to be
    169        LayoutDeviceIntPoint refPoint;
    170        auto* guiEvent = event.AsGUIEvent();
    171        if (!GetEventPoint(guiEvent, refPoint)) {
    172          break;
    173        }
    174 
    175        const nsPoint oldPos = nsLayoutUtils::GetEventCoordinatesRelativeTo(
    176            guiEvent->mWidget, mMouseDownPoint, RelativeTo{frame});
    177        const nsPoint newPos = nsLayoutUtils::GetEventCoordinatesRelativeTo(
    178            guiEvent->mWidget, refPoint, RelativeTo{frame});
    179 
    180        nsPoint mouseMove(newPos - oldPos);
    181 
    182        // Determine which direction to resize by checking the dir attribute.
    183        // For windows and menus, ensure that it can be resized in that
    184        // direction.
    185        Direction direction = GetDirection();
    186 
    187        const CSSIntSize newSize = [&] {
    188          nsSize newAuSize = mMouseDownSize;
    189          // Check if there are any size constraints on this window.
    190          newAuSize.width += direction.mHorizontal * mouseMove.x;
    191          newAuSize.height += direction.mVertical * mouseMove.y;
    192          if (newAuSize.width < AppUnitsPerCSSPixel() && mouseMove.x != 0) {
    193            newAuSize.width = AppUnitsPerCSSPixel();
    194          }
    195          if (newAuSize.height < AppUnitsPerCSSPixel() && mouseMove.y != 0) {
    196            newAuSize.height = AppUnitsPerCSSPixel();
    197          }
    198 
    199          // When changing the size in a direction, don't allow the new size to
    200          // be less that the resizer's size. This ensures that content isn't
    201          // resized too small as to make the resizer invisible.
    202          if (auto* resizerFrame = GetPrimaryFrame()) {
    203            nsRect resizerRect = resizerFrame->GetRect();
    204            if (newAuSize.width < resizerRect.width && mouseMove.x != 0) {
    205              newAuSize.width = resizerRect.width;
    206            }
    207            if (newAuSize.height < resizerRect.height && mouseMove.y != 0) {
    208              newAuSize.height = resizerRect.height;
    209            }
    210          }
    211 
    212          // Convert the rectangle into css pixels.
    213          return CSSIntSize::FromAppUnitsRounded(newAuSize);
    214        }();
    215 
    216        // Only resize in a given direction if the new size doesn't match the
    217        // current size.
    218        if (auto currentSize = GetCurrentSize()) {
    219          auto newAuSize = CSSIntSize::ToAppUnits(newSize);
    220          if (newAuSize.width == currentSize->width) {
    221            direction.mHorizontal = 0;
    222          }
    223          if (newAuSize.height == currentSize->height) {
    224            direction.mVertical = 0;
    225          }
    226        }
    227 
    228        SizeInfo sizeInfo, originalSizeInfo;
    229        sizeInfo.width.AppendInt(newSize.width);
    230        sizeInfo.height.AppendInt(newSize.height);
    231        ResizeContent(contentToResize, direction, sizeInfo, &originalSizeInfo);
    232        MaybePersistOriginalSize(contentToResize, originalSizeInfo);
    233 
    234        doDefault = false;
    235      }
    236    } break;
    237 
    238    case ePointerClick: {
    239      auto* mouseEvent = event.AsMouseEvent();
    240      if (mouseEvent->IsLeftClickEvent()) {
    241        // Execute the oncommand event handler.
    242        nsContentUtils::DispatchXULCommand(
    243            this, false, nullptr, nullptr, mouseEvent->IsControl(),
    244            mouseEvent->IsAlt(), mouseEvent->IsShift(), mouseEvent->IsMeta(),
    245            mouseEvent->mInputSource, mouseEvent->mButton);
    246      }
    247    } break;
    248 
    249    case eTouchEnd:
    250    case eMouseUp: {
    251      if (event.mClass == eTouchEventClass ||
    252          (event.mClass == eMouseEventClass &&
    253           event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
    254        mTrackingMouseMove = false;
    255        PresShell::ReleaseCapturingContent();
    256        doDefault = false;
    257      }
    258    } break;
    259 
    260    case eMouseDoubleClick: {
    261      if (event.AsMouseEvent()->mButton == MouseButton::ePrimary) {
    262        if (nsIContent* contentToResize = GetContentToResize()) {
    263          RestoreOriginalSize(contentToResize);
    264        }
    265      }
    266    } break;
    267 
    268    default:
    269      break;
    270  }
    271 
    272  if (!doDefault) {
    273    aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
    274  }
    275 }
    276 
    277 nsIContent* XULResizerElement::GetContentToResize() const {
    278  if (!IsInComposedDoc()) {
    279    return nullptr;
    280  }
    281  // Return the parent, but skip over native anonymous content
    282  nsIContent* parent = GetParent();
    283  return parent ? parent->FindFirstNonChromeOnlyAccessContent() : nullptr;
    284 }
    285 
    286 /* static */
    287 void XULResizerElement::ResizeContent(nsIContent* aContent,
    288                                      const Direction& aDirection,
    289                                      const SizeInfo& aSizeInfo,
    290                                      SizeInfo* aOriginalSizeInfo) {
    291  RefPtr inlineStyleContent = nsStyledElement::FromNode(aContent);
    292  if (!inlineStyleContent) {
    293    return;
    294  }
    295  nsCOMPtr<nsDOMCSSDeclaration> decl = inlineStyleContent->Style();
    296  if (aOriginalSizeInfo) {
    297    decl->GetPropertyValue("width"_ns, aOriginalSizeInfo->width);
    298    decl->GetPropertyValue("height"_ns, aOriginalSizeInfo->height);
    299  }
    300 
    301  // only set the property if the element could have changed in that
    302  // direction
    303  if (aDirection.mHorizontal) {
    304    nsAutoCString widthstr(aSizeInfo.width);
    305    if (!widthstr.IsEmpty() && !StringEndsWith(widthstr, "px"_ns)) {
    306      widthstr.AppendLiteral("px");
    307    }
    308    decl->SetProperty("width"_ns, widthstr, ""_ns, IgnoreErrors());
    309  }
    310 
    311  if (aDirection.mVertical) {
    312    nsAutoCString heightstr(aSizeInfo.height);
    313    if (!heightstr.IsEmpty() && !StringEndsWith(heightstr, "px"_ns)) {
    314      heightstr.AppendLiteral("px");
    315    }
    316    decl->SetProperty("height"_ns, heightstr, ""_ns, IgnoreErrors());
    317  }
    318 }
    319 
    320 /* static */
    321 void XULResizerElement::MaybePersistOriginalSize(nsIContent* aContent,
    322                                                 const SizeInfo& aSizeInfo) {
    323  nsresult rv;
    324  aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv);
    325  if (rv != NS_PROPTABLE_PROP_NOT_THERE) {
    326    return;
    327  }
    328 
    329  UniquePtr<SizeInfo> sizeInfo(new SizeInfo(aSizeInfo));
    330  rv = aContent->SetProperty(
    331      nsGkAtoms::_moz_original_size, sizeInfo.get(),
    332      nsINode::DeleteProperty<XULResizerElement::SizeInfo>);
    333  if (NS_SUCCEEDED(rv)) {
    334    (void)sizeInfo.release();
    335  }
    336 }
    337 
    338 /* static */
    339 void XULResizerElement::RestoreOriginalSize(nsIContent* aContent) {
    340  nsresult rv;
    341  SizeInfo* sizeInfo = static_cast<SizeInfo*>(
    342      aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv));
    343  if (NS_FAILED(rv)) {
    344    return;
    345  }
    346 
    347  NS_ASSERTION(sizeInfo, "We set a null sizeInfo!?");
    348  Direction direction = {1, 1};
    349  ResizeContent(aContent, direction, *sizeInfo, nullptr);
    350  aContent->RemoveProperty(nsGkAtoms::_moz_original_size);
    351 }
    352 
    353 }  // namespace mozilla::dom