Skip to content

Commit

Permalink
Bug 1889503 - Implement new document.caretPositionFromPoint Shadow DO…
Browse files Browse the repository at this point in the history
  • Loading branch information
gregorypappas committed Jul 3, 2024
1 parent 6e92e04 commit e5fe1be
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 40 deletions.
70 changes: 48 additions & 22 deletions dom/base/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13881,8 +13881,9 @@ already_AddRefed<TouchList> Document::CreateTouchList(
return retval.forget();
}

// https://drafts.csswg.org/cssom-view/Overview#dom-document-caretpositionfrompoint
already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
float aX, float aY) {
float aX, float aY, const CaretPositionFromPointOptions& aOptions) {
using FrameForPointOption = nsLayoutUtils::FrameForPointOption;

nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
Expand Down Expand Up @@ -13922,34 +13923,59 @@ already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
nsIFrame::ContentOffsets offsets =
ptFrame->GetContentOffsetsFromPoint(adjustedPoint);

nsCOMPtr<nsIContent> node = offsets.content;
nsCOMPtr<nsINode> node = offsets.content;
uint32_t offset = offsets.offset;
nsCOMPtr<nsIContent> anonNode = node;
nsCOMPtr<nsINode> anonNode = node;
bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
if (nodeIsAnonymous) {
node = ptFrame->GetContent();
nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
if (textFrame) {
// If the anonymous content node has a child, then we need to make sure
// that we get the appropriate child, as otherwise the offset may not be
// correct when we construct a range for it.
nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
if (firstChild) {
anonNode = firstChild;
}
nsINode* nonChrome =
node->AsContent()->FindFirstNonChromeOnlyAccessContent();
HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonChrome);
nsITextControlFrame* textFrame =
do_QueryFrame(nonChrome->AsContent()->GetPrimaryFrame());

if (textArea) {
offset =
nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
}
if (!textFrame) {
return nullptr;
}

node = nonanon;
} else {
node = nullptr;
offset = 0;
// If the anonymous content node has a child, then we need to make sure
// that we get the appropriate child, as otherwise the offset may not be
// correct when we construct a range for it.
nsCOMPtr<nsINode> firstChild = anonNode->GetFirstChild();
if (firstChild) {
anonNode = firstChild;
}

if (textArea) {
offset = nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
}

node = nonChrome;
}

bool offsetAndNodeNeedsAdjustment = false;

if (StaticPrefs::
dom_shadowdom_new_caretPositionFromPoint_behavior_enabled()) {
while (node->IsInShadowTree() &&
!aOptions.mShadowRoots.Contains(node->GetContainingShadow())) {
node = node->GetContainingShadowHost();
offsetAndNodeNeedsAdjustment = true;
}
}

if (offsetAndNodeNeedsAdjustment) {
const Maybe<uint32_t> maybeIndex = node->ComputeIndexInParentContent();
if (MOZ_UNLIKELY(maybeIndex.isNothing())) {
// Unlikely to happen, but still return nullptr to avoid leaking
// information about the shadow tree.
return nullptr;
}
// 5.3.1: Set startOffset to index of startNode’s root's host.
offset = maybeIndex.value();
// 5.3.2: Set startNode to startNode’s root's host's parent.
node = node->GetParentNode();
}

RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
Expand Down
4 changes: 2 additions & 2 deletions dom/base/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -3451,8 +3451,8 @@ class Document : public nsINode,
* @param aY Vertical point at which to determine the caret position, in
* page coordinates.
*/
already_AddRefed<nsDOMCaretPosition> CaretPositionFromPoint(float aX,
float aY);
already_AddRefed<nsDOMCaretPosition> CaretPositionFromPoint(
float aX, float aY, const CaretPositionFromPointOptions& aOptions);

Element* GetScrollingElement();
// A way to check whether a given element is what would get returned from
Expand Down
7 changes: 6 additions & 1 deletion dom/webidl/Document.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,14 @@ partial interface Document {
undefined enableStyleSheetsForSet (DOMString? name);
};

dictionary CaretPositionFromPointOptions {
[Pref="dom.shadowdom.new_caretPositionFromPoint_behavior.enabled"]
sequence<ShadowRoot> shadowRoots = [];
};

// https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface
partial interface Document {
CaretPosition? caretPositionFromPoint (float x, float y);
CaretPosition? caretPositionFromPoint(float x, float y, optional CaretPositionFromPointOptions options = {});

readonly attribute Element? scrollingElement;
};
Expand Down
2 changes: 2 additions & 0 deletions layout/base/tests/mochitest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ support-files = ["bug1448730.html"]
["test_caret_browsing_around_form_controls.html"]
skip-if = ["os == 'android'"]

["test_caretPositionFromPoint_insertAnonymousContent.html"]

["test_dynamic_toolbar_max_height.html"]
support-files = ["file_dynamic_toolbar_max_height.html"]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test for caretPositionFromPoint with anonymous content</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script>
info("Inserting anonymous content into the document frame");
let chromeDoc = SpecialPowers.wrap(document);
let anonymousContent = chromeDoc.insertAnonymousContent();
let div = document.createElement("div");
div.style.position = "fixed";
div.style.top = "0";
div.style.left = "0";
div.style.width = "100%";
div.style.height = "100%";
div.style.pointerEvents = "auto";
div.style.backgroundColor = "red";
anonymousContent.root.appendChild(div);
let caretPos = document.caretPositionFromPoint(
innerWidth / 2,
innerHeight / 2
);
is(caretPos, null, "caretPos should be null");
info("Removing the anonymous content");
chromeDoc.removeAnonymousContent(anonymousContent);
</script>
</body>
</html>
11 changes: 11 additions & 0 deletions modules/libpref/init/StaticPrefList.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4657,6 +4657,17 @@
value: @IS_NIGHTLY_BUILD@
mirror: always

# When this pref is enabled:
# - Shadow DOM is not pierced by default anymore
# - The method accepts optional CaretPositionFromPointOptions to allow piercing
# certain ShadowRoots
#
# https://drafts.csswg.org/cssom-view/#dom-document-caretpositionfrompoint
- name: dom.shadowdom.new_caretPositionFromPoint_behavior.enabled
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always

# NOTE: This preference is used in unit tests. If it is removed or its default
# value changes, please update test_sharedMap_static_prefs.js accordingly.
- name: dom.webcomponents.shadowdom.report_usage
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
prefs: [dom.shadowdom.new_caretPositionFromPoint_behavior.enabled:true]
[Document-caretPositionFromPoint.tentative.html]
[document.caretPositionFromPoint() throws when called without the correct parameters]
expected: FAIL

[document.caretPositionFromPoint() should return a CaretPosition at the specified location pointing to a textarea element which is the offsetNode.]
expected: FAIL

[document.caretPositionFromPoint() should return a CaretPosition at the specified location pointing to the input element's shadow host's parent when the shadow tree is not specified as an argument.]
expected: FAIL

[document.caretPositionFromPoint() should return a CaretPosition at the specified location pointing to the shadow host's parent when the shadow tree is not specified as an argument]
expected: FAIL
[document.caretPositionFromPoint() should return a CaretPosition at the specified location pointing to the outer shadow host's parent when the point is in an inner shadow tree and no shadow tree is specified as an argument]
expected: FAIL

[document.caretPositionFromPoint() should return a CaretPosition at the specified location pointing to the outer shadow tree when the point is in an inner shadow tree and the outer shadow tree is specified as an argument]
expected: FAIL

0 comments on commit e5fe1be

Please sign in to comment.