Bug 1632425 - Part 3: Triple click to select anchors r=masayuki

Differential Revision: https://phabricator.services.mozilla.com/D77683
This commit is contained in:
Kagami Sascha Rosylight 2020-06-03 23:47:55 +00:00
parent 117ae10a93
commit 45b185573f
5 changed files with 167 additions and 139 deletions

View File

@ -256,6 +256,121 @@ nsresult HTMLEditorEventListener::MouseUp(MouseEvent* aMouseEvent) {
return rv;
}
static bool IsAcceptableMouseEvent(const HTMLEditor& aHTMLEditor,
MouseEvent* aMouseEvent) {
// Contenteditable should disregard mousedowns outside it.
// IsAcceptableInputEvent() checks it for a mouse event.
WidgetMouseEvent* mousedownEvent =
aMouseEvent->WidgetEventPtr()->AsMouseEvent();
MOZ_ASSERT(mousedownEvent);
return aHTMLEditor.IsAcceptableInputEvent(mousedownEvent);
}
void HTMLEditorEventListener::MaybeDisplayResizers(HTMLEditor& aHTMLEditor,
Element& aElement,
MouseEvent& aMouseEvent) {
// if the target element is an image, we have to display resizers
int32_t clientX = aMouseEvent.ClientX();
int32_t clientY = aMouseEvent.ClientY();
DebugOnly<nsresult> rvIgnored =
aHTMLEditor.OnMouseDown(clientX, clientY, &aElement, &aMouseEvent);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"HTMLEditor::OnMouseDown() failed, but ignored");
}
nsresult HTMLEditorEventListener::HandlePrimaryMouseButtonDown(
HTMLEditor& aHTMLEditor, MouseEvent& aMouseEvent) {
RefPtr<EventTarget> eventTarget = aMouseEvent.GetExplicitOriginalTarget();
if (NS_WARN_IF(!eventTarget)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIContent> eventTargetContent = do_QueryInterface(eventTarget);
if (!eventTargetContent) {
return NS_OK;
}
RefPtr<Element> toSelect;
bool isElement = eventTargetContent->IsElement();
int32_t clickCount = aMouseEvent.Detail();
switch (clickCount) {
case 1:
if (isElement) {
RefPtr<Element> element = eventTargetContent->AsElement();
MaybeDisplayResizers(aHTMLEditor, *element, aMouseEvent);
}
break;
case 2:
if (isElement) {
toSelect = eventTargetContent->AsElement();
}
break;
case 3:
// Triple click selects `<a href>` instead of its container paragraph
// as users may want to modify the anchor element.
if (!isElement) {
toSelect = aHTMLEditor.GetInclusiveAncestorByTagName(
*nsGkAtoms::href, *eventTargetContent);
}
break;
}
if (toSelect) {
DebugOnly<nsresult> rvIgnored = aHTMLEditor.SelectElement(toSelect);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"HTMLEditor::SelectElement() failed, but ignored");
aMouseEvent.PreventDefault();
}
return NS_OK;
}
nsresult HTMLEditorEventListener::HandleSecondaryMouseButtonDown(
HTMLEditor& aHTMLEditor, MouseEvent& aMouseEvent) {
RefPtr<Selection> selection = aHTMLEditor.GetSelection();
if (NS_WARN_IF(!selection)) {
return NS_OK;
}
int32_t offset = -1;
nsCOMPtr<nsIContent> parentContent =
aMouseEvent.GetRangeParentContentAndOffset(&offset);
if (NS_WARN_IF(!parentContent)) {
return NS_ERROR_FAILURE;
}
if (EditorUtils::IsPointInSelection(*selection, *parentContent, offset)) {
return NS_OK;
}
RefPtr<EventTarget> eventTarget = aMouseEvent.GetExplicitOriginalTarget();
if (NS_WARN_IF(!eventTarget)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<Element> eventTargetElement = do_QueryInterface(eventTarget);
// Select entire element clicked on if NOT within an existing selection
// and not the entire body, or table-related elements
if (HTMLEditUtils::IsImage(eventTargetElement)) {
DebugOnly<nsresult> rvIgnored =
aHTMLEditor.SelectElement(eventTargetElement);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"HTMLEditor::SelectElement() failed, but ignored");
} else {
DebugOnly<nsresult> rvIgnored = selection->Collapse(parentContent, offset);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Selection::Collapse() failed, but ignored");
}
// HACK !!! Context click places the caret but the context menu consumes
// the event; so we need to check resizing state ourselves
DebugOnly<nsresult> rvIgnored =
aHTMLEditor.CheckSelectionStateForAnonymousButtons();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"HTMLEditor::CheckSelectionStateForAnonymousButtons() "
"failed, but ignored");
return NS_OK;
}
nsresult HTMLEditorEventListener::MouseDown(MouseEvent* aMouseEvent) {
if (NS_WARN_IF(!aMouseEvent) || DetachedFromEditor()) {
return NS_OK;
@ -269,121 +384,23 @@ nsresult HTMLEditorEventListener::MouseDown(MouseEvent* aMouseEvent) {
return NS_OK;
}
WidgetMouseEvent* mousedownEvent =
aMouseEvent->WidgetEventPtr()->AsMouseEvent();
MOZ_ASSERT(mousedownEvent);
RefPtr<HTMLEditor> htmlEditor = mEditorBase->AsHTMLEditor();
MOZ_ASSERT(htmlEditor);
// Contenteditable should disregard mousedowns outside it.
// IsAcceptableInputEvent() checks it for a mouse event.
if (!htmlEditor->IsAcceptableInputEvent(mousedownEvent)) {
if (!IsAcceptableMouseEvent(*htmlEditor, aMouseEvent)) {
return EditorEventListener::MouseDown(aMouseEvent);
}
// Detect only "context menu" click
// XXX This should be easier to do!
// But eDOMEvents_contextmenu and eContextMenu is not exposed in any event
// interface :-(
int16_t buttonNumber = aMouseEvent->Button();
bool isContextClick = buttonNumber == 2;
int32_t clickCount = aMouseEvent->Detail();
RefPtr<EventTarget> originalEventTarget =
aMouseEvent->GetExplicitOriginalTarget();
if (NS_WARN_IF(!originalEventTarget)) {
return NS_ERROR_FAILURE;
if (aMouseEvent->Button() == MouseButton::eLeft) {
nsresult rv = HandlePrimaryMouseButtonDown(*htmlEditor, *aMouseEvent);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<Element> originalEventTargetElement =
do_QueryInterface(originalEventTarget);
if (isContextClick || (buttonNumber == 0 && clickCount == 2)) {
RefPtr<Selection> selection = htmlEditor->GetSelection();
if (NS_WARN_IF(!selection)) {
return NS_OK;
} else if (aMouseEvent->Button() == MouseButton::eRight) {
nsresult rv = HandleSecondaryMouseButtonDown(*htmlEditor, *aMouseEvent);
if (NS_FAILED(rv)) {
return rv;
}
// Get location of mouse within target node
int32_t offset = -1;
nsCOMPtr<nsIContent> parentContent =
aMouseEvent->GetRangeParentContentAndOffset(&offset);
if (NS_WARN_IF(!parentContent)) {
return NS_ERROR_FAILURE;
}
// Detect if mouse point is within current selection for context click
bool nodeIsInSelection =
EditorUtils::IsPointInSelection(*selection, *parentContent, offset);
nsCOMPtr<nsIContent> originalEventTargetContent =
originalEventTargetElement;
if (!originalEventTargetContent) {
originalEventTargetContent = do_QueryInterface(originalEventTarget);
}
if (originalEventTargetContent && !nodeIsInSelection) {
RefPtr<Element> element = originalEventTargetElement.get();
if (!originalEventTargetContent->IsElement()) {
if (isContextClick) {
// Set the selection to the point under the mouse cursor:
DebugOnly<nsresult> rvIgnored =
selection->Collapse(parentContent, offset);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Selection::Collapse() failed, but ignored");
} else {
// Get enclosing link if in text so we can select the link
Element* linkElement = htmlEditor->GetInclusiveAncestorByTagName(
*nsGkAtoms::href, *originalEventTargetContent);
if (linkElement) {
element = linkElement;
}
}
}
// Select entire element clicked on if NOT within an existing selection
// and not the entire body, or table-related elements
if (element) {
if (isContextClick &&
!HTMLEditUtils::IsImage(originalEventTargetContent)) {
DebugOnly<nsresult> rvIgnored =
selection->Collapse(parentContent, offset);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Selection::Collapse() failed, but ignored");
} else {
DebugOnly<nsresult> rvIgnored = htmlEditor->SelectElement(element);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"HTMLEditor::SelectElement() failed, but ignored");
}
if (DetachedFromEditor()) {
return NS_OK;
}
}
// XXX The variable name may become a lie, but the name is useful for
// warnings above.
originalEventTargetElement = element;
}
// HACK !!! Context click places the caret but the context menu consumes
// the event; so we need to check resizing state ourselves
DebugOnly<nsresult> rvIgnored =
htmlEditor->CheckSelectionStateForAnonymousButtons();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"HTMLEditor::CheckSelectionStateForAnonymousButtons() "
"failed, but ignored");
// Prevent bubbling if we changed selection or
// for all context clicks
if (originalEventTargetElement || isContextClick) {
aMouseEvent->PreventDefault();
return NS_OK;
}
} else if (!isContextClick && buttonNumber == 0 && clickCount == 1) {
// if the target element is an image, we have to display resizers
int32_t clientX = aMouseEvent->ClientX();
int32_t clientY = aMouseEvent->ClientY();
DebugOnly<nsresult> rvIgnored = htmlEditor->OnMouseDown(
clientX, clientY, originalEventTargetElement, aMouseEvent);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"HTMLEditor::OnMouseDown() failed, but ignored");
}
nsresult rv = EditorEventListener::MouseDown(aMouseEvent);

View File

@ -79,6 +79,14 @@ class HTMLEditorEventListener final : public EditorEventListener {
nsresult ListenToMouseMoveEventForResizersOrGrabber(bool aListen,
bool aForGrabber);
MOZ_CAN_RUN_SCRIPT void MaybeDisplayResizers(HTMLEditor& aHTMLEditor,
Element& aElement,
MouseEvent& aMouseEvent);
MOZ_CAN_RUN_SCRIPT nsresult HandlePrimaryMouseButtonDown(
HTMLEditor& aHTMLEditor, MouseEvent& aMouseEvent);
MOZ_CAN_RUN_SCRIPT nsresult HandleSecondaryMouseButtonDown(
HTMLEditor& aHTMLEditor, MouseEvent& aMouseEvent);
bool mListeningToMouseMoveEventForResizers;
bool mListeningToMouseMoveEventForGrabber;
bool mListeningToResizeEvent;

View File

@ -33,6 +33,7 @@ function resetSelection() {
function runTest() {
var rightClickDown = {type: "mousedown", button: 2},
rightClickUp = {type: "mouseup", button: 2},
rightClickEnd = {type: "contextmenu", button: 2},
singleClickDown = {type: "mousedown", button: 0},
singleClickUp = {type: "mouseup", button: 0};
var selection = window.getSelection();
@ -55,6 +56,7 @@ function runTest() {
resetSelection();
synthesizeMouseAtCenter(img, rightClickDown);
synthesizeMouseAtCenter(img, rightClickUp);
synthesizeMouseAtCenter(img, rightClickEnd);
imgselected = selection.anchorNode == img.parentNode &&
selection.anchorOffset === 1 &&
selection.rangeCount === 1;

View File

@ -47,24 +47,24 @@ SimpleTest.waitForFocus(async function() {
synthesizeMouseAtCenter(b, {clickCount: 1});
synthesizeMouseAtCenter(b, {clickCount: 2});
is(selection.getRangeAt(0).startContainer, b.previousSibling,
"#0-1 Double-clicking in <b> element should set start of selection to end of previous text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-1 Double-clicking in <b> element should set start of selection to end of previous text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).startOffset, b.previousSibling.length,
"#0-1 Double-clicking in <b> element should set start of selection to end of previous text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-1 Double-clicking in <b> element should set start of selection to end of previous text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endContainer, b.nextSibling,
"#0-1 Double-clicking in <b> element should set end of selection to start of next text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-1 Double-clicking in <b> element should set end of selection to start of next text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endOffset, eatSpaceToNextWord ? 1 : 0,
"#0-1 Double-clicking in <b> element should set end of selection to start of next text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-1 Double-clicking in <b> element should set end of selection to start of next text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("")),
null,
"#0-1 nsIHTMLEditor::getSelectedElement(\"\") should return null after double-clicking in <b> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-1 nsIHTMLEditor::getSelectedElement(\"\") should return null after double-clicking in <b> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
synthesizeMouseAtCenter(i, {clickCount: 1});
synthesizeMouseAtCenter(i, {clickCount: 2});
is(selection.getRangeAt(0).startContainer, i.previousSibling,
"#0-2 Double-clicking in <i> element should set start of selection to end of previous text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-2 Double-clicking in <i> element should set start of selection to end of previous text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).startOffset, i.previousSibling.length,
"#0-2 Double-clicking in <i> element should set start of selection to end of previous text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-2 Double-clicking in <i> element should set start of selection to end of previous text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
if (eatSpaceToNextWord) {
is(selection.getRangeAt(0).endContainer, i.nextSibling,
"#0-2 Double-clicking in <i> element should set end of selection to start of next text node (eat_space_to_next_word: true)");
@ -79,70 +79,71 @@ SimpleTest.waitForFocus(async function() {
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("")),
null,
"#0-2 nsIHTMLEditor::getSelectedElement(\"\") should return null after double-clicking in <b> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-2 nsIHTMLEditor::getSelectedElement(\"\") should return null after double-clicking in <b> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
// Both clicking and double-clicking on <img> element should "select" it.
synthesizeMouseAtCenter(img, {clickCount: 1});
is(selection.getRangeAt(0).startContainer, img.parentElement,
"#0-3 Clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-3 Clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).startOffset, 1,
"#0-3 Clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-3 Clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endContainer, img.parentElement,
"#0-3 Clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-3 Clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endOffset, 2,
"#0-3 Clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-3 Clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("")),
img,
"#0-3 nsIHTMLEditor::getSelectedElement(\"\") should return the <img> element after clicking in it (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-3 nsIHTMLEditor::getSelectedElement(\"\") should return the <img> element after clicking in it (eat_space_to_next_word: ${eatSpaceToNextWord})`);
synthesizeMouseAtCenter(img, {clickCount: 1});
synthesizeMouseAtCenter(img, {clickCount: 2});
is(selection.getRangeAt(0).startContainer, img.parentElement,
"#0-4 Double-clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-4 Double-clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).startOffset, 1,
"#0-4 Double-clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-4 Double-clicking in <img> element should set start of selection to the <img> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endContainer, img.parentElement,
"#0-4 Double-clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-4 Double-clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endOffset, 2,
"#0-4 Double-clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-4 Double-clicking in <img> element should set end of selection to start of next text node (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("")),
img,
"#0-4 nsIHTMLEditor::getSelectedElement(\"\") should return the <img> element after double-clicking in it (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-4 nsIHTMLEditor::getSelectedElement(\"\") should return the <img> element after double-clicking in it (eat_space_to_next_word: ${eatSpaceToNextWord})`);
// Puts caret into the <a href> element.
synthesizeMouseAtCenter(href, {clickCount: 1});
is(selection.getRangeAt(0).startContainer, href.firstChild,
"#0-5 Clicking in <a href> element should set start of selection to the text node in it (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-5 Clicking in <a href> element should set start of selection to the text node in it (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.isCollapsed, true,
"#0-5 Clicking in <a href> element should cause collapsing Selection (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-5 Clicking in <a href> element should cause collapsing Selection (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("")),
null,
"#0-5 nsIHTMLEditor::getSelectedElement(\"\") should return null after clicking in the <a href> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-5 nsIHTMLEditor::getSelectedElement(\"\") should return null after clicking in the <a href> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("href")),
href,
"#0-5 nsIHTMLEditor::getSelectedElement(\"href\") should return the <a href> element after clicking in it (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-5 nsIHTMLEditor::getSelectedElement(\"href\") should return the <a href> element after clicking in it (eat_space_to_next_word: ${eatSpaceToNextWord})`);
// Selects the <a href> element with a double-click.
// Selects the <a href> element with a triple-click.
synthesizeMouseAtCenter(href, {clickCount: 1});
synthesizeMouseAtCenter(href, {clickCount: 2});
synthesizeMouseAtCenter(href, {clickCount: 3});
is(selection.getRangeAt(0).startContainer, href.parentElement,
"#0-6 Double-clicking in <a href> element should set start of selection to the element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-6 Triple-clicking in <a href> element should set start of selection to the element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).startOffset, 1,
"#0-6 Double-clicking in <a href> element should set start of selection to the element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-6 Triple-clicking in <a href> element should set start of selection to the element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endContainer, href.parentElement,
"#0-6 Double-clicking in <a href> element should set end of selection to start of next <br> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-6 Triple-clicking in <a href> element should set end of selection to start of next <br> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(selection.getRangeAt(0).endOffset, 2,
"#0-6 Double-clicking in <a href> element should set end of selection to start of next <br> element (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-6 Triple-clicking in <a href> element should set end of selection to start of next <br> element (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("")),
href,
"#0-6 nsIHTMLEditor::getSelectedElement(\"\") should return the <a href> element after double-clicking in it (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-6 nsIHTMLEditor::getSelectedElement(\"\") should return the <a href> element after double-clicking in it (eat_space_to_next_word: ${eatSpaceToNextWord})`);
is(SpecialPowers.unwrap(getHTMLEditor().getSelectedElement("href")),
href,
"#0-6 nsIHTMLEditor::getSelectedElement(\"href\") should return the <a href> element after double-clicking in it (eat_space_to_next_word: {$eatSpaceToNextWord})");
`#0-6 nsIHTMLEditor::getSelectedElement(\"href\") should return the <a href> element after double-clicking in it (eat_space_to_next_word: ${eatSpaceToNextWord})`);
}
editor.innerHTML = "<p>p1<b>b1</b><i>i1</i></p>";

View File

@ -268,17 +268,17 @@ SimpleTest.waitForFocus(async () => {
}
const kTests = [
{ description: "Resiziers for <img>",
{ description: "Resizers for <img>",
innerHTML: "<img id=\"target\" src=\"green.png\">",
mayPreserveRatio: true,
isAbsolutePosition: false,
},
{ description: "Resiziers for <table>",
{ description: "Resizers for <table>",
innerHTML: "<table id=\"target\" border><tr><td>cell</td><td>cell</td></tr></table>",
mayPreserveRatio: false,
isAbsolutePosition: false,
},
{ description: "Resiziers for absolute positioned <div>",
{ description: "Resizers for absolute positioned <div>",
innerHTML: "<div id=\"target\" style=\"position: absolute; top: 50px; left: 50px;\">positioned</div>",
mayPreserveRatio: false,
isAbsolutePosition: true,