Backed out 4 changesets (bug 1898321, bug 1897942, bug 1897956) for causing multiple failures.
Backed out changeset ec3fcdcbfbd7 (bug 1897942) Backed out changeset 279025d15551 (bug 1898321) Backed out changeset a239855b02ea (bug 1897942) Backed out changeset aab98ffe0ee9 (bug 1897956)
This commit is contained in:
@@ -10736,12 +10736,11 @@ nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
|
|||||||
// `Document::ScrollToRef()` is (presumably) the second "async" call mentioned
|
// `Document::ScrollToRef()` is (presumably) the second "async" call mentioned
|
||||||
// in sec. 7.4.2.3.3 in the HTML spec, "Fragment navigations":
|
// in sec. 7.4.2.3.3 in the HTML spec, "Fragment navigations":
|
||||||
// https://html.spec.whatwg.org/#scroll-to-fragid:~:text=This%20algorithm%20will%20be%20called%20twice
|
// https://html.spec.whatwg.org/#scroll-to-fragid:~:text=This%20algorithm%20will%20be%20called%20twice
|
||||||
|
const bool hasScrolledToTextFragment =
|
||||||
const RefPtr fragmentDirective = GetDocument()->FragmentDirective();
|
presShell->HighlightAndGoToTextFragment(scroll);
|
||||||
const nsTArray<RefPtr<nsRange>> textDirectives =
|
if (hasScrolledToTextFragment) {
|
||||||
fragmentDirective->FindTextFragmentsInDocument();
|
return NS_OK;
|
||||||
const bool hasTextDirectives = !textDirectives.IsEmpty();
|
}
|
||||||
fragmentDirective->HighlightTextDirectives(textDirectives);
|
|
||||||
|
|
||||||
// If we have no new anchor, we do not want to scroll, unless there is a
|
// If we have no new anchor, we do not want to scroll, unless there is a
|
||||||
// current anchor and we are doing a history load. So return if we have no
|
// current anchor and we are doing a history load. So return if we have no
|
||||||
@@ -10754,12 +10753,12 @@ nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
|
|||||||
// Both the new and current URIs refer to the same page. We can now
|
// Both the new and current URIs refer to the same page. We can now
|
||||||
// browse to the hash stored in the new URI.
|
// browse to the hash stored in the new URI.
|
||||||
|
|
||||||
if (aNewHash.IsEmpty() && !hasTextDirectives) {
|
if (aNewHash.IsEmpty()) {
|
||||||
// 2. If fragment is the empty string, then return the special value top of
|
// 2. If fragment is the empty string, then return the special value top of
|
||||||
// the document.
|
// the document.
|
||||||
//
|
//
|
||||||
// Tell the shell it's at an anchor without scrolling.
|
// Tell the shell it's at an anchor without scrolling.
|
||||||
presShell->GoToAnchor(u""_ns, nullptr, false);
|
presShell->GoToAnchor(u""_ns, false);
|
||||||
|
|
||||||
if (scroll) {
|
if (scroll) {
|
||||||
// Scroll to the top of the page. Ignore the return value; failure to
|
// Scroll to the top of the page. Ignore the return value; failure to
|
||||||
@@ -10774,10 +10773,7 @@ nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
|
|||||||
// 3. Let potentialIndicatedElement be the result of finding a potential
|
// 3. Let potentialIndicatedElement be the result of finding a potential
|
||||||
// indicated element given document and fragment.
|
// indicated element given document and fragment.
|
||||||
NS_ConvertUTF8toUTF16 uStr(aNewHash);
|
NS_ConvertUTF8toUTF16 uStr(aNewHash);
|
||||||
RefPtr<nsRange> range =
|
auto rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto);
|
||||||
!textDirectives.IsEmpty() ? textDirectives[0] : nullptr;
|
|
||||||
auto rv =
|
|
||||||
presShell->GoToAnchor(uStr, range, scroll, ScrollFlags::ScrollSmoothAuto);
|
|
||||||
|
|
||||||
// 4. If potentialIndicatedElement is not null, then return
|
// 4. If potentialIndicatedElement is not null, then return
|
||||||
// potentialIndicatedElement.
|
// potentialIndicatedElement.
|
||||||
@@ -10798,7 +10794,7 @@ nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
|
|||||||
if (fragmentBytes.IsEmpty()) {
|
if (fragmentBytes.IsEmpty()) {
|
||||||
// When aNewHash contains "%00", the unescaped string may be empty, and
|
// When aNewHash contains "%00", the unescaped string may be empty, and
|
||||||
// GoToAnchor asserts if we ask it to scroll to an empty ref.
|
// GoToAnchor asserts if we ask it to scroll to an empty ref.
|
||||||
presShell->GoToAnchor(u""_ns, nullptr, false);
|
presShell->GoToAnchor(u""_ns, false);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10815,8 +10811,7 @@ nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
|
|||||||
// there is no such anchor in the document, which is actually a success
|
// there is no such anchor in the document, which is actually a success
|
||||||
// condition for us (we want to update the session history with the new URI no
|
// condition for us (we want to update the session history with the new URI no
|
||||||
// matter whether we actually scrolled somewhere).
|
// matter whether we actually scrolled somewhere).
|
||||||
presShell->GoToAnchor(decodedFragment, nullptr, scroll,
|
presShell->GoToAnchor(decodedFragment, scroll, ScrollFlags::ScrollSmoothAuto);
|
||||||
ScrollFlags::ScrollSmoothAuto);
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13089,49 +13089,31 @@ void Document::ScrollToRef() {
|
|||||||
if (!presShell) {
|
if (!presShell) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX(:jjaschke): Document policy integration should happen here
|
|
||||||
// as soon as https://bugzil.la/1860915 lands.
|
|
||||||
// XXX(:jjaschke): Same goes for User Activation and security aspects,
|
|
||||||
// tracked in https://bugzil.la/1888756.
|
|
||||||
|
|
||||||
// https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
|
|
||||||
// Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:
|
|
||||||
// 1. Let text directives be the document's pending text directives.
|
|
||||||
const RefPtr fragmentDirective = FragmentDirective();
|
|
||||||
const nsTArray<RefPtr<nsRange>> textDirectives =
|
|
||||||
fragmentDirective->FindTextFragmentsInDocument();
|
|
||||||
// 2. If ranges is non-empty, then:
|
|
||||||
// 2.1 Let firstRange be the first item of ranges
|
|
||||||
RefPtr<nsRange> firstRange =
|
|
||||||
!textDirectives.IsEmpty() ? textDirectives.ElementAt(0) : nullptr;
|
|
||||||
// 2.2 Visually indicate each range in ranges in an implementation-defined
|
|
||||||
// way. The indication must not be observable from author script. See § 3.7
|
|
||||||
// Indicating The Text Match.
|
|
||||||
fragmentDirective->HighlightTextDirectives(textDirectives);
|
|
||||||
|
|
||||||
// In a subsequent call to `ScrollToRef()` during page load, `textDirectives`
|
|
||||||
// would only contain text directives that were not found in the previous
|
|
||||||
// runs. If an earlier call during the same page load already found a text
|
|
||||||
// directive to scroll to, only highlighting of the text directives needs to
|
|
||||||
// be done.
|
|
||||||
// This is indicated by `mScrolledToRefAlready`.
|
|
||||||
if (mScrolledToRefAlready) {
|
if (mScrolledToRefAlready) {
|
||||||
presShell->ScrollToAnchor();
|
presShell->ScrollToAnchor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If text directives is non-null, then highlight the text directives and
|
||||||
|
// scroll to the last one.
|
||||||
|
// XXX(:jjaschke): Document policy integration should happen here
|
||||||
|
// as soon as https://bugzil.la/1860915 lands.
|
||||||
|
// XXX(:jjaschke): Same goes for User Activation and security aspects,
|
||||||
|
// tracked in https://bugzil.la/1888756.
|
||||||
|
const bool didScrollToTextFragment =
|
||||||
|
presShell->HighlightAndGoToTextFragment(true);
|
||||||
|
|
||||||
|
FragmentDirective()->ClearUninvokedDirectives();
|
||||||
|
|
||||||
// 2. If fragment is the empty string and no text directives have been
|
// 2. If fragment is the empty string and no text directives have been
|
||||||
// scrolled to, then return the special value top of the document.
|
// scrolled to, then return the special value top of the document.
|
||||||
if (textDirectives.IsEmpty() && mScrollToRef.IsEmpty()) {
|
if (didScrollToTextFragment || mScrollToRef.IsEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 3. Let potentialIndicatedElement be the result of finding a potential
|
// 3. Let potentialIndicatedElement be the result of finding a potential
|
||||||
// indicated element given document and fragment.
|
// indicated element given document and fragment.
|
||||||
NS_ConvertUTF8toUTF16 ref(mScrollToRef);
|
NS_ConvertUTF8toUTF16 ref(mScrollToRef);
|
||||||
// This also covers 2.3 of the Monkeypatch for text fragments mentioned above:
|
auto rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
|
||||||
// 2.3 Set firstRange as document's indicated part, return.
|
|
||||||
auto rv = presShell->GoToAnchor(ref, firstRange,
|
|
||||||
mChangeScrollPosWhenScrollingToRef);
|
|
||||||
|
|
||||||
// 4. If potentialIndicatedElement is not null, then return
|
// 4. If potentialIndicatedElement is not null, then return
|
||||||
// potentialIndicatedElement.
|
// potentialIndicatedElement.
|
||||||
@@ -13159,7 +13141,7 @@ void Document::ScrollToRef() {
|
|||||||
|
|
||||||
// 7. Set potentialIndicatedElement to the result of finding a potential
|
// 7. Set potentialIndicatedElement to the result of finding a potential
|
||||||
// indicated element given document and decodedFragment.
|
// indicated element given document and decodedFragment.
|
||||||
rv = presShell->GoToAnchor(decodedFragment, nullptr,
|
rv = presShell->GoToAnchor(decodedFragment,
|
||||||
mChangeScrollPosWhenScrollingToRef);
|
mChangeScrollPosWhenScrollingToRef);
|
||||||
if (NS_SUCCEEDED(rv)) {
|
if (NS_SUCCEEDED(rv)) {
|
||||||
mScrolledToRefAlready = true;
|
mScrolledToRefAlready = true;
|
||||||
|
|||||||
@@ -12,14 +12,11 @@
|
|||||||
#include "mozilla/dom/FragmentDirectiveBinding.h"
|
#include "mozilla/dom/FragmentDirectiveBinding.h"
|
||||||
#include "mozilla/dom/FragmentOrElement.h"
|
#include "mozilla/dom/FragmentOrElement.h"
|
||||||
#include "mozilla/dom/NodeBinding.h"
|
#include "mozilla/dom/NodeBinding.h"
|
||||||
#include "mozilla/dom/Selection.h"
|
|
||||||
#include "mozilla/dom/Text.h"
|
#include "mozilla/dom/Text.h"
|
||||||
#include "mozilla/intl/WordBreaker.h"
|
#include "mozilla/intl/WordBreaker.h"
|
||||||
#include "mozilla/PresShell.h"
|
|
||||||
#include "nsComputedDOMStyle.h"
|
#include "nsComputedDOMStyle.h"
|
||||||
#include "nsContentUtils.h"
|
#include "nsContentUtils.h"
|
||||||
#include "nsDOMAttributeMap.h"
|
#include "nsDOMAttributeMap.h"
|
||||||
#include "nsFind.h"
|
|
||||||
#include "nsGkAtoms.h"
|
#include "nsGkAtoms.h"
|
||||||
#include "nsICSSDeclaration.h"
|
#include "nsICSSDeclaration.h"
|
||||||
#include "nsIFrame.h"
|
#include "nsIFrame.h"
|
||||||
@@ -31,10 +28,6 @@
|
|||||||
namespace mozilla::dom {
|
namespace mozilla::dom {
|
||||||
static LazyLogModule sFragmentDirectiveLog("FragmentDirective");
|
static LazyLogModule sFragmentDirectiveLog("FragmentDirective");
|
||||||
|
|
||||||
#define DBG(msg, ...) \
|
|
||||||
MOZ_LOG(sFragmentDirectiveLog, LogLevel::Debug, \
|
|
||||||
("%s(): " msg, __FUNCTION__, ##__VA_ARGS__))
|
|
||||||
|
|
||||||
/** Converts a `TextDirective` into a percent-encoded string. */
|
/** Converts a `TextDirective` into a percent-encoded string. */
|
||||||
nsCString ToString(const TextDirective& aTextDirective) {
|
nsCString ToString(const TextDirective& aTextDirective) {
|
||||||
nsCString str;
|
nsCString str;
|
||||||
@@ -98,63 +91,15 @@ void FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(
|
|||||||
nsTArray<RefPtr<nsRange>> FragmentDirective::FindTextFragmentsInDocument() {
|
nsTArray<RefPtr<nsRange>> FragmentDirective::FindTextFragmentsInDocument() {
|
||||||
MOZ_ASSERT(mDocument);
|
MOZ_ASSERT(mDocument);
|
||||||
mDocument->FlushPendingNotifications(FlushType::Frames);
|
mDocument->FlushPendingNotifications(FlushType::Frames);
|
||||||
// https://wicg.github.io/scroll-to-text-fragment/#invoke-text-directives
|
|
||||||
// To invoke text directives, given as input a list of text directives text
|
|
||||||
// directives and a Document document, run these steps:
|
|
||||||
// 1. Let ranges be a list of ranges, initially empty.
|
|
||||||
nsTArray<RefPtr<nsRange>> textDirectiveRanges;
|
nsTArray<RefPtr<nsRange>> textDirectiveRanges;
|
||||||
|
for (const TextDirective& textDirective : mUninvokedTextDirectives) {
|
||||||
// Additionally (not mentioned in the spec), remove all text directives from
|
|
||||||
// the input list to keep only the ones that are not found.
|
|
||||||
// This code runs repeatedly during a page load, so it is possible that the
|
|
||||||
// match for a text directive has not been parsed yet.
|
|
||||||
nsTArray<TextDirective> uninvokedTextDirectives(
|
|
||||||
mUninvokedTextDirectives.Length());
|
|
||||||
|
|
||||||
// 2. For each text directive directive of text directives:
|
|
||||||
for (TextDirective& textDirective : mUninvokedTextDirectives) {
|
|
||||||
// 2.1 If the result of running find a range from a text directive given
|
|
||||||
// directive and document is non-null, then append it to ranges.
|
|
||||||
if (RefPtr<nsRange> range = FindRangeForTextDirective(textDirective)) {
|
if (RefPtr<nsRange> range = FindRangeForTextDirective(textDirective)) {
|
||||||
textDirectiveRanges.AppendElement(range);
|
textDirectiveRanges.AppendElement(range);
|
||||||
} else {
|
|
||||||
uninvokedTextDirectives.AppendElement(std::move(textDirective));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mUninvokedTextDirectives = std::move(uninvokedTextDirectives);
|
|
||||||
|
|
||||||
// 3. Return ranges.
|
|
||||||
return textDirectiveRanges;
|
return textDirectiveRanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FragmentDirective::HighlightTextDirectives(
|
|
||||||
const nsTArray<RefPtr<nsRange>>& aTextDirectiveRanges) {
|
|
||||||
MOZ_ASSERT(mDocument);
|
|
||||||
if (aTextDirectiveRanges.IsEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!StaticPrefs::dom_text_fragments_enabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RefPtr<Selection> targetTextSelection =
|
|
||||||
[doc = this->mDocument]() -> Selection* {
|
|
||||||
if (auto* presShell = doc->GetPresShell()) {
|
|
||||||
return presShell->GetCurrentSelection(SelectionType::eTargetText);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}();
|
|
||||||
if (!targetTextSelection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const RefPtr<nsRange>& range : aTextDirectiveRanges) {
|
|
||||||
// Script won't be able to manipulate `aTextDirectiveRanges`,
|
|
||||||
// therefore we can mark `range` as known live.
|
|
||||||
targetTextSelection->AddRangeAndSelectFramesAndNotifyListeners(
|
|
||||||
MOZ_KnownLive(*range), IgnoreErrors());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Determine if `aNode` should be considered when traversing the DOM.
|
* @brief Determine if `aNode` should be considered when traversing the DOM.
|
||||||
*
|
*
|
||||||
@@ -446,7 +391,9 @@ RangeBoundary MoveRangeBoundaryOneWord(const RangeBoundary& aRangeBoundary,
|
|||||||
|
|
||||||
RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
||||||
const TextDirective& aTextDirective) {
|
const TextDirective& aTextDirective) {
|
||||||
DBG("Find range for text directive '%s'.", ToString(aTextDirective).Data());
|
MOZ_LOG(sFragmentDirectiveLog, LogLevel::Info,
|
||||||
|
("FragmentDirective::%s(): Find range for text directive '%s'.",
|
||||||
|
__FUNCTION__, ToString(aTextDirective).Data()));
|
||||||
// 1. Let searchRange be a range with start (document, 0) and end (document,
|
// 1. Let searchRange be a range with start (document, 0) and end (document,
|
||||||
// document’s length)
|
// document’s length)
|
||||||
ErrorResult rv;
|
ErrorResult rv;
|
||||||
@@ -468,14 +415,8 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
FindStringInRange(searchRange, aTextDirective.prefix, true, false);
|
FindStringInRange(searchRange, aTextDirective.prefix, true, false);
|
||||||
// 2.2.2. If prefixMatch is null, return null.
|
// 2.2.2. If prefixMatch is null, return null.
|
||||||
if (!prefixMatch) {
|
if (!prefixMatch) {
|
||||||
DBG("Did not find prefix '%s'. The text directive does not exist "
|
|
||||||
"in the document.",
|
|
||||||
NS_ConvertUTF16toUTF8(aTextDirective.prefix).Data());
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
DBG("Did find prefix '%s'.",
|
|
||||||
NS_ConvertUTF16toUTF8(aTextDirective.prefix).Data());
|
|
||||||
|
|
||||||
// 2.2.3. Set searchRange’s start to the first boundary point after
|
// 2.2.3. Set searchRange’s start to the first boundary point after
|
||||||
// prefixMatch’s start
|
// prefixMatch’s start
|
||||||
const RangeBoundary boundaryPoint = MoveRangeBoundaryOneWord(
|
const RangeBoundary boundaryPoint = MoveRangeBoundaryOneWord(
|
||||||
@@ -521,21 +462,14 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
false, mustEndAtWordBoundary);
|
false, mustEndAtWordBoundary);
|
||||||
// 2.2.10. If potentialMatch is null, return null.
|
// 2.2.10. If potentialMatch is null, return null.
|
||||||
if (!potentialMatch) {
|
if (!potentialMatch) {
|
||||||
DBG("Did not find start '%s'. The text directive does not exist "
|
|
||||||
"in the document.",
|
|
||||||
NS_ConvertUTF16toUTF8(aTextDirective.start).Data());
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
DBG("Did find start '%s'.",
|
|
||||||
NS_ConvertUTF16toUTF8(aTextDirective.start).Data());
|
|
||||||
// 2.2.11. If potentialMatch’s start is not matchRange’s start, then
|
// 2.2.11. If potentialMatch’s start is not matchRange’s start, then
|
||||||
// continue.
|
// continue.
|
||||||
// (In this case, we found a prefix but it was followed by something other
|
// (In this case, we found a prefix but it was followed by something other
|
||||||
// than a matching text so we’ll continue searching for the next instance
|
// than a matching text so we’ll continue searching for the next instance
|
||||||
// of prefix.)
|
// of prefix.)
|
||||||
if (potentialMatch->StartRef() != matchRange->StartRef()) {
|
if (potentialMatch->StartRef() != matchRange->StartRef()) {
|
||||||
DBG("The prefix is not directly followed by the start element. "
|
|
||||||
"Discarding this attempt.");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,9 +486,6 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
true, mustEndAtWordBoundary);
|
true, mustEndAtWordBoundary);
|
||||||
// 2.3.3. If potentialMatch is null, return null.
|
// 2.3.3. If potentialMatch is null, return null.
|
||||||
if (!potentialMatch) {
|
if (!potentialMatch) {
|
||||||
DBG("Did not find start '%s'. The text directive does not exist "
|
|
||||||
"in the document.",
|
|
||||||
NS_ConvertUTF16toUTF8(aTextDirective.start).Data());
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
// 2.3.4. Set searchRange’s start to the first boundary point after
|
// 2.3.4. Set searchRange’s start to the first boundary point after
|
||||||
@@ -594,9 +525,6 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
mustEndAtWordBoundary);
|
mustEndAtWordBoundary);
|
||||||
// 2.5.1.3. If endMatch is null then return null.
|
// 2.5.1.3. If endMatch is null then return null.
|
||||||
if (!endMatch) {
|
if (!endMatch) {
|
||||||
DBG("Did not find end '%s'. The text directive does not exist "
|
|
||||||
"in the document.",
|
|
||||||
NS_ConvertUTF16toUTF8(aTextDirective.end).Data());
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
// 2.5.1.4. Set potentialMatch’s end to endMatch’s end.
|
// 2.5.1.4. Set potentialMatch’s end to endMatch’s end.
|
||||||
@@ -609,7 +537,6 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
|
|
||||||
// 2.5.3. If parsedValues’s suffix is null, return potentialMatch.
|
// 2.5.3. If parsedValues’s suffix is null, return potentialMatch.
|
||||||
if (aTextDirective.suffix.IsEmpty()) {
|
if (aTextDirective.suffix.IsEmpty()) {
|
||||||
DBG("Did find a match.");
|
|
||||||
return potentialMatch;
|
return potentialMatch;
|
||||||
}
|
}
|
||||||
// 2.5.4. Let suffixRange be a range with start equal to potentialMatch’s
|
// 2.5.4. Let suffixRange be a range with start equal to potentialMatch’s
|
||||||
@@ -633,9 +560,6 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
// (If the suffix doesn't appear in the remaining text of the document,
|
// (If the suffix doesn't appear in the remaining text of the document,
|
||||||
// there's no possible way to make a match.)
|
// there's no possible way to make a match.)
|
||||||
if (!suffixMatch) {
|
if (!suffixMatch) {
|
||||||
DBG("Did not find suffix '%s'. The text directive does not exist "
|
|
||||||
"in the document.",
|
|
||||||
NS_ConvertUTF16toUTF8(aTextDirective.suffix).Data());
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
// 2.5.8. If suffixMatch's start is suffixRange's start, return
|
// 2.5.8. If suffixMatch's start is suffixRange's start, return
|
||||||
@@ -643,7 +567,6 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
if (suffixMatch->GetStartContainer() ==
|
if (suffixMatch->GetStartContainer() ==
|
||||||
suffixRange->GetStartContainer() &&
|
suffixRange->GetStartContainer() &&
|
||||||
suffixMatch->StartOffset() == suffixRange->StartOffset()) {
|
suffixMatch->StartOffset() == suffixRange->StartOffset()) {
|
||||||
DBG("Did find a match.");
|
|
||||||
return potentialMatch;
|
return potentialMatch;
|
||||||
}
|
}
|
||||||
// 2.5.9. If parsedValue's end item is null then break;
|
// 2.5.9. If parsedValue's end item is null then break;
|
||||||
@@ -669,29 +592,11 @@ RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
|||||||
// matches in step 9 of the above loop. If we couldn’t find a valid
|
// matches in step 9 of the above loop. If we couldn’t find a valid
|
||||||
// rangeEnd+suffix pair anywhere in the doc then there’s no possible way
|
// rangeEnd+suffix pair anywhere in the doc then there’s no possible way
|
||||||
// to make a match.)
|
// to make a match.)
|
||||||
// ----
|
// XXX(:jjaschke): should this really assert?
|
||||||
// XXX(:jjaschke): Not too sure about this. If a text directive is only
|
MOZ_ASSERT(!aTextDirective.end.IsEmpty());
|
||||||
// defined by a (prefix +) start element, and the start element happens to
|
|
||||||
// be at the end of the document, `rangeEndSearchRange` could be
|
|
||||||
// collapsed. Therefore, the loop in section 2.5 does not run. Also,
|
|
||||||
// if there would be either an `end` and/or a `suffix`, this would assert
|
|
||||||
// instead of returning `nullptr`, indicating that there's no match.
|
|
||||||
// Instead, the following would make the algorithm more safe:
|
|
||||||
// if there is no end or suffix, the potential match is actually a match,
|
|
||||||
// so return it. Otherwise, the text directive can't be in the document,
|
|
||||||
// therefore return nullptr.
|
|
||||||
if (aTextDirective.end.IsEmpty() && aTextDirective.suffix.IsEmpty()) {
|
|
||||||
DBG("rangeEndSearchRange was collapsed, no end or suffix "
|
|
||||||
"present. Returning a match");
|
|
||||||
return potentialMatch;
|
|
||||||
}
|
|
||||||
DBG("rangeEndSearchRange was collapsed, there is an end or "
|
|
||||||
"suffix. There can't be a match.");
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 3. Return null.
|
// 3. Return null.
|
||||||
DBG("Did not find a match.");
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,29 +762,111 @@ RefPtr<nsRange> FragmentDirective::FindStringInRange(nsRange* aSearchRange,
|
|||||||
bool aWordStartBounded,
|
bool aWordStartBounded,
|
||||||
bool aWordEndBounded) {
|
bool aWordEndBounded) {
|
||||||
MOZ_ASSERT(aSearchRange);
|
MOZ_ASSERT(aSearchRange);
|
||||||
DBG("query='%s', wordStartBounded='%d', wordEndBounded='%d'.\n",
|
RefPtr<nsRange> searchRange = aSearchRange->CloneRange();
|
||||||
NS_ConvertUTF16toUTF8(aQuery).Data(), aWordStartBounded, aWordEndBounded);
|
// 1. While searchRange is not collapsed
|
||||||
RefPtr<nsFind> finder = new nsFind();
|
while (searchRange && !searchRange->Collapsed()) {
|
||||||
finder->SetWordStartBounded(aWordStartBounded);
|
// 1.1. Let curNode be searchRange’s start node.
|
||||||
finder->SetWordEndBounded(aWordEndBounded);
|
RefPtr<nsINode> curNode = searchRange->GetStartContainer();
|
||||||
finder->SetCaseSensitive(false);
|
|
||||||
RefPtr<nsRange> searchRangeStart = nsRange::Create(
|
// 1.2. If curNode is part of a non-searchable subtree:
|
||||||
aSearchRange->StartRef(), aSearchRange->StartRef(), IgnoreErrors());
|
if (NodeIsPartOfNonSearchableSubTree(*curNode)) {
|
||||||
RefPtr<nsRange> searchRangeEnd = nsRange::Create(
|
// 1.2.1. Set searchRange’s start node to the next node, in
|
||||||
aSearchRange->EndRef(), aSearchRange->EndRef(), IgnoreErrors());
|
// shadow-including tree order, that isn’t a shadow-including descendant
|
||||||
RefPtr<nsRange> result;
|
// of curNode.
|
||||||
Unused << finder->Find(aQuery, aSearchRange, searchRangeStart, searchRangeEnd,
|
RefPtr<nsINode> next = curNode;
|
||||||
getter_AddRefs(result));
|
while ((next = next->GetNextNode())) {
|
||||||
if (!result || result->Collapsed()) {
|
if (!next->IsShadowIncludingInclusiveDescendantOf(curNode)) {
|
||||||
DBG("Did not find query '%s'", NS_ConvertUTF16toUTF8(aQuery).Data());
|
break;
|
||||||
} else {
|
}
|
||||||
auto rangeToString = [](nsRange* range) -> nsCString {
|
}
|
||||||
nsString rangeString;
|
if (!next) {
|
||||||
range->ToString(rangeString, IgnoreErrors());
|
return nullptr;
|
||||||
return NS_ConvertUTF16toUTF8(rangeString);
|
}
|
||||||
};
|
// 1.2.2. Set `searchRange`s `start offset` to 0
|
||||||
DBG("find returned '%s'", rangeToString(result).Data());
|
searchRange->SetStart(next, 0);
|
||||||
|
// 1.2.3. continue.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 1.3. If curNode is not a visible TextNode:
|
||||||
|
if (!NodeIsVisibleTextNode(*curNode)) {
|
||||||
|
// 1.3.1. Set searchRange’s start node to the next node, in
|
||||||
|
// shadow-including tree order, that is not a doctype.
|
||||||
|
RefPtr<nsINode> next = curNode;
|
||||||
|
while ((next = next->GetNextNode())) {
|
||||||
|
if (next->NodeType() != Node_Binding::DOCUMENT_TYPE_NODE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!next) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// 1.3.2. Set searchRange’s start offset to 0.
|
||||||
|
searchRange->SetStart(next, 0);
|
||||||
|
// 1.3.3. continue.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 1.4. Let blockAncestor be the nearest block ancestor of `curNode`
|
||||||
|
RefPtr<nsINode> blockAncestor = GetBlockAncestorForNode(curNode);
|
||||||
|
|
||||||
|
// 1.5. Let textNodeList be a list of Text nodes, initially empty.
|
||||||
|
nsTArray<RefPtr<Text>> textNodeList;
|
||||||
|
// 1.6. While curNode is a shadow-including descendant of blockAncestor and
|
||||||
|
// the position of the boundary point (curNode,0) is not after searchRange's
|
||||||
|
// end:
|
||||||
|
while (curNode &&
|
||||||
|
curNode->IsShadowIncludingInclusiveDescendantOf(blockAncestor)) {
|
||||||
|
Maybe<int32_t> comp = nsContentUtils::ComparePoints(
|
||||||
|
curNode, 0, searchRange->GetEndContainer(), searchRange->EndOffset());
|
||||||
|
if (comp) {
|
||||||
|
if (*comp >= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This means that the compared nodes are disconnected.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// 1.6.1. If curNode has block-level display, then break.
|
||||||
|
if (NodeHasBlockLevelDisplay(*curNode)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 1.6.2. If curNode is search invisible:
|
||||||
|
if (NodeIsSearchInvisible(*curNode)) {
|
||||||
|
// 1.6.2.1. Set curNode to the next node, in shadow-including tree
|
||||||
|
// order, that isn't a shadow-including descendant of curNode.
|
||||||
|
curNode = curNode->GetNextNode();
|
||||||
|
// 1.6.2.2. Continue.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 1.6.3. If curNode is a visible text node then append it to
|
||||||
|
// textNodeList.
|
||||||
|
if (NodeIsVisibleTextNode(*curNode)) {
|
||||||
|
textNodeList.AppendElement(curNode->AsText());
|
||||||
|
}
|
||||||
|
// 1.6.4. Set curNode to the next node in shadow-including
|
||||||
|
// tree order.
|
||||||
|
curNode = curNode->GetNextNode();
|
||||||
|
}
|
||||||
|
// 1.7. Run the find a range from a node list steps given
|
||||||
|
// query, searchRange, textNodeList, wordStartBounded, wordEndBounded as
|
||||||
|
// input. If the resulting Range is not null, then return it.
|
||||||
|
if (RefPtr<nsRange> range =
|
||||||
|
FindRangeFromNodeList(searchRange, aQuery, textNodeList,
|
||||||
|
aWordStartBounded, aWordEndBounded)) {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.8. If curNode is null, then break.
|
||||||
|
if (!curNode) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.9. Assert: curNode follows searchRange's start node.
|
||||||
|
|
||||||
|
// 1.10. Set searchRange's start to the boundary point (curNode,0).
|
||||||
|
searchRange->SetStart(curNode, 0);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
|
// 2. Return null.
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
} // namespace mozilla::dom
|
} // namespace mozilla::dom
|
||||||
|
|||||||
@@ -75,11 +75,6 @@ class FragmentDirective final : public nsISupports, public nsWrapperCache {
|
|||||||
/** Clears all uninvoked directives. */
|
/** Clears all uninvoked directives. */
|
||||||
void ClearUninvokedDirectives() { mUninvokedTextDirectives.Clear(); }
|
void ClearUninvokedDirectives() { mUninvokedTextDirectives.Clear(); }
|
||||||
|
|
||||||
/** Inserts all text directive ranges into a `eTargetText` `Selection`. */
|
|
||||||
MOZ_CAN_RUN_SCRIPT
|
|
||||||
void HighlightTextDirectives(
|
|
||||||
const nsTArray<RefPtr<nsRange>>& aTextDirectiveRanges);
|
|
||||||
|
|
||||||
/** Searches for the current uninvoked text directives and creates a range for
|
/** Searches for the current uninvoked text directives and creates a range for
|
||||||
* each one that is found.
|
* each one that is found.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3062,8 +3062,7 @@ UniquePtr<gfxContext> PresShell::CreateReferenceRenderingContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier
|
// https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier
|
||||||
nsresult PresShell::GoToAnchor(const nsAString& aAnchorName,
|
nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll,
|
||||||
const nsRange* aFirstTextDirective, bool aScroll,
|
|
||||||
ScrollFlags aAdditionalScrollFlags) {
|
ScrollFlags aAdditionalScrollFlags) {
|
||||||
if (!mDocument) {
|
if (!mDocument) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
@@ -3082,31 +3081,12 @@ nsresult PresShell::GoToAnchor(const nsAString& aAnchorName,
|
|||||||
// Hold a reference to the ESM in case event dispatch tears us down.
|
// Hold a reference to the ESM in case event dispatch tears us down.
|
||||||
RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
|
RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
|
||||||
|
|
||||||
// https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
|
|
||||||
// From "Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:"
|
|
||||||
// 3.4. If target is a range, then:
|
|
||||||
// 3.4.1 Set target to be the first common ancestor of target's start node and
|
|
||||||
// end node.
|
|
||||||
// 3.4.2 While target is non-null and is not an element, set target to
|
|
||||||
// target's parent.
|
|
||||||
Element* textFragmentTargetElement = [&aFirstTextDirective]() -> Element* {
|
|
||||||
nsINode* node =
|
|
||||||
aFirstTextDirective
|
|
||||||
? aFirstTextDirective->GetClosestCommonInclusiveAncestor()
|
|
||||||
: nullptr;
|
|
||||||
while (node && !node->IsElement()) {
|
|
||||||
node = node->GetParent();
|
|
||||||
}
|
|
||||||
return Element::FromNodeOrNull(node);
|
|
||||||
}();
|
|
||||||
const bool thereIsATextFragment = !!textFragmentTargetElement;
|
|
||||||
|
|
||||||
// 1. If there is no indicated part of the document, set the Document's target
|
// 1. If there is no indicated part of the document, set the Document's target
|
||||||
// element to null.
|
// element to null.
|
||||||
//
|
//
|
||||||
// FIXME(emilio): Per spec empty fragment string should take the same
|
// FIXME(emilio): Per spec empty fragment string should take the same
|
||||||
// code-path as "top"!
|
// code-path as "top"!
|
||||||
if (aAnchorName.IsEmpty() && !thereIsATextFragment) {
|
if (aAnchorName.IsEmpty()) {
|
||||||
NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
|
NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
|
||||||
esm->SetContentState(nullptr, ElementState::URLTARGET);
|
esm->SetContentState(nullptr, ElementState::URLTARGET);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
@@ -3120,10 +3100,8 @@ nsresult PresShell::GoToAnchor(const nsAString& aAnchorName,
|
|||||||
//
|
//
|
||||||
// https://html.spec.whatwg.org/#target-element
|
// https://html.spec.whatwg.org/#target-element
|
||||||
// https://html.spec.whatwg.org/#find-a-potential-indicated-element
|
// https://html.spec.whatwg.org/#find-a-potential-indicated-element
|
||||||
RefPtr<Element> target = textFragmentTargetElement;
|
RefPtr<Element> target =
|
||||||
if (!target) {
|
nsContentUtils::GetTargetElement(mDocument, aAnchorName);
|
||||||
target = nsContentUtils::GetTargetElement(mDocument, aAnchorName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. If there is no indicated part of the document, set the Document's
|
// 1. If there is no indicated part of the document, set the Document's
|
||||||
// target element to null.
|
// target element to null.
|
||||||
@@ -3142,12 +3120,6 @@ nsresult PresShell::GoToAnchor(const nsAString& aAnchorName,
|
|||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
if (aScroll) {
|
if (aScroll) {
|
||||||
// https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
|
|
||||||
// From "Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:"
|
|
||||||
// 3.9 Let blockPosition be "center" if scrollTarget is a range, "start"
|
|
||||||
// otherwise.
|
|
||||||
const auto verticalScrollPosition = WhereToScroll(
|
|
||||||
thereIsATextFragment ? WhereToScroll::Center : WhereToScroll::Start);
|
|
||||||
// 3.3. TODO: Run the ancestor details revealing algorithm on target.
|
// 3.3. TODO: Run the ancestor details revealing algorithm on target.
|
||||||
// 3.4. Scroll target into view, with behavior set to "auto", block set to
|
// 3.4. Scroll target into view, with behavior set to "auto", block set to
|
||||||
// "start", and inline set to "nearest".
|
// "start", and inline set to "nearest".
|
||||||
@@ -3155,7 +3127,7 @@ nsresult PresShell::GoToAnchor(const nsAString& aAnchorName,
|
|||||||
// smooth scroll for `top` regardless below, so maybe they should!).
|
// smooth scroll for `top` regardless below, so maybe they should!).
|
||||||
ScrollingInteractionContext scrollToAnchorContext(true);
|
ScrollingInteractionContext scrollToAnchorContext(true);
|
||||||
MOZ_TRY(ScrollContentIntoView(
|
MOZ_TRY(ScrollContentIntoView(
|
||||||
target, ScrollAxis(verticalScrollPosition, WhenToScroll::Always),
|
target, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always),
|
||||||
ScrollAxis(),
|
ScrollAxis(),
|
||||||
ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags));
|
ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags));
|
||||||
|
|
||||||
@@ -3273,6 +3245,46 @@ nsresult PresShell::ScrollToAnchor() {
|
|||||||
ScrollAxis(), ScrollFlags::AnchorScrollFlags);
|
ScrollAxis(), ScrollFlags::AnchorScrollFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PresShell::HighlightAndGoToTextFragment(bool aScrollToTextFragment) {
|
||||||
|
MOZ_ASSERT(mDocument);
|
||||||
|
if (!StaticPrefs::dom_text_fragments_enabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const RefPtr<FragmentDirective> fragmentDirective =
|
||||||
|
mDocument->FragmentDirective();
|
||||||
|
|
||||||
|
nsTArray<RefPtr<nsRange>> textDirectiveRanges =
|
||||||
|
fragmentDirective->FindTextFragmentsInDocument();
|
||||||
|
if (textDirectiveRanges.IsEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RefPtr<Selection> targetTextSelection =
|
||||||
|
GetCurrentSelection(SelectionType::eTargetText);
|
||||||
|
if (!targetTextSelection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (RefPtr<nsRange> range : textDirectiveRanges) {
|
||||||
|
targetTextSelection->AddRangeAndSelectFramesAndNotifyListeners(
|
||||||
|
*range, IgnoreErrors());
|
||||||
|
}
|
||||||
|
if (!aScrollToTextFragment) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll the last text directive into view.
|
||||||
|
nsRange* lastRange = textDirectiveRanges.LastElement();
|
||||||
|
MOZ_ASSERT(lastRange);
|
||||||
|
if (RefPtr<nsIContent> lastRangeStartContent =
|
||||||
|
nsIContent::FromNode(lastRange->GetStartContainer())) {
|
||||||
|
return ScrollContentIntoView(
|
||||||
|
lastRangeStartContent,
|
||||||
|
ScrollAxis(WhereToScroll::Center, WhenToScroll::Always),
|
||||||
|
ScrollAxis(), ScrollFlags::AnchorScrollFlags) == NS_OK;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Helper (per-continuation) for ScrollContentIntoView.
|
* Helper (per-continuation) for ScrollContentIntoView.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1563,7 +1563,7 @@ class PresShell final : public nsStubDocumentObserver,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the pres shell that the document is now at the anchor with
|
* Informs the pres shell that the document is now at the anchor with
|
||||||
* the given name or range. If |aScroll| is true, scrolls the view of the
|
* the given name. If |aScroll| is true, scrolls the view of the
|
||||||
* document so that the anchor with the specified name is displayed at
|
* document so that the anchor with the specified name is displayed at
|
||||||
* the top of the window. If |aAnchorName| is empty, then this informs
|
* the top of the window. If |aAnchorName| is empty, then this informs
|
||||||
* the pres shell that there is no current target, and |aScroll| must
|
* the pres shell that there is no current target, and |aScroll| must
|
||||||
@@ -1571,8 +1571,7 @@ class PresShell final : public nsStubDocumentObserver,
|
|||||||
* and |aScroll| is true, the scrolling may be performed with an animation.
|
* and |aScroll| is true, the scrolling may be performed with an animation.
|
||||||
*/
|
*/
|
||||||
MOZ_CAN_RUN_SCRIPT
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult GoToAnchor(const nsAString& aAnchorName,
|
nsresult GoToAnchor(const nsAString& aAnchorName, bool aScroll,
|
||||||
const nsRange* aFirstTextDirective, bool aScroll,
|
|
||||||
ScrollFlags aAdditionalScrollFlags = ScrollFlags::None);
|
ScrollFlags aAdditionalScrollFlags = ScrollFlags::None);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1585,6 +1584,18 @@ class PresShell final : public nsStubDocumentObserver,
|
|||||||
*/
|
*/
|
||||||
MOZ_CAN_RUN_SCRIPT nsresult ScrollToAnchor();
|
MOZ_CAN_RUN_SCRIPT nsresult ScrollToAnchor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds text fragments ranes in the document, highlights the ranges and
|
||||||
|
* scrolls to the last text fragment range on the page if
|
||||||
|
* `aScrollToTextFragment` is true.
|
||||||
|
*
|
||||||
|
* @param aScrollToTextFragment If true, scrolls the view to the last text
|
||||||
|
* fragment.
|
||||||
|
* @return True if scrolling happened.
|
||||||
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT bool HighlightAndGoToTextFragment(
|
||||||
|
bool aScrollToTextFragment);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When scroll anchoring adjusts positions in the root frame during page load,
|
* When scroll anchoring adjusts positions in the root frame during page load,
|
||||||
* it may move our scroll position in the root frame.
|
* it may move our scroll position in the root frame.
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include "mozilla/dom/Document.h"
|
#include "mozilla/dom/Document.h"
|
||||||
#include "mozilla/dom/DocumentInlines.h"
|
#include "mozilla/dom/DocumentInlines.h"
|
||||||
#include "mozilla/dom/DocGroup.h"
|
#include "mozilla/dom/DocGroup.h"
|
||||||
#include "mozilla/dom/FragmentDirective.h"
|
|
||||||
#include "mozilla/widget/Screen.h"
|
#include "mozilla/widget/Screen.h"
|
||||||
#include "nsPresContext.h"
|
#include "nsPresContext.h"
|
||||||
#include "nsIFrame.h"
|
#include "nsIFrame.h"
|
||||||
@@ -1078,10 +1077,6 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) {
|
|||||||
|
|
||||||
if (!mStopped) {
|
if (!mStopped) {
|
||||||
if (mDocument) {
|
if (mDocument) {
|
||||||
// This is the final attempt to scroll to an anchor / text directive.
|
|
||||||
// This is the last iteration of the algorithm described in the spec for
|
|
||||||
// trying to scroll to a fragment.
|
|
||||||
// https://html.spec.whatwg.org/#try-to-scroll-to-the-fragment
|
|
||||||
nsCOMPtr<Document> document = mDocument;
|
nsCOMPtr<Document> document = mDocument;
|
||||||
document->ScrollToRef();
|
document->ScrollToRef();
|
||||||
}
|
}
|
||||||
@@ -1098,19 +1093,6 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
|
|
||||||
// Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:
|
|
||||||
// 2.1 If the user agent has reason to believe the user is no longer
|
|
||||||
// interested in scrolling to the fragment, then:
|
|
||||||
// 2.1.1 Set pending text directives to null.
|
|
||||||
//
|
|
||||||
// Gecko's implementation differs from the spec (ie., it implements its
|
|
||||||
// intention but doesn't follow step by step), therefore the mentioned steps
|
|
||||||
// are not applied in the same manner.
|
|
||||||
// However, this should be the right place to do this.
|
|
||||||
if (mDocument) {
|
|
||||||
mDocument->FragmentDirective()->ClearUninvokedDirectives();
|
|
||||||
}
|
|
||||||
if (mDocument && !restoring) {
|
if (mDocument && !restoring) {
|
||||||
mDocument->LoadEventFired();
|
mDocument->LoadEventFired();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,17 @@
|
|||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Cross-origin with element-id fallback]
|
[Cross-origin with element-id fallback]
|
||||||
expected: [FAIL, PASS]
|
expected: FAIL
|
||||||
|
|
||||||
[Navigate cross-origin iframe via window.location]
|
[Navigate cross-origin iframe via window.location]
|
||||||
expected: [FAIL, PASS]
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
FAIL
|
||||||
|
|
||||||
|
[Navigate same-origin iframe via window.location]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Non-matching text with element-id fallback]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|||||||
@@ -12,6 +12,3 @@
|
|||||||
|
|
||||||
[Text directive blocked in application/xml]
|
[Text directive blocked in application/xml]
|
||||||
expected: [NOTRUN, FAIL, TIMEOUT, PASS]
|
expected: [NOTRUN, FAIL, TIMEOUT, PASS]
|
||||||
|
|
||||||
[Text directive blocked in text/css]
|
|
||||||
expected: FAIL
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[percent-encoding.html]
|
||||||
|
[Test navigation with fragment: Percent char without hex digits is invalid..]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Test navigation with fragment: Percent char followed by percent char is invalid..]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Test navigation with fragment: Single digit percent-encoding is invalid..]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Test navigation with fragment: Percent-encoding limited to two digits..]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Test navigation with fragment: Percent-encoded "%%F".]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Percent-encoding multibyte codepoint (CHECKMARK)..]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
@@ -1,4 +1,92 @@
|
|||||||
[scroll-to-text-fragment.html]
|
[scroll-to-text-fragment.html]
|
||||||
[Test navigation with fragment: Text directive should horizontally scroll into view.]
|
[Test navigation with fragment: Text directive with invalid syntax (context terms without "-") should not parse as a text directive.]
|
||||||
expected:
|
expected:
|
||||||
if (os == "android"): [PASS, FAIL]
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Test navigation with fragment: Exact text with no context should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Exact text with prefix should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Exact text with suffix should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Exact text with prefix and suffix should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Exact text with prefix and suffix and query equals prefix..]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text range with no context should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text range with prefix should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text range with suffix should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text range with prefix and suffix should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Exact text with percent encoded spaces should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Fragment directive with percent encoded syntactical characters "&,-" should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Fragment directive with percent encoded non-ASCII unicode character should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Fragment directive with all TextMatchChars should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Multiple matching exact texts should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: A non-matching text directive followed by a matching text directive should match and scroll into view the second text directive.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text directive followed by non-text directive should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Multiple text directives and a non-text directive should match text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text directive with existing element fragment should match and scroll into view text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text directive with nonexistent element fragment should match and scroll into view text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Multiple match text directive disambiguated by prefix should match the prefixed text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Multiple match text directive disambiguated by suffix should match the suffixed text.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Multiple match text directive disambiguated by prefix and suffix should match the text with the given context.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text directive should match when context terms are separated by node boundaries.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text directive should match text within shadow DOM.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Text directive should horizontally scroll into view.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Test navigation with fragment: Uppercase TEXT directive should not parse as a text directive.]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Test navigation with fragment: Generic fragment directive with existing element fragment should scroll to element.]
|
||||||
|
expected:
|
||||||
|
if (os == "mac") and not debug: [PASS, FAIL]
|
||||||
|
if (os == "android") and debug: [PASS, FAIL]
|
||||||
|
|
||||||
|
[Test navigation with fragment: Non-matching text directive with existing element fragment should scroll to element.]
|
||||||
|
expected:
|
||||||
|
if (os == "android") and debug: [PASS, FAIL]
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<title>
|
|
||||||
Navigating to a text fragment which is only available after `DOMContentLoaded`
|
|
||||||
</title>
|
|
||||||
<script src="stash.js"></script>
|
|
||||||
<script>
|
|
||||||
function checkScroll() {
|
|
||||||
const results = {hasScrolled: window.scrollY != 0};
|
|
||||||
let key = (new URL(document.location)).searchParams.get("key");
|
|
||||||
stashResultsThenClose(key, results);
|
|
||||||
};
|
|
||||||
window.onload = () => {
|
|
||||||
window.requestAnimationFrame(function() {
|
|
||||||
window.requestAnimationFrame(checkScroll);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
document.body.innerHTML = `<div style="margin-top: 200vh">After DOMContentLoaded</div>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<title>Navigating to a text fragment directive</title>
|
|
||||||
<meta charset=utf-8>
|
|
||||||
<link rel="help" href="https://wicg.github.io/ScrollToTextFragment/">
|
|
||||||
<meta name="timeout" content="long">
|
|
||||||
<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="/common/utils.js"></script>
|
|
||||||
<script src="stash.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
promise_test(t => new Promise((resolve, reject) => {
|
|
||||||
let key = token();
|
|
||||||
test_driver.bless('Open a URL with a text fragment directive', () => {
|
|
||||||
window.open(`scroll-to-text-fragment-after-DOMContentLoaded-target.html?key=${key}#:~:text=DOMContentLoaded`, "_blank", "noopener");
|
|
||||||
|
|
||||||
fetchResults(key, resolve, reject);
|
|
||||||
})
|
|
||||||
}).then(data => {
|
|
||||||
assert_true(data.hasScrolled, "Expected text directive to be scrolled to.");
|
|
||||||
})
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
@@ -46,11 +46,6 @@ let test_cases = [
|
|||||||
expect_position: 'text',
|
expect_position: 'text',
|
||||||
description: 'Exact text with no context should match text'
|
description: 'Exact text with no context should match text'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
fragment: '#:~:text=TEST',
|
|
||||||
expect_position: 'text',
|
|
||||||
description: 'Case-insensitive search with no context should match text'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
fragment: '#:~:text=this is a-,test',
|
fragment: '#:~:text=this is a-,test',
|
||||||
expect_position: 'text',
|
expect_position: 'text',
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ XPIDL_SOURCES += [
|
|||||||
"nsIWebBrowserFind.idl",
|
"nsIWebBrowserFind.idl",
|
||||||
]
|
]
|
||||||
|
|
||||||
EXPORTS += [
|
|
||||||
"nsFind.h",
|
|
||||||
]
|
|
||||||
|
|
||||||
XPIDL_MODULE = "mozfind"
|
XPIDL_MODULE = "mozfind"
|
||||||
|
|
||||||
UNIFIED_SOURCES += [
|
UNIFIED_SOURCES += [
|
||||||
|
|||||||
@@ -480,13 +480,13 @@ NS_IMETHODIMP
|
|||||||
nsFind::GetEntireWord(bool* aEntireWord) {
|
nsFind::GetEntireWord(bool* aEntireWord) {
|
||||||
if (!aEntireWord) return NS_ERROR_NULL_POINTER;
|
if (!aEntireWord) return NS_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
*aEntireWord = mWordStartBounded && mWordEndBounded;
|
*aEntireWord = mEntireWord;
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsFind::SetEntireWord(bool aEntireWord) {
|
nsFind::SetEntireWord(bool aEntireWord) {
|
||||||
mWordStartBounded = mWordEndBounded = aEntireWord;
|
mEntireWord = aEntireWord;
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -861,7 +861,7 @@ nsFind::Find(const nsAString& aPatText, nsRange* aSearchRange,
|
|||||||
// Figure whether the previous char is a word-breaking one,
|
// Figure whether the previous char is a word-breaking one,
|
||||||
// if we care about word boundaries.
|
// if we care about word boundaries.
|
||||||
bool wordBreakPrev = true;
|
bool wordBreakPrev = true;
|
||||||
if (mWordStartBounded && prevChar) {
|
if (mEntireWord && prevChar) {
|
||||||
if (prevChar == NBSP_CHARCODE) {
|
if (prevChar == NBSP_CHARCODE) {
|
||||||
prevChar = CHAR_TO_UNICHAR(' ');
|
prevChar = CHAR_TO_UNICHAR(' ');
|
||||||
}
|
}
|
||||||
@@ -874,8 +874,7 @@ nsFind::Find(const nsAString& aPatText, nsRange* aSearchRange,
|
|||||||
// b) a match has already been stored
|
// b) a match has already been stored
|
||||||
// c) the previous character is a different "class" than the current
|
// c) the previous character is a different "class" than the current
|
||||||
// character.
|
// character.
|
||||||
if ((c == patc && (!(mWordStartBounded || mWordEndBounded) ||
|
if ((c == patc && (!mEntireWord || matchAnchorNode || wordBreakPrev)) ||
|
||||||
matchAnchorNode || wordBreakPrev)) ||
|
|
||||||
(inWhitespace && IsSpace(c))) {
|
(inWhitespace && IsSpace(c))) {
|
||||||
prevCharInMatch = c;
|
prevCharInMatch = c;
|
||||||
if (inWhitespace) {
|
if (inWhitespace) {
|
||||||
@@ -902,7 +901,7 @@ nsFind::Find(const nsAString& aPatText, nsRange* aSearchRange,
|
|||||||
|
|
||||||
// Make the range:
|
// Make the range:
|
||||||
// Check for word break (if necessary)
|
// Check for word break (if necessary)
|
||||||
if (mWordEndBounded || inWhitespace) {
|
if (mEntireWord || inWhitespace) {
|
||||||
int32_t nextfindex = findex + incr;
|
int32_t nextfindex = findex + incr;
|
||||||
|
|
||||||
char32_t nextChar;
|
char32_t nextChar;
|
||||||
@@ -923,7 +922,7 @@ nsFind::Find(const nsAString& aPatText, nsRange* aSearchRange,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If a word break isn't there when it needs to be, reset search.
|
// If a word break isn't there when it needs to be, reset search.
|
||||||
if (mWordEndBounded && nextChar && !BreakInBetween(c, nextChar)) {
|
if (mEntireWord && nextChar && !BreakInBetween(c, nextChar)) {
|
||||||
matchAnchorNode = nullptr;
|
matchAnchorNode = nullptr;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,19 +28,6 @@ class nsFind : public nsIFind {
|
|||||||
NS_DECL_NSIFIND
|
NS_DECL_NSIFIND
|
||||||
NS_DECL_CYCLE_COLLECTION_CLASS(nsFind)
|
NS_DECL_CYCLE_COLLECTION_CLASS(nsFind)
|
||||||
|
|
||||||
// The IDL interface allows to toggle the "find entire words" search mode
|
|
||||||
// by calling `SetEntireWord()`.
|
|
||||||
// Internally, it is possible to specify word boundaries at the beginning
|
|
||||||
// and/or the end of the search pattern.
|
|
||||||
// `GetEntireWord()` returns true if `mWordStartBounded` and `mWordEndBounded`
|
|
||||||
// are true.
|
|
||||||
void SetWordStartBounded(bool aWordStartBounded) {
|
|
||||||
mWordStartBounded = aWordStartBounded;
|
|
||||||
}
|
|
||||||
void SetWordEndBounded(bool aWordEndBounded) {
|
|
||||||
mWordEndBounded = aWordEndBounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~nsFind() = default;
|
virtual ~nsFind() = default;
|
||||||
|
|
||||||
@@ -49,8 +36,9 @@ class nsFind : public nsIFind {
|
|||||||
bool mCaseSensitive = false;
|
bool mCaseSensitive = false;
|
||||||
bool mMatchDiacritics = false;
|
bool mMatchDiacritics = false;
|
||||||
|
|
||||||
bool mWordStartBounded = false;
|
// Use "find entire words" mode by setting mEntireWord to true; or false to
|
||||||
bool mWordEndBounded = false;
|
// disable "entire words" mode.
|
||||||
|
bool mEntireWord = false;
|
||||||
|
|
||||||
struct State;
|
struct State;
|
||||||
class StateRestorer;
|
class StateRestorer;
|
||||||
|
|||||||
Reference in New Issue
Block a user