Bug 85686 - Mimic Chromium's getSelection().toString() behaviour r=masayuki,dom-core,webidl,smaug
Basically when getSelection().toString() is called, Chromium may return an serialization on a different content than Firefox. This patch tries to address this webcompat issue by mimicing the same behaviour. We should still address the spec issues, but this seems to be an acceptable compromise. Differential Revision: https://phabricator.services.mozilla.com/D239657
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
#include "mozilla/dom/FragmentOrElement.h"
|
||||
#include "mozilla/dom/AncestorIterator.h"
|
||||
#include "DOMIntersectionObserver.h"
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
#include "mozilla/EffectSet.h"
|
||||
@@ -1034,6 +1035,28 @@ Element* nsIContent::GetAutofocusDelegate(IsFocusableFlags aFlags) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool nsIContent::CanStartSelectionAsWebCompatHack() const {
|
||||
if (!StaticPrefs::dom_selection_mimic_chrome_tostring_enabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const nsIContent* content = this; content;
|
||||
content = content->GetFlattenedTreeParent()) {
|
||||
if (content->IsEditable()) {
|
||||
return true;
|
||||
}
|
||||
nsIFrame* frame = content->GetPrimaryFrame();
|
||||
if (!frame) {
|
||||
return true;
|
||||
}
|
||||
if (!frame->IsSelectable(nullptr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Element* nsIContent::GetFocusDelegate(IsFocusableFlags aFlags) const {
|
||||
const nsIContent* whereToLook = this;
|
||||
if (ShadowRoot* root = GetShadowRoot()) {
|
||||
|
||||
@@ -509,7 +509,8 @@ void printRange(nsRange* aDomRange) {
|
||||
}
|
||||
#endif /* PRINT_RANGE */
|
||||
|
||||
void Selection::Stringify(nsAString& aResult, FlushFrames aFlushFrames) {
|
||||
void Selection::Stringify(nsAString& aResult, CallerType aCallerType,
|
||||
FlushFrames aFlushFrames) {
|
||||
if (aFlushFrames == FlushFrames::Yes) {
|
||||
// We need FlushType::Frames here to make sure frames have been created for
|
||||
// the selected content. Use mFrameSelection->GetPresShell() which returns
|
||||
@@ -524,8 +525,17 @@ void Selection::Stringify(nsAString& aResult, FlushFrames aFlushFrames) {
|
||||
}
|
||||
|
||||
IgnoredErrorResult rv;
|
||||
ToStringWithFormat(u"text/plain"_ns, nsIDocumentEncoder::SkipInvisibleContent,
|
||||
0, aResult, rv);
|
||||
uint32_t flags = nsIDocumentEncoder::SkipInvisibleContent;
|
||||
if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() &&
|
||||
Type() == SelectionType::eNormal &&
|
||||
aCallerType == CallerType::NonSystem) {
|
||||
if (mFrameSelection && !mFrameSelection->GetLimiter()) {
|
||||
// NonSystem and non-independent selection
|
||||
flags |= nsIDocumentEncoder::MimicChromeToStringBehaviour;
|
||||
}
|
||||
}
|
||||
|
||||
ToStringWithFormat(u"text/plain"_ns, flags, 0, aResult, rv);
|
||||
if (rv.Failed()) {
|
||||
aResult.Truncate();
|
||||
}
|
||||
@@ -559,7 +569,17 @@ void Selection::ToStringWithFormat(const nsAString& aFormatType,
|
||||
return;
|
||||
}
|
||||
|
||||
encoder->SetSelection(this);
|
||||
Selection* selectionToEncode = this;
|
||||
|
||||
if (aFlags & nsIDocumentEncoder::MimicChromeToStringBehaviour) {
|
||||
if (const nsFrameSelection* sel =
|
||||
presShell->GetLastSelectionForToString()) {
|
||||
MOZ_ASSERT(StaticPrefs::dom_selection_mimic_chrome_tostring_enabled());
|
||||
selectionToEncode = &sel->NormalSelection();
|
||||
}
|
||||
}
|
||||
|
||||
encoder->SetSelection(selectionToEncode);
|
||||
if (aWrapCol != 0) encoder->SetWrapColumn(aWrapCol);
|
||||
|
||||
rv = encoder->EncodeToString(aReturn);
|
||||
@@ -2446,6 +2466,12 @@ void Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv) {
|
||||
mCalledByJS = true;
|
||||
RefPtr<Document> document(GetDocument());
|
||||
AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document, aRv);
|
||||
if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() &&
|
||||
!aRv.Failed()) {
|
||||
if (auto* presShell = GetPresShell()) {
|
||||
presShell->UpdateLastSelectionForToString(mFrameSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
|
||||
@@ -3353,6 +3379,12 @@ void Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv) {
|
||||
AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
|
||||
mCalledByJS = true;
|
||||
SelectAllChildren(aNode, aRv);
|
||||
if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() &&
|
||||
!aRv.Failed()) {
|
||||
if (auto* presShell = GetPresShell()) {
|
||||
presShell->UpdateLastSelectionForToString(mFrameSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) {
|
||||
@@ -4122,6 +4154,12 @@ void Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
|
||||
AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
|
||||
mCalledByJS = true;
|
||||
SetBaseAndExtent(aAnchorNode, aAnchorOffset, aFocusNode, aFocusOffset, aRv);
|
||||
if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled() &&
|
||||
!aRv.Failed()) {
|
||||
if (auto* presShell = GetPresShell()) {
|
||||
presShell->UpdateLastSelectionForToString(mFrameSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
|
||||
|
||||
@@ -514,7 +514,9 @@ class Selection final : public nsSupportsWeakReference,
|
||||
*/
|
||||
enum class FlushFrames { No, Yes };
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
void Stringify(nsAString& aResult, FlushFrames = FlushFrames::Yes);
|
||||
void Stringify(nsAString& aResult,
|
||||
CallerType aCallerType = CallerType::System,
|
||||
FlushFrames = FlushFrames::Yes);
|
||||
|
||||
/**
|
||||
* Indicates whether the node is part of the selection. If partlyContained
|
||||
|
||||
@@ -357,6 +357,22 @@ class nsIContent : public nsINode {
|
||||
*/
|
||||
inline nsIContent* GetFlattenedTreeParent() const;
|
||||
|
||||
// This method is used to provide a similar CanStartSelection behaviour in
|
||||
// Chromium, see the link for exact Chromium's behaviour.
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/dom/node.cc;l=1909;drc=58fb75d86a0ad2642beec2d6c16b1e6c008e33cd;bpv=1;bpt=1
|
||||
//
|
||||
// Basically, Chromium has this method to decide if the selection should be
|
||||
// changed or remain at the current element when an element is focused. This
|
||||
// creates a webcompat issue for when window.getSelection().toString()
|
||||
// is called, web authors expect Firefox to serialize the old content, but
|
||||
// Firefox decides to serialize a different content.
|
||||
//
|
||||
// This method, along with PresShell::mLastSelectionForToString is used to
|
||||
// address this webcompat issue.
|
||||
//
|
||||
// THIS METHOD SHOULD BE USED WITH EXTRA CAUTIOUS.
|
||||
bool CanStartSelectionAsWebCompatHack() const;
|
||||
|
||||
protected:
|
||||
// Handles getting inserted or removed directly under a <slot> element.
|
||||
// This is meant to only be called from the two functions below.
|
||||
|
||||
@@ -764,7 +764,13 @@ TextInputSelectionController::ScrollCharacter(bool aRight) {
|
||||
void TextInputSelectionController::SelectionWillTakeFocus() {
|
||||
if (mFrameSelection) {
|
||||
if (PresShell* shell = mFrameSelection->GetPresShell()) {
|
||||
shell->FrameSelectionWillTakeFocus(*mFrameSelection);
|
||||
// text input selection always considers to move the
|
||||
// selection.
|
||||
shell->FrameSelectionWillTakeFocus(
|
||||
*mFrameSelection,
|
||||
StaticPrefs::dom_selection_mimic_chrome_tostring_enabled()
|
||||
? PresShell::CanMoveLastSelectionForToString::Yes
|
||||
: PresShell::CanMoveLastSelectionForToString::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,12 @@ interface nsIDocumentEncoder : nsISupports
|
||||
const unsigned long RequiresReinitAfterOutput = (1 << 28);
|
||||
|
||||
const unsigned long AllowCrossShadowBoundary = (1 << 29);
|
||||
|
||||
/**
|
||||
* Whether window.getSelection().toString() should mimic Chrome's
|
||||
* behaviour. See nsIContent::CanStartSelection for more details.
|
||||
*/
|
||||
const unsigned long MimicChromeToStringBehaviour = (1 << 30);
|
||||
/**
|
||||
* Initialize with a pointer to the document and the mime type.
|
||||
* Resets wrap column to 72 and resets node fixup.
|
||||
|
||||
@@ -84,6 +84,7 @@ interface Selection {
|
||||
[Throws]
|
||||
boolean containsNode(Node node,
|
||||
optional boolean allowPartialContainment = false);
|
||||
[NeedsCallerType]
|
||||
stringifier DOMString ();
|
||||
};
|
||||
|
||||
|
||||
@@ -828,7 +828,8 @@ nsAutoString AccessibleCaretManager::StringifiedSelection() const {
|
||||
nsAutoString str;
|
||||
RefPtr<Selection> selection = GetSelection();
|
||||
if (selection) {
|
||||
selection->Stringify(str, mLayoutFlusher.mAllowFlushing
|
||||
selection->Stringify(str, CallerType::System,
|
||||
mLayoutFlusher.mAllowFlushing
|
||||
? Selection::FlushFrames::Yes
|
||||
: Selection::FlushFrames::No);
|
||||
}
|
||||
|
||||
@@ -773,6 +773,7 @@ bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) {
|
||||
PresShell::PresShell(Document* aDocument)
|
||||
: mDocument(aDocument),
|
||||
mViewManager(nullptr),
|
||||
mLastSelectionForToString(nullptr),
|
||||
mAutoWeakFrames(nullptr),
|
||||
#ifdef ACCESSIBILITY
|
||||
mDocAccessible(nullptr),
|
||||
@@ -1539,7 +1540,8 @@ bool PresShell::FixUpFocus() {
|
||||
|
||||
void PresShell::SelectionWillTakeFocus() {
|
||||
if (mSelection) {
|
||||
FrameSelectionWillTakeFocus(*mSelection);
|
||||
FrameSelectionWillTakeFocus(*mSelection,
|
||||
CanMoveLastSelectionForToString::No);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1584,11 +1586,20 @@ void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) {
|
||||
}
|
||||
|
||||
if (mSelection) {
|
||||
FrameSelectionWillTakeFocus(*mSelection);
|
||||
FrameSelectionWillTakeFocus(*mSelection,
|
||||
CanMoveLastSelectionForToString::No);
|
||||
}
|
||||
}
|
||||
|
||||
void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) {
|
||||
void PresShell::FrameSelectionWillTakeFocus(
|
||||
nsFrameSelection& aFrameSelection,
|
||||
CanMoveLastSelectionForToString aCanMoveLastSelectionForToString) {
|
||||
if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled()) {
|
||||
if (aCanMoveLastSelectionForToString ==
|
||||
CanMoveLastSelectionForToString::Yes) {
|
||||
UpdateLastSelectionForToString(&aFrameSelection);
|
||||
}
|
||||
}
|
||||
if (mFocusedFrameSelection == &aFrameSelection) {
|
||||
#ifdef XP_MACOSX
|
||||
// FIXME: Mac needs to update the global selection cache, even if the
|
||||
@@ -1616,6 +1627,14 @@ void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) {
|
||||
}
|
||||
}
|
||||
|
||||
void PresShell::UpdateLastSelectionForToString(
|
||||
const nsFrameSelection* aFrameSelection) {
|
||||
MOZ_ASSERT(StaticPrefs::dom_selection_mimic_chrome_tostring_enabled());
|
||||
if (mLastSelectionForToString != aFrameSelection) {
|
||||
mLastSelectionForToString = aFrameSelection;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresShell::SetDisplaySelection(int16_t aToggle) {
|
||||
mSelection->SetDisplaySelection(aToggle);
|
||||
|
||||
@@ -574,12 +574,17 @@ class PresShell final : public nsStubDocumentObserver,
|
||||
|
||||
void ClearFrameRefs(nsIFrame* aFrame);
|
||||
|
||||
enum class CanMoveLastSelectionForToString { No, Yes };
|
||||
// Clears the selection of the older focused frame selection if any.
|
||||
void FrameSelectionWillTakeFocus(nsFrameSelection&);
|
||||
void FrameSelectionWillTakeFocus(nsFrameSelection&,
|
||||
CanMoveLastSelectionForToString);
|
||||
|
||||
// Clears and repaint mFocusedFrameSelection if it matches the argument.
|
||||
void FrameSelectionWillLoseFocus(nsFrameSelection&);
|
||||
|
||||
// Update mLastSelectionForToString to the given frame selection.
|
||||
void UpdateLastSelectionForToString(const nsFrameSelection*);
|
||||
|
||||
/**
|
||||
* Get a reference rendering context. This is a context that should not
|
||||
* be rendered to, but is suitable for measuring text and performing
|
||||
@@ -652,6 +657,10 @@ class PresShell final : public nsStubDocumentObserver,
|
||||
*/
|
||||
nsFrameSelection* GetLastFocusedFrameSelection();
|
||||
|
||||
const nsFrameSelection* GetLastSelectionForToString() const {
|
||||
return mLastSelectionForToString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to dispatch events via the presshell
|
||||
* @note The caller must have a strong reference to the PresShell.
|
||||
@@ -2999,6 +3008,11 @@ class PresShell final : public nsStubDocumentObserver,
|
||||
// hide if we focus another selection. May or may not be the same as
|
||||
// `mSelection`.
|
||||
RefPtr<nsFrameSelection> mFocusedFrameSelection;
|
||||
|
||||
// This the frame selection that will be used when getSelection().toString()
|
||||
// is called. See nsIContent::CanStartSelection for its reasoning.
|
||||
RefPtr<const nsFrameSelection> mLastSelectionForToString;
|
||||
|
||||
RefPtr<nsCaret> mCaret;
|
||||
RefPtr<nsCaret> mOriginalCaret;
|
||||
RefPtr<AccessibleCaretEventHub> mAccessibleCaretEventHub;
|
||||
|
||||
@@ -1408,7 +1408,10 @@ nsresult nsFrameSelection::TakeFocus(nsIContent& aNewFocus,
|
||||
__FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset,
|
||||
static_cast<int>(aHint), static_cast<int>(aFocusMode)));
|
||||
|
||||
mPresShell->FrameSelectionWillTakeFocus(*this);
|
||||
mPresShell->FrameSelectionWillTakeFocus(
|
||||
*this, aNewFocus.CanStartSelectionAsWebCompatHack()
|
||||
? PresShell::CanMoveLastSelectionForToString::Yes
|
||||
: PresShell::CanMoveLastSelectionForToString::No);
|
||||
|
||||
// Clear all table selection data
|
||||
mTableSelection.mMode = TableSelectionMode::None;
|
||||
@@ -3115,8 +3118,17 @@ void nsFrameSelection::DisconnectFromPresShell() {
|
||||
MOZ_ASSERT(mDomSelections[i]);
|
||||
mDomSelections[i]->Clear(nullptr);
|
||||
}
|
||||
|
||||
if (auto* presshell = mPresShell) {
|
||||
if (const nsFrameSelection* sel =
|
||||
presshell->GetLastSelectionForToString()) {
|
||||
if (sel == this) {
|
||||
presshell->UpdateLastSelectionForToString(nullptr);
|
||||
}
|
||||
}
|
||||
mPresShell = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
/**
|
||||
|
||||
@@ -166,7 +166,11 @@ function test()
|
||||
// iframe/input/custom-element contents must not be included on the parent
|
||||
// selection.
|
||||
checkCharacter(sel, "f", false, "iframe (checking on parent)");
|
||||
checkCharacter(sel, "i", false, "input (checking on parent)");
|
||||
checkCharacter(sel, "i",
|
||||
SpecialPowers.getBoolPref("dom.selection.mimic_chrome_tostring.enabled")
|
||||
? aInputShouldBeSelected
|
||||
: false,
|
||||
"input (checking on parent)");
|
||||
checkCharacter(sel, "x", false, "Custom element contents (checking on parent)");
|
||||
|
||||
var selInIFrame = iframe.contentWindow.getSelection().toString();
|
||||
|
||||
@@ -87,7 +87,11 @@ function test()
|
||||
|
||||
// input contents must not be included on the parent
|
||||
// selection.
|
||||
checkCharacter(sel, "i", false, "input (checking on parent)");
|
||||
checkCharacter(sel, "i",
|
||||
SpecialPowers.getBoolPref("dom.selection.mimic_chrome_tostring.enabled")
|
||||
? aInputShouldBeSelected
|
||||
: false
|
||||
, "input (checking on parent)");
|
||||
|
||||
var selInput = getSelectionForEditor(input).toString();
|
||||
checkCharacter(selInput, "i", aInputShouldBeSelected, "input");
|
||||
|
||||
@@ -5231,6 +5231,12 @@
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
|
||||
# Mimic Chrome's window.getSelection().toString() behaviour
|
||||
- name: dom.selection.mimic_chrome_tostring.enabled
|
||||
type: bool
|
||||
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
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
[email-set-value.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[setValue(sanitizedValue) is reflected in visible text field content]
|
||||
expected: FAIL
|
||||
prefs: [dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
[textarea-insertfrompaste-type-inputevent-data-withnewline-atend.html]
|
||||
[Input event data for inputType insertFromPaste should be set]
|
||||
expected: FAIL
|
||||
prefs: [dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
[textarea-insertfrompaste-type-inputevent-data-withnewline-atstart.html]
|
||||
[Input event data for inputType insertFromPaste should be set]
|
||||
expected: FAIL
|
||||
prefs: [dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
[textarea-insertfrompaste-type-inputevent-data.html]
|
||||
[Input event data for inputType insertFromPaste should be set]
|
||||
expected: FAIL
|
||||
prefs: [dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
[select.htm]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[HTML5 Selection: Call select() on a text field]
|
||||
expected: FAIL
|
||||
prefs: [dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
[selectionStartEnd.htm]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[HTML5 Selection: Set selectionStart and selectionEnd on a text field]
|
||||
expected: FAIL
|
||||
prefs: [dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
[setSelectionRange.htm]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[HTML5 Selection: Call setSelectionRange() on a text field]
|
||||
expected: FAIL
|
||||
prefs: [dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[stringifier_editable_element.tentative.html]
|
||||
prefs:[dom.selection.mimic_chrome_tostring.enabled:true]
|
||||
@@ -26,8 +26,8 @@
|
||||
await utils.sendCopyShortcutKey();
|
||||
await utils.sendPasteShortcutKey();
|
||||
// Event data should now be set with the first line of the text.
|
||||
assert_equals(selectedData, "Copying and pasting first line including interchange newline\n");
|
||||
assert_equals(eventData, selectedData);
|
||||
assert_equals(selectedData.replace(/\r\n/g, "\n"), "Copying and pasting first line including interchange newline\n");
|
||||
assert_equals(eventData, selectedData.replace(/\r\n/g, "\n"));
|
||||
}, "Input event data for inputType insertFromPaste should be set");
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -27,8 +27,8 @@ insertFromPaste</textarea>
|
||||
await utils.sendCopyShortcutKey();
|
||||
await utils.sendPasteShortcutKey();
|
||||
// Event data should now be set with the second line of the text
|
||||
assert_equals(selectedData, "\nat the start should set the event.data with the selected part for inputType");
|
||||
assert_equals(eventData, selectedData);
|
||||
assert_equals(selectedData.replace(/\r\n/g, "\n"), "\nat the start should set the event.data with the selected part for inputType");
|
||||
assert_equals(eventData, selectedData.replace(/\r\n/g, "\n"));
|
||||
}, "Input event data for inputType insertFromPaste should be set");
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
<!DOCTYPE HTML>
|
||||
<meta charset=utf-8>
|
||||
<title>Selection: stringifier for editable elements</title>
|
||||
<!--
|
||||
There are two open issues about how this should behave
|
||||
https://github.com/w3c/selection-api/issues/83
|
||||
https://github.com/w3c/selection-api/issues/7
|
||||
-->
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-vendor.js"></script>
|
||||
<script src="/resources/testdriver-actions.js"></script>
|
||||
<body>
|
||||
<input id="dummyInput"></input>
|
||||
|
||||
<input id="textInput" value="This is a text">
|
||||
<textarea id="textArea" rows="5" cols="40">
|
||||
|
||||
Line one
|
||||
Line two
|
||||
|
||||
</textarea>
|
||||
|
||||
<button id="button">Button</button>
|
||||
<a id="anchor">Anchor</a>
|
||||
<span id="text">Text</span>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
function reset() {
|
||||
window.getSelection().empty();
|
||||
dummyInput.focus();
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
test(() => {
|
||||
reset();
|
||||
textInput.select();
|
||||
|
||||
assert_equals(document.activeElement, textInput);
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "This is a text");
|
||||
}, "select the entire input should result all the content");
|
||||
|
||||
test(() => {
|
||||
reset();
|
||||
textInput.select();
|
||||
dummyInput.focus();
|
||||
|
||||
assert_equals(document.activeElement, dummyInput);
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "");
|
||||
}, "toString() should return empty when the focus is not on the editable content");
|
||||
|
||||
test(() => {
|
||||
reset();
|
||||
|
||||
textInput.selectionStart = 3;
|
||||
textInput.selectionEnd = 7;
|
||||
|
||||
assert_equals(document.activeElement, dummyInput);
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "");
|
||||
|
||||
textInput.focus();
|
||||
assert_equals(document.activeElement, textInput);
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "s is");
|
||||
}, "toString() works with selectionStart and selectionEnd for input");
|
||||
|
||||
test(() => {
|
||||
reset();
|
||||
|
||||
textArea.select();
|
||||
|
||||
assert_equals(document.activeElement, textArea);
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "\n Line one\n Line two\n\n ");
|
||||
}, "select the entire textarea should result all the content");
|
||||
|
||||
test(() => {
|
||||
reset();
|
||||
|
||||
textArea.selectionStart = 3;
|
||||
textArea.selectionEnd = 12;
|
||||
|
||||
assert_equals(document.activeElement, dummyInput);
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "");
|
||||
|
||||
textArea.focus();
|
||||
assert_equals(document.activeElement, textArea);
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "Line one\n");
|
||||
}, "toString() works with selectionStart and selectionEnd for textarea");
|
||||
|
||||
test(() => {
|
||||
reset();
|
||||
textInput.select();
|
||||
|
||||
button.focus();
|
||||
|
||||
assert_equals(document.activeElement, button);
|
||||
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "This is a text");
|
||||
}, "toString() works even if a click just occured on a button");
|
||||
|
||||
promise_test((t) => {
|
||||
reset();
|
||||
textInput.select();
|
||||
return new Promise(r => {
|
||||
anchor.addEventListener("click", function() {
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "This is a text");
|
||||
r();
|
||||
}, {once: true});
|
||||
|
||||
anchor.click();
|
||||
});
|
||||
}, "toString() works for programatically calling .click() on anchor (without href)");
|
||||
|
||||
promise_test((t) => {
|
||||
reset();
|
||||
textInput.select();
|
||||
return new Promise(r => {
|
||||
anchor.addEventListener("click", function() {
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "");
|
||||
r();
|
||||
}, {once : true});
|
||||
|
||||
test_driver.click(anchor);
|
||||
});
|
||||
}, "toString() doesn't work for actual clicking the anchor (without href)");
|
||||
|
||||
promise_test((t) => {
|
||||
reset();
|
||||
textInput.select();
|
||||
anchor.href = "#";
|
||||
return new Promise(r => {
|
||||
anchor.addEventListener("click", function() {
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "This is a text");
|
||||
r();
|
||||
}, {once: true});
|
||||
|
||||
anchor.click(); // anchor has href now
|
||||
});
|
||||
}, "toString() works for programatically calling .click() on anchor (with href)");
|
||||
|
||||
promise_test((t) => {
|
||||
reset();
|
||||
textInput.select();
|
||||
return new Promise(r => {
|
||||
anchor.addEventListener("click", function() {
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "This is a text");
|
||||
r();
|
||||
}, {once : true});
|
||||
|
||||
test_driver.click(anchor); // anchor has href now
|
||||
});
|
||||
}, "toString() also works for actual clicking the anchor (with href)");
|
||||
|
||||
promise_test((t) => {
|
||||
reset();
|
||||
textInput.select();
|
||||
|
||||
return new Promise(async r => {
|
||||
anchor.addEventListener("click", function() {
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "");
|
||||
r();
|
||||
}, {once : true});
|
||||
|
||||
await test_driver.click(text);
|
||||
test_driver.click(anchor);
|
||||
});
|
||||
}, "Click on a text prior to toString() moves the seleciton");
|
||||
|
||||
promise_test((t) => {
|
||||
reset();
|
||||
textInput.select();
|
||||
|
||||
text.style = "user-select: none";
|
||||
return new Promise(async r => {
|
||||
anchor.addEventListener("click", function() {
|
||||
assert_equals(window.getSelection().toString().replace(/\r\n/g, "\n"), "This is a text");
|
||||
r();
|
||||
}, {once : true});
|
||||
|
||||
await test_driver.click(text);
|
||||
test_driver.click(anchor);
|
||||
});
|
||||
}, "Click on a `user-select:none` text prior to toString() doesn't move the seleciton");
|
||||
};
|
||||
</script>
|
||||
</html>
|
||||
Reference in New Issue
Block a user