зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 6 changesets (bug 1867939) for causing build bustages in Document.h CLOSED TREE
Backed out changeset 0853f9a7a06c (bug 1867939) Backed out changeset 2279e6577c13 (bug 1867939) Backed out changeset 1b3af4b6a27b (bug 1867939) Backed out changeset 2fbe643ca483 (bug 1867939) Backed out changeset 575873406614 (bug 1867939) Backed out changeset 82f99c0210b0 (bug 1867939)
This commit is contained in:
Родитель
42f414b3f1
Коммит
a1bb69acae
|
@ -1494,15 +1494,6 @@ dependencies = [
|
|||
"bitflags 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dom_fragmentdirectives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nsstring",
|
||||
"percent-encoding",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
|
@ -2264,7 +2255,6 @@ dependencies = [
|
|||
"data_storage",
|
||||
"detect_win32k_conflicts",
|
||||
"dom",
|
||||
"dom_fragmentdirectives",
|
||||
"encoding_glue",
|
||||
"fallible_collections",
|
||||
"fluent",
|
||||
|
|
|
@ -704,7 +704,6 @@ class PageStyleActor extends Actor {
|
|||
case "::first-line":
|
||||
case "::selection":
|
||||
case "::highlight":
|
||||
case "::target-text":
|
||||
return true;
|
||||
case "::marker":
|
||||
return this._nodeIsListItem(node);
|
||||
|
|
|
@ -172,8 +172,6 @@
|
|||
#include "mozilla/dom/FeaturePolicy.h"
|
||||
#include "mozilla/dom/FeaturePolicyUtils.h"
|
||||
#include "mozilla/dom/FontFaceSet.h"
|
||||
#include "mozilla/dom/FragmentDirective.h"
|
||||
#include "mozilla/dom/fragmentdirectives_ffi_generated.h"
|
||||
#include "mozilla/dom/FromParser.h"
|
||||
#include "mozilla/dom/HighlightRegistry.h"
|
||||
#include "mozilla/dom/HTMLAllCollection.h"
|
||||
|
@ -2486,7 +2484,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
|
|||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFragmentDirective)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
|
||||
|
||||
// Traverse all Document nsCOMPtrs.
|
||||
|
@ -2634,7 +2631,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
|
|||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFragmentDirective)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
|
||||
|
@ -4069,21 +4065,6 @@ void Document::StopDocumentLoad() {
|
|||
void Document::SetDocumentURI(nsIURI* aURI) {
|
||||
nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
|
||||
mDocumentURI = aURI;
|
||||
// This loosely implements §3.4.1 of Text Fragments
|
||||
// https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
|
||||
// Unlike specified in the spec, the fragment directive is not stripped from
|
||||
// the URL in the session history entry. Instead it is removed when the URL is
|
||||
// set in the `Document`. Also, instead of storing the `uninvokedDirective` in
|
||||
// `Document` as mentioned in the spec, the extracted directives are moved to
|
||||
// the `FragmentDirective` object which deals with finding the ranges to
|
||||
// highlight in `ScrollToRef()`.
|
||||
// XXX(:jjaschke): This is only a temporary solution.
|
||||
// https://bugzil.la/1881429 is filed for revisiting this.
|
||||
nsTArray<TextDirective> textDirectives;
|
||||
FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(
|
||||
mDocumentURI, &textDirectives);
|
||||
FragmentDirective()->SetTextDirectives(std::move(textDirectives));
|
||||
|
||||
nsIURI* newBase = GetDocBaseURI();
|
||||
|
||||
mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
|
||||
|
@ -13104,29 +13085,25 @@ void Document::SetScrollToRef(nsIURI* aDocumentURI) {
|
|||
|
||||
// https://html.spec.whatwg.org/#scrolling-to-a-fragment
|
||||
void Document::ScrollToRef() {
|
||||
if (mScrolledToRefAlready) {
|
||||
RefPtr<PresShell> presShell = GetPresShell();
|
||||
if (presShell) {
|
||||
presShell->ScrollToAnchor();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If fragment is the empty string, then return the special value top of
|
||||
// the document.
|
||||
if (mScrollToRef.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<PresShell> presShell = GetPresShell();
|
||||
if (!presShell) {
|
||||
return;
|
||||
}
|
||||
if (mScrolledToRefAlready) {
|
||||
presShell->ScrollToAnchor();
|
||||
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);
|
||||
|
||||
// 2. If fragment is the empty string and no text directives have been
|
||||
// scrolled to, then return the special value top of the document.
|
||||
if (didScrollToTextFragment || mScrollToRef.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 3. Let potentialIndicatedElement be the result of finding a potential
|
||||
// indicated element given document and fragment.
|
||||
NS_ConvertUTF8toUTF16 ref(mScrollToRef);
|
||||
|
@ -19084,13 +19061,6 @@ HighlightRegistry& Document::HighlightRegistry() {
|
|||
return *mHighlightRegistry;
|
||||
}
|
||||
|
||||
FragmentDirective* Document::FragmentDirective() {
|
||||
if (!mFragmentDirective) {
|
||||
mFragmentDirective = MakeRefPtr<class FragmentDirective>(this);
|
||||
}
|
||||
return mFragmentDirective;
|
||||
}
|
||||
|
||||
RadioGroupContainer& Document::OwnedRadioGroupContainer() {
|
||||
if (!mRadioGroupContainer) {
|
||||
mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
|
||||
|
|
|
@ -244,7 +244,6 @@ class EventListener;
|
|||
struct FailedCertSecurityInfo;
|
||||
class FeaturePolicy;
|
||||
class FontFaceSet;
|
||||
class FragmentDirective;
|
||||
class FrameRequestCallback;
|
||||
class ImageTracker;
|
||||
class HighlightRegistry;
|
||||
|
@ -4092,13 +4091,6 @@ class Document : public nsINode,
|
|||
*/
|
||||
class HighlightRegistry& HighlightRegistry();
|
||||
|
||||
/**
|
||||
* @brief Returns the `FragmentDirective` object which contains information
|
||||
* and functionality to extract or create text directives.
|
||||
* Guaranteed to be non-null.
|
||||
*/
|
||||
FragmentDirective* FragmentDirective();
|
||||
|
||||
bool ShouldResistFingerprinting(RFPTarget aTarget) const;
|
||||
bool IsInPrivateBrowsing() const;
|
||||
|
||||
|
@ -5380,7 +5372,6 @@ class Document : public nsINode,
|
|||
nsTArray<CanvasUsage> mCanvasUsage;
|
||||
uint64_t mLastCanvasUsage = 0;
|
||||
|
||||
RefPtr<class FragmentDirective> mFragmentDirective;
|
||||
UniquePtr<RadioGroupContainer> mRadioGroupContainer;
|
||||
|
||||
public:
|
||||
|
|
|
@ -1,879 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FragmentDirective.h"
|
||||
#include <cstdint>
|
||||
#include "RangeBoundary.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "Document.h"
|
||||
#include "mozilla/dom/FragmentDirectiveBinding.h"
|
||||
#include "mozilla/dom/FragmentOrElement.h"
|
||||
#include "mozilla/dom/NodeBinding.h"
|
||||
#include "mozilla/dom/Text.h"
|
||||
#include "mozilla/intl/WordBreaker.h"
|
||||
#include "nsComputedDOMStyle.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDOMAttributeMap.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsICSSDeclaration.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsIURIMutator.h"
|
||||
#include "nsRange.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
static LazyLogModule sFragmentDirectiveLog("FragmentDirective");
|
||||
|
||||
/** Converts a `TextDirective` into a percent-encoded string. */
|
||||
nsCString ToString(const TextDirective& aTextDirective) {
|
||||
nsCString str;
|
||||
create_text_directive(&aTextDirective, &str);
|
||||
return str;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FragmentDirective, mDocument)
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(FragmentDirective)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(FragmentDirective)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FragmentDirective)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
FragmentDirective::FragmentDirective(Document* aDocument)
|
||||
: mDocument(aDocument) {}
|
||||
|
||||
JSObject* FragmentDirective::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return FragmentDirective_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(
|
||||
nsCOMPtr<nsIURI>& aURI, nsTArray<TextDirective>* aTextDirectives) {
|
||||
if (!aURI || !StaticPrefs::dom_text_fragments_enabled()) {
|
||||
return;
|
||||
}
|
||||
bool hasRef = false;
|
||||
aURI->GetHasRef(&hasRef);
|
||||
if (!hasRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoCString hash;
|
||||
aURI->GetRef(hash);
|
||||
|
||||
ParsedFragmentDirectiveResult fragmentDirective;
|
||||
const bool hasRemovedFragmentDirective =
|
||||
parse_fragment_directive(&hash, &fragmentDirective);
|
||||
if (!hasRemovedFragmentDirective) {
|
||||
return;
|
||||
}
|
||||
Unused << NS_MutateURI(aURI)
|
||||
.SetRef(fragmentDirective.url_without_fragment_directive)
|
||||
.Finalize(aURI);
|
||||
if (aTextDirectives) {
|
||||
aTextDirectives->SwapElements(fragmentDirective.text_directives);
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<nsRange>> FragmentDirective::FindTextFragmentsInDocument() {
|
||||
MOZ_ASSERT(mDocument);
|
||||
mDocument->FlushPendingNotifications(FlushType::Frames);
|
||||
nsTArray<RefPtr<nsRange>> textDirectiveRanges;
|
||||
for (const TextDirective& textDirective : mUninvokedTextDirectives) {
|
||||
if (RefPtr<nsRange> range = FindRangeForTextDirective(textDirective)) {
|
||||
textDirectiveRanges.AppendElement(range);
|
||||
}
|
||||
}
|
||||
mUninvokedTextDirectives.Clear();
|
||||
return textDirectiveRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determine if `aNode` should be considered when traversing the DOM.
|
||||
*
|
||||
* A node is "search invisible" if it is an element in the HTML namespace and
|
||||
* 1. The computed value of its `display` property is `none`
|
||||
* 2. It serializes as void
|
||||
* 3. It is one of the following types:
|
||||
* - HTMLIFrameElement
|
||||
* - HTMLImageElement
|
||||
* - HTMLMeterElement
|
||||
* - HTMLObjectElement
|
||||
* - HTMLProgressElement
|
||||
* - HTMLStyleElement
|
||||
* - HTMLScriptElement
|
||||
* - HTMLVideoElement
|
||||
* - HTMLAudioElement
|
||||
* 4. It is a `select` element whose `multiple` content attribute is absent
|
||||
*
|
||||
* see https://wicg.github.io/scroll-to-text-fragment/#search-invisible
|
||||
*/
|
||||
bool NodeIsSearchInvisible(nsINode& aNode) {
|
||||
if (!aNode.IsElement()) {
|
||||
return false;
|
||||
}
|
||||
// 2. If the node serializes as void.
|
||||
nsAtom* nodeNameAtom = aNode.NodeInfo()->NameAtom();
|
||||
if (FragmentOrElement::IsHTMLVoid(nodeNameAtom)) {
|
||||
return true;
|
||||
}
|
||||
// 3. Is any of the following types: HTMLIFrameElement, HTMLImageElement,
|
||||
// HTMLMeterElement, HTMLObjectElement, HTMLProgressElement, HTMLStyleElement,
|
||||
// HTMLScriptElement, HTMLVideoElement, HTMLAudioElement
|
||||
if (aNode.IsAnyOfHTMLElements(
|
||||
nsGkAtoms::iframe, nsGkAtoms::image, nsGkAtoms::meter,
|
||||
nsGkAtoms::object, nsGkAtoms::progress, nsGkAtoms::style,
|
||||
nsGkAtoms::script, nsGkAtoms::video, nsGkAtoms::audio)) {
|
||||
return true;
|
||||
}
|
||||
// 4. Is a select element whose multiple content attribute is absent.
|
||||
if (aNode.IsHTMLElement(nsGkAtoms::select)) {
|
||||
return aNode.GetAttributes()->GetNamedItem(u"multiple"_ns) == nullptr;
|
||||
}
|
||||
// This is tested last because it's the most expensive check.
|
||||
// 1. The computed value of its 'display' property is 'none'.
|
||||
const Element* nodeAsElement = Element::FromNode(aNode);
|
||||
const RefPtr<const ComputedStyle> computedStyle =
|
||||
nsComputedDOMStyle::GetComputedStyleNoFlush(nodeAsElement);
|
||||
return !computedStyle ||
|
||||
computedStyle->StyleDisplay()->mDisplay == StyleDisplay::None;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if `aNode` has block-level display.
|
||||
* A node has block-level display if it is an element and the computed value
|
||||
* of its display property is any of
|
||||
* - block
|
||||
* - table
|
||||
* - flow-root
|
||||
* - grid
|
||||
* - flex
|
||||
* - list-item
|
||||
*
|
||||
* See https://wicg.github.io/scroll-to-text-fragment/#has-block-level-display
|
||||
*/
|
||||
bool NodeHasBlockLevelDisplay(nsINode& aNode) {
|
||||
if (!aNode.IsElement()) {
|
||||
return false;
|
||||
}
|
||||
const Element* nodeAsElement = Element::FromNode(aNode);
|
||||
const RefPtr<const ComputedStyle> computedStyle =
|
||||
nsComputedDOMStyle::GetComputedStyleNoFlush(nodeAsElement);
|
||||
if (!computedStyle) {
|
||||
return false;
|
||||
}
|
||||
const StyleDisplay& styleDisplay = computedStyle->StyleDisplay()->mDisplay;
|
||||
return styleDisplay == StyleDisplay::Block ||
|
||||
styleDisplay == StyleDisplay::Table ||
|
||||
styleDisplay == StyleDisplay::FlowRoot ||
|
||||
styleDisplay == StyleDisplay::Grid ||
|
||||
styleDisplay == StyleDisplay::Flex || styleDisplay.IsListItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the Block Ancestor For `aNode`.
|
||||
*
|
||||
* see https://wicg.github.io/scroll-to-text-fragment/#nearest-block-ancestor
|
||||
*/
|
||||
nsINode* GetBlockAncestorForNode(nsINode* aNode) {
|
||||
// 1. Let curNode be node.
|
||||
RefPtr<nsINode> curNode = aNode;
|
||||
// 2. While curNode is non-null
|
||||
while (curNode) {
|
||||
// 2.1. If curNode is not a Text node and it has block-level display then
|
||||
// return curNode.
|
||||
if (!curNode->IsText() && NodeHasBlockLevelDisplay(*curNode)) {
|
||||
return curNode;
|
||||
}
|
||||
// 2.2. Otherwise, set curNode to curNode’s parent.
|
||||
curNode = curNode->GetParentNode();
|
||||
}
|
||||
// 3.Return node’s node document's document element.
|
||||
return aNode->GetOwnerDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if `aNode` is part of a non-searchable subtree.
|
||||
*
|
||||
* A node is part of a non-searchable subtree if it is or has a shadow-including
|
||||
* ancestor that is search invisible.
|
||||
*
|
||||
* see https://wicg.github.io/scroll-to-text-fragment/#non-searchable-subtree
|
||||
*/
|
||||
bool NodeIsPartOfNonSearchableSubTree(nsINode& aNode) {
|
||||
nsINode* node = &aNode;
|
||||
do {
|
||||
if (NodeIsSearchInvisible(*node)) {
|
||||
return true;
|
||||
}
|
||||
} while ((node = node->GetParentOrShadowHostNode()));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return true if `aNode` is a visible Text node.
|
||||
*
|
||||
* A node is a visible text node if it is a Text node, the computed value of
|
||||
* its parent element's visibility property is visible, and it is being
|
||||
* rendered.
|
||||
*
|
||||
* see https://wicg.github.io/scroll-to-text-fragment/#visible-text-node
|
||||
*/
|
||||
bool NodeIsVisibleTextNode(const nsINode& aNode) {
|
||||
const Text* text = Text::FromNode(aNode);
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
const nsIFrame* frame = text->GetPrimaryFrame();
|
||||
return frame && frame->StyleVisibility()->IsVisible();
|
||||
}
|
||||
|
||||
enum class TextScanDirection { Left = -1, Right = 1 };
|
||||
|
||||
/**
|
||||
* @brief Tests if there is whitespace at the given position and direction.
|
||||
*
|
||||
* This algorithm tests for whitespaces and ` ` at `aPos`.
|
||||
* It returns the size of the whitespace found at the position, i.e. 5/6 for
|
||||
* ` /;` and 1 otherwise.
|
||||
*
|
||||
* This function follows a subsection of this section of the spec, but has been
|
||||
* adapted to be able to scan in both directions:
|
||||
* https://wicg.github.io/scroll-to-text-fragment/#next-non-whitespace-position
|
||||
*/
|
||||
uint32_t IsWhitespaceAtPosition(nsString& aText, uint32_t aPos,
|
||||
TextScanDirection aDirection) {
|
||||
if (aText.Length() == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (aDirection == TextScanDirection::Right) {
|
||||
if (aText.Length() > (aPos + 5)) {
|
||||
if (Substring(aText, aPos, 5).Equals(u" ")) {
|
||||
return aText.Length() > (aPos + 6) && aText.CharAt(aPos + 6) == u';'
|
||||
? 6
|
||||
: 5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (aPos > 6 && Substring(aText, aPos - 6, 6).Equals(u" ")) {
|
||||
return 6;
|
||||
}
|
||||
if (aPos > 5 && Substring(aText, aPos - 5, 5).Equals(u" ")) {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
return uint32_t(IsSpaceCharacter(aText.CharAt(aPos)));
|
||||
}
|
||||
|
||||
/** Advances the start of `aRange` to the next non-whitespace position.
|
||||
* The function follows this section of the spec:
|
||||
* https://wicg.github.io/scroll-to-text-fragment/#next-non-whitespace-position
|
||||
*/
|
||||
void AdvanceStartToNextNonWhitespacePosition(nsRange& aRange) {
|
||||
// 1. While range is not collapsed:
|
||||
while (!aRange.Collapsed()) {
|
||||
// 1.1. Let node be range's start node.
|
||||
RefPtr<nsINode> node = aRange.GetStartContainer();
|
||||
MOZ_ASSERT(node);
|
||||
// 1.2. Let offset be range's start offset.
|
||||
const uint32_t offset = aRange.StartOffset();
|
||||
// 1.3. If node is part of a non-searchable subtree or if node is not a
|
||||
// visible text node or if offset is equal to node's length then:
|
||||
if (NodeIsPartOfNonSearchableSubTree(*node) ||
|
||||
!NodeIsVisibleTextNode(*node) || offset == node->Length()) {
|
||||
// 1.3.1. Set range's start node to the next node, in shadow-including
|
||||
// tree order.
|
||||
// 1.3.2. Set range's start offset to 0.
|
||||
if (NS_FAILED(aRange.SetStart(node->GetNextNode(), 0))) {
|
||||
return;
|
||||
}
|
||||
// 1.3.3. Continue.
|
||||
continue;
|
||||
}
|
||||
const Text* text = Text::FromNode(node);
|
||||
nsAutoString textData;
|
||||
text->GetData(textData);
|
||||
// These steps are moved to `IsWhitespaceAtPosition()`.
|
||||
// 1.4. If the substring data of node at offset offset and count 6 is equal
|
||||
// to the string " " then:
|
||||
// 1.4.1. Add 6 to range’s start offset.
|
||||
// 1.5. Otherwise, if the substring data of node at offset offset and count
|
||||
// 5 is equal to the string " " then:
|
||||
// 1.5.1. Add 5 to range’s start offset.
|
||||
// 1.6. Otherwise:
|
||||
// 1.6.1 Let cp be the code point at the offset index in node’s data.
|
||||
// 1.6.2 If cp does not have the White_Space property set, return.
|
||||
// 1.6.3 Add 1 to range’s start offset.
|
||||
const uint32_t whitespace =
|
||||
IsWhitespaceAtPosition(textData, offset, TextScanDirection::Right);
|
||||
if (whitespace == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
aRange.SetStart(node, offset + whitespace);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Moves `aRangeBoundary` one word in `aDirection`.
|
||||
*
|
||||
* Word boundaries are determined using `intl::WordBreaker::FindWord()`.
|
||||
*
|
||||
*
|
||||
* @param aRangeBoundary[in] The range boundary that should be moved.
|
||||
* Must be set and valid.
|
||||
* @param aDirection[in] The direction into which to move.
|
||||
* @return A new `RangeBoundary` which is moved to the next word.
|
||||
*/
|
||||
RangeBoundary MoveRangeBoundaryOneWord(const RangeBoundary& aRangeBoundary,
|
||||
TextScanDirection aDirection) {
|
||||
MOZ_ASSERT(aRangeBoundary.IsSetAndValid());
|
||||
RefPtr<nsINode> curNode = aRangeBoundary.Container();
|
||||
uint32_t offset = *aRangeBoundary.Offset(
|
||||
RangeBoundary::OffsetFilter::kValidOrInvalidOffsets);
|
||||
|
||||
const int offsetIncrement = int(aDirection);
|
||||
// Get the text node of the start of the range and the offset.
|
||||
// This is the current position of the start of the range.
|
||||
nsAutoString text;
|
||||
if (NodeIsVisibleTextNode(*curNode)) {
|
||||
const Text* textNode = Text::FromNode(curNode);
|
||||
textNode->GetData(text);
|
||||
|
||||
// Assuming that the current position might not be at a word boundary,
|
||||
// advance to the word boundary at word begin/end.
|
||||
if (!IsWhitespaceAtPosition(text, offset, aDirection)) {
|
||||
const intl::WordRange wordRange =
|
||||
intl::WordBreaker::FindWord(text, offset);
|
||||
if (aDirection == TextScanDirection::Right &&
|
||||
offset != wordRange.mBegin) {
|
||||
offset = wordRange.mEnd;
|
||||
} else if (aDirection == TextScanDirection::Left &&
|
||||
offset != wordRange.mEnd) {
|
||||
// The additional -1 is necessary to move to offset to *before* the
|
||||
// start of the word.
|
||||
offset = wordRange.mBegin - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now, skip any whitespace, so that `offset` points to the word boundary of
|
||||
// the next word (which is the one this algorithm actually aims to move over).
|
||||
while (curNode) {
|
||||
if (!NodeIsVisibleTextNode(*curNode) || NodeIsSearchInvisible(*curNode) ||
|
||||
offset >= curNode->Length()) {
|
||||
curNode = aDirection == TextScanDirection::Left ? curNode->GetPrevNode()
|
||||
: curNode->GetNextNode();
|
||||
if (!curNode) {
|
||||
break;
|
||||
}
|
||||
offset =
|
||||
aDirection == TextScanDirection::Left ? curNode->Length() - 1 : 0;
|
||||
if (const Text* textNode = Text::FromNode(curNode)) {
|
||||
textNode->GetData(text);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (const uint32_t whitespace =
|
||||
IsWhitespaceAtPosition(text, offset, aDirection)) {
|
||||
offset += offsetIncrement * whitespace;
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, the caret has been moved to the next non-whitespace
|
||||
// position.
|
||||
// find word boundaries at the current position
|
||||
const intl::WordRange wordRange = intl::WordBreaker::FindWord(text, offset);
|
||||
offset = aDirection == TextScanDirection::Left ? wordRange.mBegin
|
||||
: wordRange.mEnd;
|
||||
|
||||
return {curNode, offset};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<nsRange> FragmentDirective::FindRangeForTextDirective(
|
||||
const TextDirective& aTextDirective) {
|
||||
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,
|
||||
// document’s length)
|
||||
ErrorResult rv;
|
||||
RefPtr<nsRange> searchRange =
|
||||
nsRange::Create(mDocument, 0, mDocument, mDocument->Length(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2. While searchRange is not collapsed:
|
||||
while (!searchRange->Collapsed()) {
|
||||
// 2.1. Let potentialMatch be null.
|
||||
RefPtr<nsRange> potentialMatch;
|
||||
// 2.2. If parsedValues’s prefix is not null:
|
||||
if (!aTextDirective.prefix.IsEmpty()) {
|
||||
// 2.2.1. Let prefixMatch be the the result of running the find a string
|
||||
// in range steps with query parsedValues’s prefix, searchRange
|
||||
// searchRange, wordStartBounded true and wordEndBounded false.
|
||||
RefPtr<nsRange> prefixMatch =
|
||||
FindStringInRange(searchRange, aTextDirective.prefix, true, false);
|
||||
// 2.2.2. If prefixMatch is null, return null.
|
||||
if (!prefixMatch) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.2.3. Set searchRange’s start to the first boundary point after
|
||||
// prefixMatch’s start
|
||||
const RangeBoundary boundaryPoint = MoveRangeBoundaryOneWord(
|
||||
{prefixMatch->GetStartContainer(), prefixMatch->StartOffset()},
|
||||
TextScanDirection::Right);
|
||||
if (!boundaryPoint.IsSetAndValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
searchRange->SetStart(boundaryPoint.AsRaw(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 2.2.4. Let matchRange be a range whose start is prefixMatch’s end and
|
||||
// end is searchRange’s end.
|
||||
RefPtr<nsRange> matchRange = nsRange::Create(
|
||||
prefixMatch->GetEndContainer(), prefixMatch->EndOffset(),
|
||||
searchRange->GetEndContainer(), searchRange->EndOffset(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.2.5. Advance matchRange’s start to the next non-whitespace position.
|
||||
AdvanceStartToNextNonWhitespacePosition(*matchRange);
|
||||
// 2.2.6. If matchRange is collapsed return null.
|
||||
// (This can happen if prefixMatch’s end or its subsequent non-whitespace
|
||||
// position is at the end of the document.)
|
||||
if (matchRange->Collapsed()) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.2.7. Assert: matchRange’s start node is a Text node.
|
||||
// (matchRange’s start now points to the next non-whitespace text data
|
||||
// following a matched prefix.)
|
||||
MOZ_ASSERT(matchRange->GetStartContainer()->IsText());
|
||||
|
||||
// 2.2.8. Let mustEndAtWordBoundary be true if parsedValues’s end is
|
||||
// non-null or parsedValues’s suffix is null, false otherwise.
|
||||
const bool mustEndAtWordBoundary =
|
||||
!aTextDirective.end.IsEmpty() || aTextDirective.suffix.IsEmpty();
|
||||
// 2.2.9. Set potentialMatch to the result of running the find a string in
|
||||
// range steps with query parsedValues’s start, searchRange matchRange,
|
||||
// wordStartBounded false, and wordEndBounded mustEndAtWordBoundary.
|
||||
potentialMatch = FindStringInRange(matchRange, aTextDirective.start,
|
||||
false, mustEndAtWordBoundary);
|
||||
// 2.2.10. If potentialMatch is null, return null.
|
||||
if (!potentialMatch) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.2.11. If potentialMatch’s start is not matchRange’s start, then
|
||||
// continue.
|
||||
// (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
|
||||
// of prefix.)
|
||||
if (potentialMatch->GetStartContainer() !=
|
||||
matchRange->GetStartContainer()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 2.3. Otherwise:
|
||||
else {
|
||||
// 2.3.1. Let mustEndAtWordBoundary be true if parsedValues’s end is
|
||||
// non-null or parsedValues’s suffix is null, false otherwise.
|
||||
const bool mustEndAtWordBoundary =
|
||||
!aTextDirective.end.IsEmpty() || aTextDirective.suffix.IsEmpty();
|
||||
// 2.3.2. Set potentialMatch to the result of running the find a string in
|
||||
// range steps with query parsedValues’s start, searchRange searchRange,
|
||||
// wordStartBounded true, and wordEndBounded mustEndAtWordBoundary.
|
||||
potentialMatch = FindStringInRange(searchRange, aTextDirective.start,
|
||||
true, mustEndAtWordBoundary);
|
||||
// 2.3.3. If potentialMatch is null, return null.
|
||||
if (!potentialMatch) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.3.4. Set searchRange’s start to the first boundary point after
|
||||
// potentialMatch’s start
|
||||
RangeBoundary newRangeBoundary = MoveRangeBoundaryOneWord(
|
||||
{potentialMatch->GetStartContainer(), potentialMatch->StartOffset()},
|
||||
TextScanDirection::Right);
|
||||
if (!newRangeBoundary.IsSetAndValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
searchRange->SetStart(newRangeBoundary.AsRaw(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// 2.4. Let rangeEndSearchRange be a range whose start is potentialMatch’s
|
||||
// end and whose end is searchRange’s end.
|
||||
RefPtr<nsRange> rangeEndSearchRange = nsRange::Create(
|
||||
potentialMatch->GetEndContainer(), potentialMatch->EndOffset(),
|
||||
searchRange->GetEndContainer(), searchRange->EndOffset(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.5. While rangeEndSearchRange is not collapsed:
|
||||
while (!rangeEndSearchRange->Collapsed()) {
|
||||
// 2.5.1. If parsedValues’s end item is non-null, then:
|
||||
if (!aTextDirective.end.IsEmpty()) {
|
||||
// 2.5.1.1. Let mustEndAtWordBoundary be true if parsedValues’s suffix
|
||||
// is null, false otherwise.
|
||||
const bool mustEndAtWordBoundary = aTextDirective.suffix.IsEmpty();
|
||||
// 2.5.1.2. Let endMatch be the result of running the find a string in
|
||||
// range steps with query parsedValues’s end, searchRange
|
||||
// rangeEndSearchRange, wordStartBounded true, and wordEndBounded
|
||||
// mustEndAtWordBoundary.
|
||||
RefPtr<nsRange> endMatch =
|
||||
FindStringInRange(rangeEndSearchRange, aTextDirective.end, true,
|
||||
mustEndAtWordBoundary);
|
||||
// 2.5.1.3. If endMatch is null then return null.
|
||||
if (!endMatch) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.5.1.4. Set potentialMatch’s end to endMatch’s end.
|
||||
potentialMatch->SetEnd(endMatch->GetEndContainer(),
|
||||
endMatch->EndOffset());
|
||||
}
|
||||
// 2.5.2. Assert: potentialMatch is non-null, not collapsed and represents
|
||||
// a range exactly containing an instance of matching text.
|
||||
MOZ_ASSERT(potentialMatch && !potentialMatch->Collapsed());
|
||||
|
||||
// 2.5.3. If parsedValues’s suffix is null, return potentialMatch.
|
||||
if (aTextDirective.suffix.IsEmpty()) {
|
||||
return potentialMatch;
|
||||
}
|
||||
// 2.5.4. Let suffixRange be a range with start equal to potentialMatch’s
|
||||
// end and end equal to searchRange’s end.
|
||||
RefPtr<nsRange> suffixRange = nsRange::Create(
|
||||
potentialMatch->GetEndContainer(), potentialMatch->EndOffset(),
|
||||
searchRange->GetEndContainer(), searchRange->EndOffset(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.5.5. Advance suffixRange's start to the next non-whitespace position.
|
||||
AdvanceStartToNextNonWhitespacePosition(*suffixRange);
|
||||
|
||||
// 2.5.6. Let suffixMatch be result of running the find a string in range
|
||||
// steps with query parsedValue's suffix, searchRange suffixRange,
|
||||
// wordStartBounded false, and wordEndBounded true.
|
||||
RefPtr<nsRange> suffixMatch =
|
||||
FindStringInRange(suffixRange, aTextDirective.suffix, false, true);
|
||||
|
||||
// 2.5.7. If suffixMatch is null, return null.
|
||||
// (If the suffix doesn't appear in the remaining text of the document,
|
||||
// there's no possible way to make a match.)
|
||||
if (!suffixMatch) {
|
||||
return nullptr;
|
||||
}
|
||||
// 2.5.8. If suffixMatch's start is suffixRange's start, return
|
||||
// potentialMatch.
|
||||
if (suffixMatch->GetStartContainer() ==
|
||||
suffixRange->GetStartContainer() &&
|
||||
suffixMatch->StartOffset() == suffixRange->StartOffset()) {
|
||||
return potentialMatch;
|
||||
}
|
||||
// 2.5.9. If parsedValue's end item is null then break;
|
||||
// (If this is an exact match and the suffix doesn’t match, start
|
||||
// searching for the next range start by breaking out of this loop without
|
||||
// rangeEndSearchRange being collapsed. If we’re looking for a range
|
||||
// match, we’ll continue iterating this inner loop since the range start
|
||||
// will already be correct.)
|
||||
if (aTextDirective.end.IsEmpty()) {
|
||||
break;
|
||||
}
|
||||
// 2.5.10. Set rangeEndSearchRange's start to potentialMatch's end.
|
||||
// (Otherwise, it is possible that we found the correct range start, but
|
||||
// not the correct range end. Continue the inner loop to keep searching
|
||||
// for another matching instance of rangeEnd.)
|
||||
rangeEndSearchRange->SetStart(potentialMatch->GetEndContainer(),
|
||||
potentialMatch->EndOffset());
|
||||
}
|
||||
// 2.6. If rangeEndSearchRange is collapsed then:
|
||||
if (rangeEndSearchRange->Collapsed()) {
|
||||
// 2.6.1. Assert parsedValue's end item is non-null.
|
||||
// (This can only happen for range matches due to the break for exact
|
||||
// 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
|
||||
// to make a match.)
|
||||
// XXX(:jjaschke): should this really assert?
|
||||
MOZ_ASSERT(!aTextDirective.end.IsEmpty());
|
||||
}
|
||||
}
|
||||
// 3. Return null.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convenience function that returns true if the given position in a
|
||||
* string is a word boundary.
|
||||
*
|
||||
* This is a thin wrapper around the `WordBreaker::FindWord()` function.
|
||||
*
|
||||
* @param aText The text input.
|
||||
* @param aPosition The position to check.
|
||||
* @return true if there is a word boundary at `aPosition`.
|
||||
* @return false otherwise.
|
||||
*/
|
||||
bool IsAtWordBoundary(const nsAString& aText, uint32_t aPosition) {
|
||||
const intl::WordRange wordRange =
|
||||
intl::WordBreaker::FindWord(aText, aPosition);
|
||||
return wordRange.mBegin == aPosition || wordRange.mEnd == aPosition;
|
||||
}
|
||||
|
||||
enum class IsEndIndex : bool { No, Yes };
|
||||
RangeBoundary GetBoundaryPointAtIndex(
|
||||
uint32_t aIndex, const nsTArray<RefPtr<Text>>& aTextNodeList,
|
||||
IsEndIndex aIsEndIndex) {
|
||||
// 1. Let counted be 0.
|
||||
uint32_t counted = 0;
|
||||
// 2. For each curNode of nodes:
|
||||
for (Text* curNode : aTextNodeList) {
|
||||
// 2.1. Let nodeEnd be counted + curNode’s length.
|
||||
uint32_t nodeEnd = counted + curNode->Length();
|
||||
// 2.2. If isEnd is true, add 1 to nodeEnd.
|
||||
if (aIsEndIndex == IsEndIndex::Yes) {
|
||||
++nodeEnd;
|
||||
}
|
||||
// 2.3. If nodeEnd is greater than index then:
|
||||
if (nodeEnd > aIndex) {
|
||||
// 2.3.1. Return the boundary point (curNode, index − counted).
|
||||
return RangeBoundary(curNode->AsNode(), aIndex - counted);
|
||||
}
|
||||
// 2.4. Increment counted by curNode’s length.
|
||||
counted += curNode->Length();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<nsRange> FindRangeFromNodeList(
|
||||
nsRange* aSearchRange, const nsAString& aQuery,
|
||||
const nsTArray<RefPtr<Text>>& aTextNodeList, bool aWordStartBounded,
|
||||
bool aWordEndBounded) {
|
||||
// 1. Let searchBuffer be the concatenation of the data of each item in nodes.
|
||||
// XXX(:jjaschke): There's an open issue here that deals with what
|
||||
// data is supposed to be (text data vs. rendered text)
|
||||
// https://github.com/WICG/scroll-to-text-fragment/issues/98
|
||||
uint32_t bufferLength = 0;
|
||||
for (const Text* text : aTextNodeList) {
|
||||
bufferLength += text->Length();
|
||||
}
|
||||
// bail out if the search query is longer than the text data.
|
||||
if (bufferLength < aQuery.Length()) {
|
||||
return nullptr;
|
||||
}
|
||||
nsAutoString searchBuffer;
|
||||
searchBuffer.SetCapacity(bufferLength);
|
||||
for (Text* text : aTextNodeList) {
|
||||
text->AppendTextTo(searchBuffer);
|
||||
}
|
||||
// 2. Let searchStart be 0.
|
||||
// 3. If the first item in nodes is searchRange’s start node then set
|
||||
// searchStart to searchRange’s start offset.
|
||||
uint32_t searchStart =
|
||||
aTextNodeList.SafeElementAt(0) == aSearchRange->GetStartContainer()
|
||||
? aSearchRange->StartOffset()
|
||||
: 0;
|
||||
|
||||
// 4. Let start and end be boundary points, initially null.
|
||||
RangeBoundary start, end;
|
||||
// 5. Let matchIndex be null.
|
||||
// "null" here doesn't mean 0, instead "not set". 0 would be a valid index.
|
||||
// Therefore, "null" is represented by the value -1.
|
||||
int32_t matchIndex = -1;
|
||||
|
||||
// 6. While matchIndex is null
|
||||
// As explained above, "null" == -1 in this algorithm.
|
||||
while (matchIndex == -1) {
|
||||
// 6.1. Set matchIndex to the index of the first instance of queryString in
|
||||
// searchBuffer, starting at searchStart. The string search must be
|
||||
// performed using a base character comparison, or the primary level, as
|
||||
// defined in [UTS10].
|
||||
// [UTS10]
|
||||
// Ken Whistler; Markus Scherer.Unicode Collation Algorithm.26 August 2022.
|
||||
// Unicode Technical Standard #10.
|
||||
// URL : https://www.unicode.org/reports/tr10/tr10-47.html
|
||||
|
||||
// XXX(:jjaschke): For the initial implementation, a standard case-sensitive
|
||||
// find-in-string is used.
|
||||
// See: https://github.com/WICG/scroll-to-text-fragment/issues/233
|
||||
matchIndex = searchBuffer.Find(aQuery, searchStart);
|
||||
// 6.2. If matchIndex is null, return null.
|
||||
if (matchIndex == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 6.3. Let endIx be matchIndex + queryString’s length.
|
||||
// endIx is the index of the last character in the match + 1.
|
||||
const uint32_t endIx = matchIndex + aQuery.Length();
|
||||
|
||||
// 6.4. Set start to the boundary point result of get boundary point at
|
||||
// index matchIndex run over nodes with isEnd false.
|
||||
start = GetBoundaryPointAtIndex(matchIndex, aTextNodeList, IsEndIndex::No);
|
||||
// 6.5. Set end to the boundary point result of get boundary point at index
|
||||
// endIx run over nodes with isEnd true.
|
||||
end = GetBoundaryPointAtIndex(endIx, aTextNodeList, IsEndIndex::Yes);
|
||||
|
||||
// 6.6. If wordStartBounded is true and matchIndex is not at a word boundary
|
||||
// in searchBuffer, given the language from start’s node as the locale; or
|
||||
// wordEndBounded is true and matchIndex + queryString’s length is not at a
|
||||
// word boundary in searchBuffer, given the language from end’s node as the
|
||||
// locale:
|
||||
if ((aWordStartBounded && !IsAtWordBoundary(searchBuffer, matchIndex)) ||
|
||||
(aWordEndBounded && !IsAtWordBoundary(searchBuffer, endIx))) {
|
||||
// 6.6.1. Set searchStart to matchIndex + 1.
|
||||
searchStart = matchIndex + 1;
|
||||
// 6.6.2. Set matchIndex to null.
|
||||
matchIndex = -1;
|
||||
}
|
||||
}
|
||||
// 7. Let endInset be 0.
|
||||
// 8. If the last item in nodes is searchRange’s end node then set endInset
|
||||
// to (searchRange’s end node's length − searchRange’s end offset)
|
||||
// (endInset is the offset from the last position in the last node in the
|
||||
// reverse direction. Alternatively, it is the length of the node that’s not
|
||||
// included in the range.)
|
||||
uint32_t endInset =
|
||||
aTextNodeList.LastElement() == aSearchRange->GetEndContainer()
|
||||
? aSearchRange->GetEndContainer()->Length() -
|
||||
aSearchRange->EndOffset()
|
||||
: 0;
|
||||
|
||||
// 9. If matchIndex + queryString’s length is greater than searchBuffer’s
|
||||
// length − endInset return null.
|
||||
// (If the match runs past the end of the search range, return null.)
|
||||
if (matchIndex + aQuery.Length() > searchBuffer.Length() - endInset) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 10. Assert: start and end are non-null, valid boundary points in
|
||||
// searchRange.
|
||||
MOZ_ASSERT(start.IsSetAndValid());
|
||||
MOZ_ASSERT(end.IsSetAndValid());
|
||||
|
||||
// 11. Return a range with start start and end end.
|
||||
ErrorResult rv;
|
||||
RefPtr<nsRange> range = nsRange::Create(start, end, rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
RefPtr<nsRange> FragmentDirective::FindStringInRange(nsRange* aSearchRange,
|
||||
const nsAString& aQuery,
|
||||
bool aWordStartBounded,
|
||||
bool aWordEndBounded) {
|
||||
MOZ_ASSERT(aSearchRange);
|
||||
RefPtr<nsRange> searchRange = aSearchRange->CloneRange();
|
||||
// 1. While searchRange is not collapsed
|
||||
while (searchRange && !searchRange->Collapsed()) {
|
||||
// 1.1. Let curNode be searchRange’s start node.
|
||||
RefPtr<nsINode> curNode = searchRange->GetStartContainer();
|
||||
|
||||
// 1.2. If curNode is part of a non-searchable subtree:
|
||||
if (NodeIsPartOfNonSearchableSubTree(*curNode)) {
|
||||
// 1.2.1. Set searchRange’s start node to the next node, in
|
||||
// shadow-including tree order, that isn’t a shadow-including descendant
|
||||
// of curNode.
|
||||
RefPtr<nsINode> next = curNode;
|
||||
while ((next = next->GetNextNode())) {
|
||||
if (!next->IsShadowIncludingInclusiveDescendantOf(curNode)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!next) {
|
||||
return nullptr;
|
||||
}
|
||||
// 1.2.2. Set `searchRange`s `start offset` to 0
|
||||
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);
|
||||
}
|
||||
|
||||
// 2. Return null.
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace mozilla::dom
|
|
@ -1,111 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_FRAGMENTDIRECTIVE_H_
|
||||
#define DOM_FRAGMENTDIRECTIVE_H_
|
||||
|
||||
#include "js/TypeDecls.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
|
||||
#include "mozilla/dom/fragmentdirectives_ffi_generated.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsINode;
|
||||
class nsIURI;
|
||||
class nsRange;
|
||||
namespace mozilla::dom {
|
||||
class Document;
|
||||
class Text;
|
||||
|
||||
/**
|
||||
* @brief The `FragmentDirective` class is the C++ representation of the
|
||||
* `Document.fragmentDirective` webidl property.
|
||||
*
|
||||
* This class also serves as the main interface to interact with the fragment
|
||||
* directive from the C++ side. It allows to find text fragment ranges from a
|
||||
* given list of `TextDirective`s using
|
||||
* `FragmentDirective::FindTextFragmentsInDocument()`.
|
||||
* To avoid Text Directives being applied multiple times, this class implements
|
||||
* the `uninvoked directive` mechanism, which in the spec is defined to be part
|
||||
* of the `Document` [0].
|
||||
*
|
||||
* [0]
|
||||
* https://wicg.github.io/scroll-to-text-fragment/#document-uninvoked-directives
|
||||
*/
|
||||
class FragmentDirective final : public nsISupports, public nsWrapperCache {
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FragmentDirective)
|
||||
|
||||
public:
|
||||
explicit FragmentDirective(Document* aDocument);
|
||||
FragmentDirective(Document* aDocument,
|
||||
nsTArray<TextDirective>&& aTextDirectives)
|
||||
: mDocument(aDocument),
|
||||
mUninvokedTextDirectives(std::move(aTextDirectives)) {}
|
||||
|
||||
protected:
|
||||
~FragmentDirective() = default;
|
||||
|
||||
public:
|
||||
Document* GetParentObject() const { return mDocument; };
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
/**
|
||||
* @brief Sets Text Directives as "uninvoked directive".
|
||||
*/
|
||||
void SetTextDirectives(nsTArray<TextDirective>&& aTextDirectives) {
|
||||
mUninvokedTextDirectives = std::move(aTextDirectives);
|
||||
}
|
||||
|
||||
/** Returns true if there are Text Directives that have not been applied to
|
||||
* the `Document`.
|
||||
*/
|
||||
bool HasUninvokedDirectives() const {
|
||||
return !mUninvokedTextDirectives.IsEmpty();
|
||||
};
|
||||
|
||||
/** Searches for the current uninvoked text directives and creates a range for
|
||||
* each one that is found.
|
||||
*
|
||||
* When this method returns, the uninvoked directives for this document are
|
||||
* cleared.
|
||||
*
|
||||
* This method tries to follow the specification as close as possible in how
|
||||
* to find a matching range for a text directive. However, instead of using
|
||||
* collator-based search, a standard case-insensitive search is used
|
||||
* (`nsString::find()`).
|
||||
*/
|
||||
nsTArray<RefPtr<nsRange>> FindTextFragmentsInDocument();
|
||||
|
||||
/** Utility function which parses the fragment directive and removes it from
|
||||
* the hash of the given URI. This operation happens in-place.
|
||||
*
|
||||
* If aTextDirectives is nullptr, the parsed fragment directive is discarded.
|
||||
*/
|
||||
static void ParseAndRemoveFragmentDirectiveFromFragment(
|
||||
nsCOMPtr<nsIURI>& aURI,
|
||||
nsTArray<TextDirective>* aTextDirectives = nullptr);
|
||||
|
||||
private:
|
||||
RefPtr<nsRange> FindRangeForTextDirective(
|
||||
const TextDirective& aTextDirective);
|
||||
RefPtr<nsRange> FindStringInRange(nsRange* aSearchRange,
|
||||
const nsAString& aQuery,
|
||||
bool aWordStartBounded,
|
||||
bool aWordEndBounded);
|
||||
|
||||
RefPtr<Document> mDocument;
|
||||
nsTArray<TextDirective> mUninvokedTextDirectives;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_FRAGMENTDIRECTIVE_H_
|
|
@ -34,7 +34,6 @@
|
|||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/FragmentDirective.h"
|
||||
#include "mozilla/dom/LocationBinding.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "ReferrerInfo.h"
|
||||
|
@ -106,9 +105,6 @@ nsresult Location::GetURI(nsIURI** aURI, bool aGetInnermostURI) {
|
|||
}
|
||||
|
||||
NS_ASSERTION(uri, "nsJARURI screwed up?");
|
||||
|
||||
// Remove the fragment directive from the url hash.
|
||||
FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(uri);
|
||||
nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(uri);
|
||||
exposableURI.forget(aURI);
|
||||
return NS_OK;
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "dom_fragmentdirectives"
|
||||
version = "0.1.0"
|
||||
authors = ["Jan Jaeschke <jjaschke@mozilla.com>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
nsstring = { path = "../../../xpcom/rust/nsstring/" }
|
||||
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
|
||||
percent-encoding = { version = "2.3.1" }
|
||||
[lib]
|
||||
path = "lib.rs"
|
|
@ -1,15 +0,0 @@
|
|||
header = """/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
|
||||
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
|
||||
"""
|
||||
include_version = true
|
||||
braces = "SameLine"
|
||||
line_length = 100
|
||||
tab_width = 2
|
||||
language = "C++"
|
||||
include_guard = "fragmentdirectives_ffi_generated_h"
|
||||
includes = ["nsStringFwd.h", "nsTArrayForwardDeclare.h"]
|
||||
|
||||
[export.rename]
|
||||
"ThinVec" = "nsTArray"
|
|
@ -1,342 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
use percent_encoding::{percent_decode, percent_encode, NON_ALPHANUMERIC};
|
||||
use std::str;
|
||||
|
||||
/// The `FragmentDirectiveParameter` represents one of
|
||||
/// `[prefix-,]start[,end][,-suffix]` without any surrounding `-` or `,`.
|
||||
///
|
||||
/// The token is stored as percent-decoded string.
|
||||
/// Therefore, interfaces exist to
|
||||
/// - create a `FragmentDirectiveParameter` from a percent-encoded string.
|
||||
/// This function will determine from occurrence and position of a dash
|
||||
/// if the token represents a `prefix`, `suffix` or either `start` or `end`.
|
||||
/// - create a percent-encoded string from the value the token holds.
|
||||
pub enum TextDirectiveParameter {
|
||||
Prefix(String),
|
||||
StartOrEnd(String),
|
||||
Suffix(String),
|
||||
}
|
||||
|
||||
impl TextDirectiveParameter {
|
||||
/// Creates a token from a percent-encoded string.
|
||||
/// Based on position of a dash the correct token type is determined.
|
||||
/// Returns `None` in case of an ill-formed token:
|
||||
/// - starts and ends with a dash (i.e. `-token-`)
|
||||
/// - only consists of a dash (i.e. `-`) or is empty
|
||||
/// - conversion from percent-encoded string to utf8 fails.
|
||||
pub fn from_percent_encoded(token: &[u8]) -> Option<Self> {
|
||||
if token.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let starts_with_dash = *token.first().unwrap() == b'-';
|
||||
let ends_with_dash = *token.last().unwrap() == b'-';
|
||||
if starts_with_dash && ends_with_dash {
|
||||
// `-token-` is not valid.
|
||||
return None;
|
||||
}
|
||||
if token.len() == 1 && starts_with_dash {
|
||||
// `-` is not valid.
|
||||
return None;
|
||||
}
|
||||
// Note: Trimming of the raw strings is currently not mentioned in the spec.
|
||||
// However, it looks as it is implicitly expected.
|
||||
if starts_with_dash {
|
||||
if let Ok(decoded_suffix) = percent_decode(&token[1..]).decode_utf8() {
|
||||
return Some(TextDirectiveParameter::Suffix(String::from(
|
||||
decoded_suffix.trim(),
|
||||
)));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
if ends_with_dash {
|
||||
if let Ok(decoded_prefix) = percent_decode(&token[..token.len() - 1]).decode_utf8() {
|
||||
return Some(TextDirectiveParameter::Prefix(String::from(
|
||||
decoded_prefix.trim(),
|
||||
)));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
if let Ok(decoded_text) = percent_decode(&token).decode_utf8() {
|
||||
return Some(TextDirectiveParameter::StartOrEnd(String::from(
|
||||
decoded_text.trim(),
|
||||
)));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the value of the token as percent-decoded `String`.
|
||||
pub fn value(&self) -> &String {
|
||||
match self {
|
||||
TextDirectiveParameter::Prefix(value) => &value,
|
||||
TextDirectiveParameter::StartOrEnd(value) => &value,
|
||||
TextDirectiveParameter::Suffix(value) => &value,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a percent-encoded string of the token's value.
|
||||
/// This includes placing a dash appropriately
|
||||
/// to indicate whether this token is prefix, suffix or start/end.
|
||||
///
|
||||
/// This method always returns a new object.
|
||||
pub fn to_percent_encoded_string(&self) -> String {
|
||||
let encode = |text: &String| percent_encode(text.as_bytes(), NON_ALPHANUMERIC).to_string();
|
||||
match self {
|
||||
Self::Prefix(text) => encode(text) + "-",
|
||||
Self::StartOrEnd(text) => encode(text),
|
||||
Self::Suffix(text) => {
|
||||
let encoded = encode(text);
|
||||
let mut result = String::with_capacity(encoded.len() + 1);
|
||||
result.push_str("-");
|
||||
result.push_str(&encoded);
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct represents one parsed text directive using Rust types.
|
||||
///
|
||||
/// A text fragment is encoded into a URL fragment like this:
|
||||
/// `text=[prefix-,]start[,end][,-suffix]`
|
||||
///
|
||||
/// The text directive is considered valid if at least `start` is not None.
|
||||
/// (see `Self::is_valid()`).
|
||||
#[derive(Default)]
|
||||
pub struct TextDirective {
|
||||
prefix: Option<TextDirectiveParameter>,
|
||||
start: Option<TextDirectiveParameter>,
|
||||
end: Option<TextDirectiveParameter>,
|
||||
suffix: Option<TextDirectiveParameter>,
|
||||
}
|
||||
impl TextDirective {
|
||||
/// Creates an instance from string parts.
|
||||
/// This function is intended to be used when a fragment directive string should be created.
|
||||
/// Returns `None` if `start` is empty.
|
||||
pub fn from_parts(prefix: String, start: String, end: String, suffix: String) -> Option<Self> {
|
||||
if !start.is_empty() {
|
||||
Some(Self {
|
||||
prefix: if !prefix.is_empty() {
|
||||
Some(TextDirectiveParameter::Prefix(prefix.trim().into()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
start: Some(TextDirectiveParameter::StartOrEnd(start.trim().into())),
|
||||
end: if !end.is_empty() {
|
||||
Some(TextDirectiveParameter::StartOrEnd(end.trim().into()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
suffix: if !suffix.is_empty() {
|
||||
Some(TextDirectiveParameter::Suffix(suffix.trim().into()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an instance from a percent-encoded string
|
||||
/// that originates from a fragment directive.
|
||||
///
|
||||
/// `text_fragment` is supposed to have this format:
|
||||
/// ```
|
||||
/// text=[prefix-,]start[,end][,-suffix]
|
||||
/// ```
|
||||
/// This function returns `None` if `text_fragment`
|
||||
/// does not start with `text=`, it contains 0 or more
|
||||
/// than 4 elements or prefix/suffix/start or end
|
||||
/// occur too many times.
|
||||
/// It also returns `None` if any of the tokens parses to fail.
|
||||
pub fn from_percent_encoded_string(text_directive: &str) -> Option<Self> {
|
||||
// first check if the string starts with `text=`
|
||||
if text_directive.len() < 6 {
|
||||
return None;
|
||||
}
|
||||
if !text_directive.starts_with("text=") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut parsed_text_directive = Self::default();
|
||||
let valid = text_directive[5..]
|
||||
.split(",")
|
||||
// Parse the substrings into `TextDirectiveParameter`s. This will determine
|
||||
// for each substring if it is a Prefix, Suffix or Start/End,
|
||||
// or if it is invalid.
|
||||
.map(|token| TextDirectiveParameter::from_percent_encoded(token.as_bytes()))
|
||||
// populate `parsed_text_directive` and check its validity by inserting the parameters
|
||||
// one by one. Given that the parameters are sorted by their position in the source,
|
||||
// the validity of the text directive can be determined while adding the parameters.
|
||||
.map(|token| match token {
|
||||
Some(TextDirectiveParameter::Prefix(..)) => {
|
||||
if !parsed_text_directive.is_empty() {
|
||||
// `prefix-` must be the first result.
|
||||
return false;
|
||||
}
|
||||
parsed_text_directive.prefix = token;
|
||||
return true;
|
||||
}
|
||||
Some(TextDirectiveParameter::StartOrEnd(..)) => {
|
||||
if parsed_text_directive.suffix.is_some() {
|
||||
// start or end must come before `-suffix`.
|
||||
return false;
|
||||
}
|
||||
if parsed_text_directive.start.is_none() {
|
||||
parsed_text_directive.start = token;
|
||||
return true;
|
||||
}
|
||||
if parsed_text_directive.end.is_none() {
|
||||
parsed_text_directive.end = token;
|
||||
return true;
|
||||
}
|
||||
// if `start` and `end` is already filled,
|
||||
// this is invalid as well.
|
||||
return false;
|
||||
}
|
||||
Some(TextDirectiveParameter::Suffix(..)) => {
|
||||
if parsed_text_directive.start.is_some()
|
||||
&& parsed_text_directive.suffix.is_none()
|
||||
{
|
||||
// `start` must be present and `-suffix` must not be present.
|
||||
// `end` may be present.
|
||||
parsed_text_directive.suffix = token;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// empty or invalid token renders the whole text directive invalid.
|
||||
None => false,
|
||||
})
|
||||
.all(|valid| valid);
|
||||
if valid {
|
||||
return Some(parsed_text_directive);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Creates a percent-encoded string for the current `TextDirective`.
|
||||
/// In the unlikely case that the `TextDirective` is invalid (i.e. `start` is None),
|
||||
/// which should have been caught earlier,this method returns an empty string.
|
||||
pub fn to_percent_encoded_string(&self) -> String {
|
||||
if !self.is_valid() {
|
||||
return String::default();
|
||||
}
|
||||
String::from("text=")
|
||||
+ &[&self.prefix, &self.start, &self.end, &self.suffix]
|
||||
.iter()
|
||||
.filter_map(|&token| token.as_ref())
|
||||
.map(|token| token.to_percent_encoded_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
}
|
||||
|
||||
pub fn start(&self) -> &Option<TextDirectiveParameter> {
|
||||
&self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> &Option<TextDirectiveParameter> {
|
||||
&self.end
|
||||
}
|
||||
|
||||
pub fn prefix(&self) -> &Option<TextDirectiveParameter> {
|
||||
&self.prefix
|
||||
}
|
||||
|
||||
pub fn suffix(&self) -> &Option<TextDirectiveParameter> {
|
||||
&self.suffix
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.prefix.is_none() && self.start.is_none() && self.end.is_none() && self.suffix.is_none()
|
||||
}
|
||||
|
||||
/// A `TextDirective` object is valid if it contains the `start` token.
|
||||
/// All other tokens are optional.
|
||||
fn is_valid(&self) -> bool {
|
||||
self.start.is_some()
|
||||
}
|
||||
}
|
||||
/// Parses a fragment directive into a list of `TextDirective` objects and removes
|
||||
/// the fragment directive from the input url.
|
||||
///
|
||||
/// If the hash does not contain a fragment directive, `url` is not modified
|
||||
/// and this function returns `None`.
|
||||
/// Otherwise, the fragment directive is removed from `url` and parsed.
|
||||
/// If parsing fails, this function returns `None`.
|
||||
pub fn parse_fragment_directive_and_remove_it_from_hash(
|
||||
url: &str,
|
||||
) -> Option<(&str, &str, Vec<TextDirective>)> {
|
||||
// The Fragment Directive is preceded by a `:~:`,
|
||||
// which is only allowed to appear in the hash once.
|
||||
// However (even if unlikely), it might appear outside of the hash,
|
||||
// so this code only considers it when it is after the #.
|
||||
let maybe_first_hash_pos = url.find("#");
|
||||
// If there is no # in url, it is considered to be only the hash (and not a full url).
|
||||
let first_hash_pos = maybe_first_hash_pos.unwrap_or_default();
|
||||
let mut fragment_directive_iter = url[first_hash_pos..].split(":~:");
|
||||
let url_with_stripped_fragment_directive =
|
||||
&url[..first_hash_pos + fragment_directive_iter.next().unwrap_or_default().len()];
|
||||
|
||||
if let Some(fragment_directive) = fragment_directive_iter.next() {
|
||||
if fragment_directive_iter.next().is_some() {
|
||||
// There are multiple occurrences of `:~:`, which is not allowed.
|
||||
return None;
|
||||
}
|
||||
// - fragments are separated by `&`.
|
||||
// - if a fragment does not start with `text=`, it is not a text fragment and will be ignored.
|
||||
// - if parsing of the text fragment fails (for whatever reason), it will be ignored.
|
||||
let text_directives: Vec<_> = fragment_directive
|
||||
.split("&")
|
||||
.map(|maybe_text_fragment| {
|
||||
TextDirective::from_percent_encoded_string(&maybe_text_fragment)
|
||||
})
|
||||
.filter_map(|maybe_text_directive| maybe_text_directive)
|
||||
.collect();
|
||||
if !text_directives.is_empty() {
|
||||
return Some((
|
||||
url_with_stripped_fragment_directive
|
||||
.strip_suffix("#")
|
||||
.unwrap_or(url_with_stripped_fragment_directive),
|
||||
fragment_directive,
|
||||
text_directives,
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Creates a percent-encoded text fragment string.
|
||||
///
|
||||
/// The returned string starts with `:~:`, so that it can be appended
|
||||
/// to a normal fragment.
|
||||
/// Text directives which are not valid (ie., they are missing the `start` parameter),
|
||||
/// are skipped.
|
||||
///
|
||||
/// Returns `None` if `fragment_directives` is empty.
|
||||
pub fn create_fragment_directive_string(text_directives: &Vec<TextDirective>) -> Option<String> {
|
||||
if text_directives.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let encoded_fragment_directives: Vec<_> = text_directives
|
||||
.iter()
|
||||
.filter(|&fragment_directive| fragment_directive.is_valid())
|
||||
.map(|fragment_directive| fragment_directive.to_percent_encoded_string())
|
||||
.filter(|text_directive| !text_directive.is_empty())
|
||||
.collect();
|
||||
if encoded_fragment_directives.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(String::from(":~:") + &encoded_fragment_directives.join("&"))
|
||||
}
|
||||
|
||||
/// Creates the percent-encoded text directive string for a single text directive.
|
||||
pub fn create_text_directive_string(text_directive: &TextDirective) -> Option<String> {
|
||||
if text_directive.is_valid() {
|
||||
Some(text_directive.to_percent_encoded_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use nsstring::{nsCString, nsString};
|
||||
use thin_vec::ThinVec;
|
||||
pub mod fragment_directive_impl;
|
||||
mod test;
|
||||
|
||||
/// This struct contains the percent-decoded parts of a text directive.
|
||||
/// All parts besides `start` are optional (which is indicated by an empty string).
|
||||
///
|
||||
/// This struct uses Gecko String types, whereas the parser internally uses Rust types.
|
||||
/// Therefore, conversion functions are provided.
|
||||
#[repr(C)]
|
||||
pub struct TextDirective {
|
||||
prefix: nsString,
|
||||
start: nsString,
|
||||
end: nsString,
|
||||
suffix: nsString,
|
||||
}
|
||||
|
||||
impl TextDirective {
|
||||
/// Creates a `FragmentDirectiveElement` object from a `FragmentDirectiveElementInternal` object
|
||||
/// (which uses Rust string types).
|
||||
fn from_rust_type(element: &fragment_directive_impl::TextDirective) -> Self {
|
||||
Self {
|
||||
prefix: element
|
||||
.prefix()
|
||||
.as_ref()
|
||||
.map_or_else(nsString::new, |token| nsString::from(token.value())),
|
||||
start: element
|
||||
.start()
|
||||
.as_ref()
|
||||
.map_or_else(nsString::new, |token| nsString::from(token.value())),
|
||||
end: element
|
||||
.end()
|
||||
.as_ref()
|
||||
.map_or_else(nsString::new, |token| nsString::from(token.value())),
|
||||
suffix: element
|
||||
.suffix()
|
||||
.as_ref()
|
||||
.map_or_else(nsString::new, |token| nsString::from(token.value())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the contents of this object into Rust types.
|
||||
/// Returns `None` if the given fragment is not valid.
|
||||
/// The only invalid condition is a fragment that is missing the `start` token.
|
||||
fn to_rust_type(&self) -> Option<fragment_directive_impl::TextDirective> {
|
||||
fragment_directive_impl::TextDirective::from_parts(
|
||||
self.prefix.to_string(),
|
||||
self.start.to_string(),
|
||||
self.end.to_string(),
|
||||
self.suffix.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of the `parse_fragment_directive()` function.
|
||||
///
|
||||
/// The result contains the original given URL without the fragment directive,
|
||||
/// a unsanitized string version of the extracted fragment directive,
|
||||
/// and an array of the parsed text directives.
|
||||
#[repr(C)]
|
||||
pub struct ParsedFragmentDirectiveResult {
|
||||
url_without_fragment_directive: nsCString,
|
||||
fragment_directive: nsCString,
|
||||
text_directives: ThinVec<TextDirective>,
|
||||
}
|
||||
|
||||
/// Parses the fragment directive from a given URL.
|
||||
///
|
||||
/// This function writes the result data into `result`.
|
||||
/// The result consists of
|
||||
/// - the input url without the fragment directive,
|
||||
/// - the fragment directive as unparsed string,
|
||||
/// - a list of the parsed and percent-decoded text directives.
|
||||
///
|
||||
/// Directives which are unknown will be ignored.
|
||||
/// If new directive types are added in the future, they should also be considered here.
|
||||
/// This function returns false if no fragment directive is found, or it could not be parsed.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn parse_fragment_directive(
|
||||
url: &nsCString,
|
||||
result: &mut ParsedFragmentDirectiveResult,
|
||||
) -> bool {
|
||||
// sanitize inputs
|
||||
result.url_without_fragment_directive = nsCString::new();
|
||||
result.fragment_directive = nsCString::new();
|
||||
result.text_directives.clear();
|
||||
|
||||
let url_as_rust_string = url.to_utf8();
|
||||
if let Some((stripped_url, fragment_directive, text_directives)) =
|
||||
fragment_directive_impl::parse_fragment_directive_and_remove_it_from_hash(
|
||||
&url_as_rust_string,
|
||||
)
|
||||
{
|
||||
result
|
||||
.url_without_fragment_directive
|
||||
.assign(&stripped_url);
|
||||
result.fragment_directive.assign(&fragment_directive);
|
||||
result.text_directives.extend(
|
||||
text_directives
|
||||
.iter()
|
||||
.map(|text_directive| TextDirective::from_rust_type(text_directive)),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Creates a percent-encoded fragment directive string from a given list of `FragmentDirectiveElement`s.
|
||||
///
|
||||
/// The returned string has this form:
|
||||
/// `:~:text=[prefix1-,]start1[,end1][,-suffix1]&text=[prefix2-,]start2[,end2][,-suffix2]`
|
||||
///
|
||||
/// Invalid `FragmentDirectiveElement`s are ignored, where "invalid" means that no `start` token is provided.
|
||||
/// If there are no valid `FragmentDirectiveElement`s, an empty string is returned.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn create_fragment_directive(
|
||||
text_directives: &ThinVec<TextDirective>,
|
||||
fragment_directive: &mut nsCString,
|
||||
) -> bool {
|
||||
let directives_rust = Vec::from_iter(
|
||||
text_directives
|
||||
.iter()
|
||||
.filter_map(|fragment| fragment.to_rust_type()),
|
||||
);
|
||||
if let Some(fragment_directive_rust) =
|
||||
fragment_directive_impl::create_fragment_directive_string(&directives_rust)
|
||||
{
|
||||
fragment_directive.assign(&fragment_directive_rust);
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Creates a percent-encoded text directive string for a single text directive.
|
||||
/// The returned string has the form `text=[prefix-,]start[,end][,-suffix]`.
|
||||
/// If the provided `TextDirective` is invalid (i.e. it has no `start` attribute),
|
||||
/// the outparam `directive_string` is empty and the function returns false.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn create_text_directive(
|
||||
text_directive: &TextDirective,
|
||||
directive_string: &mut nsCString,
|
||||
) -> bool {
|
||||
if let Some(text_directive_rust) = text_directive.to_rust_type() {
|
||||
if let Some(text_directive_string_rust) =
|
||||
fragment_directive_impl::create_text_directive_string(&text_directive_rust)
|
||||
{
|
||||
directive_string.assign(&text_directive_string_rust);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
|
@ -1,599 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::fragment_directive_impl::{
|
||||
create_fragment_directive_string, parse_fragment_directive_and_remove_it_from_hash,
|
||||
TextDirective,
|
||||
};
|
||||
|
||||
/// This test verifies that valid combinations of [prefix-,]start[,end][,-suffix] are parsed correctly.
|
||||
#[test]
|
||||
fn test_parse_fragment_directive_with_one_text_directive() {
|
||||
let test_cases = vec![
|
||||
("#:~:text=start", (None, Some("start"), None, None)),
|
||||
(
|
||||
"#:~:text=start,end",
|
||||
(None, Some("start"), Some("end"), None),
|
||||
),
|
||||
(
|
||||
"#:~:text=prefix-,start",
|
||||
(Some("prefix"), Some("start"), None, None),
|
||||
),
|
||||
(
|
||||
"#:~:text=prefix-,start,end",
|
||||
(Some("prefix"), Some("start"), Some("end"), None),
|
||||
),
|
||||
(
|
||||
"#:~:text=prefix-,start,end,-suffix",
|
||||
(Some("prefix"), Some("start"), Some("end"), Some("suffix")),
|
||||
),
|
||||
(
|
||||
"#:~:text=start,-suffix",
|
||||
(None, Some("start"), None, Some("suffix")),
|
||||
),
|
||||
(
|
||||
"#:~:text=start,end,-suffix",
|
||||
(None, Some("start"), Some("end"), Some("suffix")),
|
||||
),
|
||||
("#:~:text=text=", (None, Some("text="), None, None)),
|
||||
];
|
||||
for (url, (prefix, start, end, suffix)) in test_cases {
|
||||
let (stripped_url, fragment_directive, result) =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url)
|
||||
.expect("The parser must find a result.");
|
||||
assert_eq!(
|
||||
fragment_directive,
|
||||
&url[4..],
|
||||
"The extracted fragment directive string
|
||||
should be unsanitized and therefore match the input string."
|
||||
);
|
||||
assert_eq!(result.len(), 1, "There must be one parsed text fragment.");
|
||||
assert_eq!(
|
||||
stripped_url, "",
|
||||
"The fragment directive must be removed from the url hash."
|
||||
);
|
||||
let text_directive = result.first().unwrap();
|
||||
if prefix.is_none() {
|
||||
assert!(
|
||||
text_directive.prefix().is_none(),
|
||||
"There must be no `prefix` token (test case `{}`).",
|
||||
url
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
text_directive
|
||||
.prefix()
|
||||
.as_ref()
|
||||
.expect("There must be a `prefix` token.")
|
||||
.value()
|
||||
== prefix.unwrap(),
|
||||
"Wrong value for `prefix` (test case `{}`).",
|
||||
url
|
||||
);
|
||||
}
|
||||
if start.is_none() {
|
||||
assert!(
|
||||
text_directive.start().is_none(),
|
||||
"There must be no `start` token (test case `{}`).",
|
||||
url
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` token.")
|
||||
.value()
|
||||
== start.unwrap(),
|
||||
"Wrong value for `start` (test case `{}`).",
|
||||
url
|
||||
);
|
||||
}
|
||||
if end.is_none() {
|
||||
assert!(
|
||||
text_directive.end().is_none(),
|
||||
"There must be no `end` token (test case `{}`).",
|
||||
url
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
text_directive
|
||||
.end()
|
||||
.as_ref()
|
||||
.expect("There must be a `end` token.")
|
||||
.value()
|
||||
== end.unwrap(),
|
||||
"Wrong value for `end` (test case `{}`).",
|
||||
url
|
||||
);
|
||||
}
|
||||
if suffix.is_none() {
|
||||
assert!(
|
||||
text_directive.suffix().is_none(),
|
||||
"There must be no `suffix` token (test case `{}`).",
|
||||
url
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
text_directive
|
||||
.suffix()
|
||||
.as_ref()
|
||||
.expect("There must be a `suffix` token.")
|
||||
.value()
|
||||
== suffix.unwrap(),
|
||||
"Wrong value for `suffix` (test case `{}`).",
|
||||
url
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_full_url() {
|
||||
for (url, stripped_url_ref) in [
|
||||
("https://example.com#:~:text=foo", "https://example.com"),
|
||||
(
|
||||
"https://example.com/some/page.html?query=answer#:~:text=foo",
|
||||
"https://example.com/some/page.html?query=answer",
|
||||
),
|
||||
(
|
||||
"https://example.com/some/page.html?query=answer#fragment:~:text=foo",
|
||||
"https://example.com/some/page.html?query=answer#fragment",
|
||||
),
|
||||
(
|
||||
"http://example.com/page.html?query=irrelevant:~:#bar:~:text=foo",
|
||||
"http://example.com/page.html?query=irrelevant:~:#bar"
|
||||
)
|
||||
] {
|
||||
let (stripped_url, fragment_directive, _) =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url)
|
||||
.expect("The parser must find a result");
|
||||
assert_eq!(stripped_url, stripped_url_ref, "The stripped url is not correct.");
|
||||
assert_eq!(fragment_directive, "text=foo");
|
||||
}
|
||||
}
|
||||
|
||||
/// This test verifies that a text fragment is parsed correctly if it is preceded
|
||||
/// or followed by a fragment (i.e. `#foo:~:text=bar`).
|
||||
#[test]
|
||||
fn test_parse_text_fragment_after_fragments() {
|
||||
let url = "#foo:~:text=start";
|
||||
let (stripped_url, fragment_directive, result) =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url)
|
||||
.expect("The parser must find a result.");
|
||||
assert_eq!(
|
||||
result.len(),
|
||||
1,
|
||||
"There must be exactly one parsed text fragment."
|
||||
);
|
||||
assert_eq!(
|
||||
stripped_url, "#foo",
|
||||
"The fragment directive was not removed correctly."
|
||||
);
|
||||
assert_eq!(
|
||||
fragment_directive, "text=start",
|
||||
"The fragment directive was not extracted correctly."
|
||||
);
|
||||
let fragment = result.first().unwrap();
|
||||
assert!(fragment.prefix().is_none(), "There is no `prefix` token.");
|
||||
assert_eq!(
|
||||
fragment
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` token.")
|
||||
.value(),
|
||||
"start"
|
||||
);
|
||||
assert!(fragment.end().is_none(), "There is no `end` token.");
|
||||
assert!(fragment.suffix().is_none(), "There is no `suffix` token.");
|
||||
}
|
||||
|
||||
/// Ensure that multiple text fragments are parsed correctly.
|
||||
#[test]
|
||||
fn test_parse_multiple_text_fragments() {
|
||||
let url = "#:~:text=prefix-,start,-suffix&text=foo&text=bar,-suffix";
|
||||
let (_, _, text_directives) =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url)
|
||||
.expect("The parser must find a result.");
|
||||
assert_eq!(
|
||||
text_directives.len(),
|
||||
3,
|
||||
"There must be exactly two parsed text fragments."
|
||||
);
|
||||
let first_text_directive = &text_directives[0];
|
||||
assert_eq!(
|
||||
first_text_directive
|
||||
.prefix()
|
||||
.as_ref()
|
||||
.expect("There must be a `prefix` token.")
|
||||
.value(),
|
||||
"prefix"
|
||||
);
|
||||
assert_eq!(
|
||||
first_text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` token.")
|
||||
.value(),
|
||||
"start"
|
||||
);
|
||||
assert!(
|
||||
first_text_directive.end().is_none(),
|
||||
"There is no `end` token."
|
||||
);
|
||||
assert_eq!(
|
||||
first_text_directive
|
||||
.suffix()
|
||||
.as_ref()
|
||||
.expect("There must be a `suffix` token.")
|
||||
.value(),
|
||||
"suffix"
|
||||
);
|
||||
|
||||
let second_text_directive = &text_directives[1];
|
||||
assert!(
|
||||
second_text_directive.prefix().is_none(),
|
||||
"There is no `prefix` token."
|
||||
);
|
||||
assert_eq!(
|
||||
second_text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` token.")
|
||||
.value(),
|
||||
"foo"
|
||||
);
|
||||
assert!(
|
||||
second_text_directive.end().is_none(),
|
||||
"There is no `end` token."
|
||||
);
|
||||
assert!(
|
||||
second_text_directive.suffix().is_none(),
|
||||
"There is no `suffix` token."
|
||||
);
|
||||
let third_text_directive = &text_directives[2];
|
||||
assert!(
|
||||
third_text_directive.prefix().is_none(),
|
||||
"There is no `prefix` token."
|
||||
);
|
||||
assert_eq!(
|
||||
third_text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` token.")
|
||||
.value(),
|
||||
"bar"
|
||||
);
|
||||
assert!(
|
||||
third_text_directive.end().is_none(),
|
||||
"There is no `end` token."
|
||||
);
|
||||
assert_eq!(
|
||||
third_text_directive
|
||||
.suffix()
|
||||
.as_ref()
|
||||
.expect("There must be a `suffix` token.")
|
||||
.value(),
|
||||
"suffix"
|
||||
);
|
||||
}
|
||||
|
||||
/// Multiple text directives should be parsed correctly
|
||||
/// if they are surrounded or separated by unknown directives.
|
||||
#[test]
|
||||
fn test_parse_multiple_text_directives_with_unknown_directive_in_between() {
|
||||
for url in [
|
||||
"#:~:foo&text=start1&text=start2",
|
||||
"#:~:text=start1&foo&text=start2",
|
||||
"#:~:text=start1&text=start2&foo",
|
||||
] {
|
||||
let (_, fragment_directive, text_directives) =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url)
|
||||
.expect("The parser must find a result.");
|
||||
assert_eq!(
|
||||
fragment_directive,
|
||||
&url[4..],
|
||||
"The extracted fragment directive string is unsanitized
|
||||
and should contain the unknown directive."
|
||||
);
|
||||
assert_eq!(
|
||||
text_directives.len(),
|
||||
2,
|
||||
"There must be exactly two parsed text fragments."
|
||||
);
|
||||
let first_text_directive = &text_directives[0];
|
||||
assert_eq!(
|
||||
first_text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` token.")
|
||||
.value(),
|
||||
"start1"
|
||||
);
|
||||
let second_text_directive = &text_directives[1];
|
||||
assert_eq!(
|
||||
second_text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` token.")
|
||||
.value(),
|
||||
"start2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that input that doesn't contain a text fragment does not produce a result.
|
||||
/// This includes the use of partial identifying tokens necessary for a text fragment
|
||||
/// (e.g. `:~:` without `text=`, `text=foo` without the `:~:` or multiple occurrences of `:~:`)
|
||||
/// In these cases, the parser must return `None` to indicate that there are no valid text fragments.
|
||||
#[test]
|
||||
fn test_parse_invalid_or_unknown_fragment_directive() {
|
||||
for url in [
|
||||
"#foo",
|
||||
"#foo:",
|
||||
"#foo:~:",
|
||||
"#foo:~:bar",
|
||||
"text=prefix-,start",
|
||||
"#:~:text=foo-,bar,-baz:~:text=foo",
|
||||
] {
|
||||
let text_directives =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url);
|
||||
assert!(
|
||||
text_directives.is_none(),
|
||||
"The fragment `{}` does not contain a valid or known fragment directive.",
|
||||
url
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that ill-formed text directives (but valid fragment directives)
|
||||
/// (starting correctly with `:~:text=`) are not parsed.
|
||||
/// Instead `None` must be returned.
|
||||
/// Test cases include invalid combinations of `prefix`/`suffix`es,
|
||||
/// additional `,`s, too many `start`/`end` tokens, or empty text fragments.
|
||||
#[test]
|
||||
fn test_parse_invalid_text_fragments() {
|
||||
for url in [
|
||||
"#:~:text=start,start,start",
|
||||
"#:~:text=prefix-,prefix-",
|
||||
"#:~:text=prefix-,-suffix",
|
||||
"#:~:text=prefix-,start,start,start",
|
||||
"#:~:text=prefix-,start,start,start,-suffix",
|
||||
"#:~:text=start,start,start,-suffix",
|
||||
"#:~:text=prefix-,start,end,-suffix,foo",
|
||||
"#:~:text=foo,prefix-,start",
|
||||
"#:~:text=prefix-,,start,",
|
||||
"#:~:text=,prefix,start",
|
||||
"#:~:text=",
|
||||
] {
|
||||
let text_directives =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url);
|
||||
assert!(
|
||||
text_directives.is_none(),
|
||||
"The fragment directive `{}` does not contain a valid text directive.",
|
||||
url
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that out of multiple text fragments only the invalid ones are ignored
|
||||
/// while valid text fragments are still returned.
|
||||
/// Since correct parsing of multiple text fragments as well as
|
||||
/// several forms of invalid text fragments are already tested in
|
||||
/// `test_parse_multiple_text_fragments` and `test_parse_invalid_text_fragments()`,
|
||||
/// it should be enough to test this with only one fragment directive
|
||||
/// that contains two text fragments, one of them being invalid.
|
||||
#[test]
|
||||
fn test_valid_and_invalid_text_directives() {
|
||||
for url in [
|
||||
"#:~:text=start&text=,foo,",
|
||||
"#:~:text=foo,foo,foo&text=start",
|
||||
] {
|
||||
let (_, fragment_directive, text_directives) =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url)
|
||||
.expect("The parser must find a result.");
|
||||
assert_eq!(
|
||||
fragment_directive,
|
||||
&url[4..],
|
||||
"The extracted fragment directive string is unsanitized
|
||||
and should contain invalid text directives."
|
||||
);
|
||||
assert_eq!(
|
||||
text_directives.len(),
|
||||
1,
|
||||
"There must be exactly one parsed text fragment."
|
||||
);
|
||||
let text_directive = text_directives.first().unwrap();
|
||||
assert_eq!(
|
||||
text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a `start` value.")
|
||||
.value(),
|
||||
"start",
|
||||
"The `start` value of the text directive has the wrong value."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that a fragment directive that contains percent-encoded characters
|
||||
/// is decoded correctly. This explicitly includes characters which are used
|
||||
/// for identifying text fragments, i.e. `#`, `, `, `&`, `:`, `~` and `-`.
|
||||
#[test]
|
||||
fn test_parse_percent_encoding_tokens() {
|
||||
let url = "#:~:text=prefix%26-,start%20and%2C,end%23,-%26suffix%2D";
|
||||
let (_, fragment_directive, text_directives) =
|
||||
parse_fragment_directive_and_remove_it_from_hash(&url)
|
||||
.expect("The parser must find a result.");
|
||||
assert_eq!(
|
||||
fragment_directive,
|
||||
&url[4..],
|
||||
"The extracted fragment directive string is unsanitized
|
||||
and should contain the original and percent-decoded string."
|
||||
);
|
||||
let text_directive = text_directives.first().unwrap();
|
||||
assert_eq!(
|
||||
text_directive
|
||||
.prefix()
|
||||
.as_ref()
|
||||
.expect("There must be a prefix.")
|
||||
.value(),
|
||||
"prefix&",
|
||||
""
|
||||
);
|
||||
assert_eq!(
|
||||
text_directive
|
||||
.start()
|
||||
.as_ref()
|
||||
.expect("There must be a prefix.")
|
||||
.value(),
|
||||
"start and,",
|
||||
""
|
||||
);
|
||||
assert_eq!(
|
||||
text_directive
|
||||
.end()
|
||||
.as_ref()
|
||||
.expect("There must be a prefix.")
|
||||
.value(),
|
||||
"end#",
|
||||
""
|
||||
);
|
||||
assert_eq!(
|
||||
text_directive
|
||||
.suffix()
|
||||
.as_ref()
|
||||
.expect("There must be a prefix.")
|
||||
.value(),
|
||||
"&suffix-",
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensures that a text fragment is created correctly,
|
||||
/// based on a given combination of tokens.
|
||||
/// This includes all sorts of combinations of
|
||||
/// `prefix`, `suffix`, `start` and `end`,
|
||||
/// als well as values for these tokens which contain
|
||||
/// characters that need to be encoded because they are
|
||||
/// identifiers for text fragments
|
||||
/// (#`, `, `, `&`, `:`, `~` and `-`).
|
||||
#[test]
|
||||
fn test_create_fragment_directive() {
|
||||
for (text_directive, expected_fragment_directive) in [
|
||||
(
|
||||
TextDirective::from_parts(
|
||||
String::new(),
|
||||
String::from("start"),
|
||||
String::new(),
|
||||
String::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
":~:text=start",
|
||||
),
|
||||
(
|
||||
TextDirective::from_parts(
|
||||
String::new(),
|
||||
String::from("start"),
|
||||
String::from("end"),
|
||||
String::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
":~:text=start,end",
|
||||
),
|
||||
(
|
||||
TextDirective::from_parts(
|
||||
String::from("prefix"),
|
||||
String::from("start"),
|
||||
String::from("end"),
|
||||
String::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
":~:text=prefix-,start,end",
|
||||
),
|
||||
(
|
||||
TextDirective::from_parts(
|
||||
String::from("prefix"),
|
||||
String::from("start"),
|
||||
String::from("end"),
|
||||
String::from("suffix"),
|
||||
)
|
||||
.unwrap(),
|
||||
":~:text=prefix-,start,end,-suffix",
|
||||
),
|
||||
(
|
||||
TextDirective::from_parts(
|
||||
String::new(),
|
||||
String::from("start"),
|
||||
String::from("end"),
|
||||
String::from("suffix"),
|
||||
)
|
||||
.unwrap(),
|
||||
":~:text=start,end,-suffix",
|
||||
),
|
||||
(
|
||||
TextDirective::from_parts(
|
||||
String::from("prefix"),
|
||||
String::from("start"),
|
||||
String::new(),
|
||||
String::from("suffix"),
|
||||
)
|
||||
.unwrap(),
|
||||
":~:text=prefix-,start,-suffix",
|
||||
),
|
||||
(
|
||||
TextDirective::from_parts(
|
||||
String::from("prefix-"),
|
||||
String::from("start and,"),
|
||||
String::from("&end"),
|
||||
String::from("#:~:suffix"),
|
||||
)
|
||||
.unwrap(),
|
||||
":~:text=prefix%2D-,start%20and%2C,%26end,-%23%3A%7E%3Asuffix",
|
||||
),
|
||||
] {
|
||||
let fragment_directive = create_fragment_directive_string(&vec![text_directive])
|
||||
.expect("The given input must produce a valid fragment directive.");
|
||||
assert_eq!(fragment_directive, expected_fragment_directive);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that a fragment directive is created correctly if multiple text fragments are given.
|
||||
/// The resulting fragment must start with `:~:`
|
||||
/// and each text fragment must be separated using `&text=`.
|
||||
#[test]
|
||||
fn test_create_fragment_directive_from_multiple_text_directives() {
|
||||
let text_directives = vec![
|
||||
TextDirective::from_parts(
|
||||
String::new(),
|
||||
String::from("start1"),
|
||||
String::new(),
|
||||
String::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
TextDirective::from_parts(
|
||||
String::new(),
|
||||
String::from("start2"),
|
||||
String::new(),
|
||||
String::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
TextDirective::from_parts(
|
||||
String::new(),
|
||||
String::from("start3"),
|
||||
String::new(),
|
||||
String::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
let fragment_directive = create_fragment_directive_string(&text_directives)
|
||||
.expect("The given input must produce a valid fragment directive.");
|
||||
assert_eq!(
|
||||
fragment_directive, ":~:text=start1&text=start2&text=start3",
|
||||
"The created fragment directive is wrong for multiple fragments."
|
||||
);
|
||||
}
|
||||
}
|
|
@ -194,7 +194,6 @@ EXPORTS.mozilla.dom += [
|
|||
"External.h",
|
||||
"FilteredNodeIterator.h",
|
||||
"FormData.h",
|
||||
"FragmentDirective.h",
|
||||
"FragmentOrElement.h",
|
||||
"FromParser.h",
|
||||
"GeneratedImageContent.h",
|
||||
|
@ -298,7 +297,6 @@ if CONFIG["FUZZING"]:
|
|||
|
||||
if CONFIG["COMPILE_ENVIRONMENT"]:
|
||||
EXPORTS.mozilla.dom += [
|
||||
"!fragmentdirectives_ffi_generated.h",
|
||||
"!GeneratedElementDocumentState.h",
|
||||
"RustTypes.h",
|
||||
]
|
||||
|
@ -308,11 +306,6 @@ if CONFIG["COMPILE_ENVIRONMENT"]:
|
|||
inputs=["rust"],
|
||||
)
|
||||
|
||||
CbindgenHeader(
|
||||
"fragmentdirectives_ffi_generated.h",
|
||||
inputs=["fragmentdirectives"],
|
||||
)
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"!UseCounterMetrics.cpp",
|
||||
"AbstractRange.cpp",
|
||||
|
@ -357,7 +350,6 @@ UNIFIED_SOURCES += [
|
|||
"EventSourceEventService.cpp",
|
||||
"External.cpp",
|
||||
"FormData.cpp",
|
||||
"FragmentDirective.cpp",
|
||||
"FragmentOrElement.cpp",
|
||||
"GeneratedImageContent.cpp",
|
||||
"GlobalTeardownObserver.cpp",
|
||||
|
|
|
@ -41,12 +41,11 @@ interface nsISelectionController : nsISelectionDisplay
|
|||
const short SELECTION_FIND = 8;
|
||||
const short SELECTION_URLSECONDARY = 9;
|
||||
const short SELECTION_URLSTRIKEOUT = 10;
|
||||
const short SELECTION_TARGET_TEXT = 11;
|
||||
// Custom Highlight API
|
||||
// (see https://drafts.csswg.org/css-highlight-api-1/#enumdef-highlighttype)
|
||||
const short SELECTION_HIGHLIGHT = 12;
|
||||
const short SELECTION_HIGHLIGHT = 11;
|
||||
// End of RawSelectionType values.
|
||||
const short NUM_SELECTIONTYPES = 13;
|
||||
const short NUM_SELECTIONTYPES = 12;
|
||||
|
||||
// SelectionRegion values:
|
||||
const short SELECTION_ANCHOR_REGION = 0;
|
||||
|
@ -312,7 +311,6 @@ enum class SelectionType : RawSelectionType
|
|||
eFind = nsISelectionController::SELECTION_FIND,
|
||||
eURLSecondary = nsISelectionController::SELECTION_URLSECONDARY,
|
||||
eURLStrikeout = nsISelectionController::SELECTION_URLSTRIKEOUT,
|
||||
eTargetText = nsISelectionController::SELECTION_TARGET_TEXT,
|
||||
eHighlight = nsISelectionController::SELECTION_HIGHLIGHT,
|
||||
};
|
||||
|
||||
|
@ -329,7 +327,6 @@ static const SelectionType kPresentSelectionTypes[] = {
|
|||
SelectionType::eFind,
|
||||
SelectionType::eURLSecondary,
|
||||
SelectionType::eURLStrikeout,
|
||||
SelectionType::eTargetText,
|
||||
SelectionType::eHighlight,
|
||||
};
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
* https://drafts.csswg.org/cssom/#extensions-to-the-document-interface
|
||||
* https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface
|
||||
* https://wicg.github.io/feature-policy/#policy
|
||||
* https://wicg.github.io/scroll-to-text-fragment/#feature-detectability
|
||||
*/
|
||||
|
||||
interface ContentSecurityPolicy;
|
||||
|
@ -748,12 +747,3 @@ partial interface Document {
|
|||
};
|
||||
|
||||
Document includes NonElementParentNode;
|
||||
|
||||
/**
|
||||
* Extension to add the fragmentDirective property.
|
||||
* https://wicg.github.io/scroll-to-text-fragment/#feature-detectability
|
||||
*/
|
||||
partial interface Document {
|
||||
[Pref="dom.text_fragments.enabled", Exposed=Window, SameObject]
|
||||
readonly attribute FragmentDirective fragmentDirective;
|
||||
};
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* The origin of this IDL file is
|
||||
* https://wicg.github.io/scroll-to-text-fragment/
|
||||
*/
|
||||
[Exposed=Window, Pref="dom.text_fragments.enabled"]
|
||||
interface FragmentDirective {
|
||||
};
|
|
@ -568,7 +568,6 @@ WEBIDL_FILES = [
|
|||
"FontFaceSet.webidl",
|
||||
"FontFaceSource.webidl",
|
||||
"FormData.webidl",
|
||||
"FragmentDirective.webidl",
|
||||
"Function.webidl",
|
||||
"GainNode.webidl",
|
||||
"Gamepad.webidl",
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include "mozilla/dom/AncestorIterator.h"
|
||||
#include "mozilla/dom/FontFaceSet.h"
|
||||
#include "mozilla/dom/ElementBinding.h"
|
||||
#include "mozilla/dom/FragmentDirective.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "mozilla/dom/MouseEventBinding.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
|
@ -3292,46 +3291,6 @@ nsresult PresShell::ScrollToAnchor() {
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -1609,18 +1609,6 @@ class PresShell final : public nsStubDocumentObserver,
|
|||
*/
|
||||
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,
|
||||
* it may move our scroll position in the root frame.
|
||||
|
|
|
@ -156,10 +156,9 @@ PeekOffsetStruct::PeekOffsetStruct(nsSelectionAmount aAmount,
|
|||
|
||||
} // namespace mozilla
|
||||
|
||||
// Array which contains index of each SelectionType in
|
||||
// Selection::mDOMSelections. For avoiding using if nor switch to retrieve the
|
||||
// index, this needs to have -1 for SelectionTypes which won't be created its
|
||||
// Selection instance.
|
||||
// Array which contains index of each SelecionType in Selection::mDOMSelections.
|
||||
// For avoiding using if nor switch to retrieve the index, this needs to have
|
||||
// -1 for SelectionTypes which won't be created its Selection instance.
|
||||
static const int8_t kIndexOfSelections[] = {
|
||||
-1, // SelectionType::eInvalid
|
||||
-1, // SelectionType::eNone
|
||||
|
@ -173,7 +172,6 @@ static const int8_t kIndexOfSelections[] = {
|
|||
7, // SelectionType::eFind
|
||||
8, // SelectionType::eURLSecondary
|
||||
9, // SelectionType::eURLStrikeout
|
||||
10, // SelectionType::eTargetText
|
||||
-1, // SelectionType::eHighlight
|
||||
};
|
||||
|
||||
|
|
|
@ -2402,15 +2402,6 @@ already_AddRefed<ComputedStyle> nsIFrame::ComputeHighlightSelectionStyle(
|
|||
*element, PseudoStyleType::highlight, aHighlightName, Style());
|
||||
}
|
||||
|
||||
already_AddRefed<ComputedStyle> nsIFrame::ComputeTargetTextStyle() const {
|
||||
const Element* element = FindElementAncestorForMozSelection(GetContent());
|
||||
if (!element) {
|
||||
return nullptr;
|
||||
}
|
||||
return PresContext()->StyleSet()->ProbePseudoElementStyle(
|
||||
*element, PseudoStyleType::targetText, nullptr, Style());
|
||||
}
|
||||
|
||||
template <typename SizeOrMaxSize>
|
||||
static inline bool IsIntrinsicKeyword(const SizeOrMaxSize& aSize) {
|
||||
// All keywords other than auto/none/-moz-available depend on intrinsic sizes.
|
||||
|
|
|
@ -937,8 +937,6 @@ class nsIFrame : public nsQueryFrame {
|
|||
already_AddRefed<ComputedStyle> ComputeHighlightSelectionStyle(
|
||||
nsAtom* aHighlightName);
|
||||
|
||||
already_AddRefed<ComputedStyle> ComputeTargetTextStyle() const;
|
||||
|
||||
/**
|
||||
* Accessor functions for geometric parent.
|
||||
*/
|
||||
|
|
|
@ -5676,10 +5676,6 @@ bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
|
|||
aHighlightName, aBackground);
|
||||
return hasForeground || hasBackground;
|
||||
}
|
||||
case SelectionType::eTargetText: {
|
||||
aTextPaintStyle.GetTargetTextColors(aForeground, aBackground);
|
||||
return true;
|
||||
}
|
||||
case SelectionType::eURLSecondary:
|
||||
aTextPaintStyle.GetURLSecondaryColor(aForeground);
|
||||
*aBackground = NS_RGBA(0, 0, 0, 0);
|
||||
|
|
|
@ -213,24 +213,6 @@ void nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
|
|||
*aBackColor = NS_TRANSPARENT;
|
||||
}
|
||||
|
||||
void nsTextPaintStyle::GetTargetTextColors(nscolor* aForeColor,
|
||||
nscolor* aBackColor) {
|
||||
NS_ASSERTION(aForeColor, "aForeColor is null");
|
||||
NS_ASSERTION(aBackColor, "aBackColor is null");
|
||||
const RefPtr<const ComputedStyle> targetTextStyle =
|
||||
mFrame->ComputeTargetTextStyle();
|
||||
if (targetTextStyle) {
|
||||
*aForeColor = targetTextStyle->GetVisitedDependentColor(
|
||||
&nsStyleText::mWebkitTextFillColor);
|
||||
*aBackColor = targetTextStyle->GetVisitedDependentColor(
|
||||
&nsStyleBackground::mBackgroundColor);
|
||||
return;
|
||||
}
|
||||
// XXX(:jjaschke): Before shipping this feature, a sensible set of colors must
|
||||
// be set (Bug 1867940).
|
||||
// in the meantime, use the colors of find selection.
|
||||
GetHighlightColors(aForeColor, aBackColor);
|
||||
}
|
||||
bool nsTextPaintStyle::GetCustomHighlightTextColor(nsAtom* aHighlightName,
|
||||
nscolor* aForeColor) {
|
||||
NS_ASSERTION(aForeColor, "aForeColor is null");
|
||||
|
|
|
@ -65,7 +65,6 @@ class MOZ_STACK_CLASS nsTextPaintStyle {
|
|||
*/
|
||||
bool GetSelectionColors(nscolor* aForeColor, nscolor* aBackColor);
|
||||
void GetHighlightColors(nscolor* aForeColor, nscolor* aBackColor);
|
||||
void GetTargetTextColors(nscolor* aForeColor, nscolor* aBackColor);
|
||||
// Computes colors for custom highlights.
|
||||
// Returns false if there are no rules associated with `aHighlightName`.
|
||||
bool GetCustomHighlightTextColor(nsAtom* aHighlightName, nscolor* aForeColor);
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
"::highlight",
|
||||
"::placeholder",
|
||||
"::selection",
|
||||
"::target-text",
|
||||
"::-moz-color-swatch",
|
||||
"::-moz-focus-inner",
|
||||
"::-moz-meter-bar",
|
||||
|
|
|
@ -51,7 +51,6 @@ CSS_PSEUDO_ELEMENT(highlight, ":highlight", 0)
|
|||
CSS_PSEUDO_ELEMENT(selection, ":selection",
|
||||
CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS)
|
||||
|
||||
CSS_PSEUDO_ELEMENT(targetText, ":target-text", 0)
|
||||
// XXXbz should we really allow random content to style these? Maybe
|
||||
// use our flags to prevent that?
|
||||
CSS_PSEUDO_ELEMENT(mozFocusInner, ":-moz-focus-inner", 0)
|
||||
|
|
|
@ -130,8 +130,6 @@ class nsCSSPseudoElements {
|
|||
switch (aType) {
|
||||
case Type::highlight:
|
||||
return mozilla::StaticPrefs::dom_customHighlightAPI_enabled();
|
||||
case Type::targetText:
|
||||
return mozilla::StaticPrefs::dom_text_fragments_enabled();
|
||||
case Type::sliderTrack:
|
||||
case Type::sliderThumb:
|
||||
case Type::sliderFill:
|
||||
|
|
|
@ -4131,12 +4131,6 @@
|
|||
value: false
|
||||
mirror: always
|
||||
|
||||
- name: dom.text_fragments.enabled
|
||||
type: bool
|
||||
value: false
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
- name: dom.textMetrics.actualBoundingBox.enabled
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
|
|
|
@ -159,11 +159,6 @@ impl PseudoElement {
|
|||
matches!(*self, Self::Highlight(_))
|
||||
}
|
||||
|
||||
/// Whether this pseudo-element is the ::target-text pseudo.
|
||||
#[inline]
|
||||
pub fn is_target_text(&self) -> bool {
|
||||
*self == PseudoElement::TargetText
|
||||
}
|
||||
/// Whether this pseudo-element supports user action selectors.
|
||||
pub fn supports_user_action_state(&self) -> bool {
|
||||
(self.flags() & structs::CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) != 0
|
||||
|
@ -173,7 +168,6 @@ impl PseudoElement {
|
|||
pub fn enabled_in_content(&self) -> bool {
|
||||
match *self {
|
||||
Self::Highlight(..) => pref!("dom.customHighlightAPI.enabled"),
|
||||
Self::TargetText => pref!("dom.text_fragments.enabled"),
|
||||
Self::SliderFill | Self::SliderTrack | Self::SliderThumb => {
|
||||
pref!("layout.css.modern-range-pseudos.enabled")
|
||||
},
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
prefs: [dom.css_pseudo_element.enabled:true, layout.css.animation-composition.enabled:true, layout.css.floating-first-letter.tight-glyph-bounds:0, dom.customHighlightAPI.enabled:true, layout.css.modern-range-pseudos.enabled:true, dom.text_fragments.enabled:true]
|
||||
prefs: [dom.css_pseudo_element.enabled:true, layout.css.animation-composition.enabled:true, layout.css.floating-first-letter.tight-glyph-bounds:0, dom.customHighlightAPI.enabled:true, layout.css.modern-range-pseudos.enabled:true]
|
||||
leak-threshold: [tab:51200]
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
[highlight-currentcolor-computed-visited.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[getComputedStyle() for ::target-text at #target1]
|
||||
expected: FAIL
|
||||
|
||||
[getComputedStyle() for ::target-text at #target2]
|
||||
expected: FAIL
|
||||
|
||||
[getComputedStyle() for ::spelling-error at #target1]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
[highlight-currentcolor-computed.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[getComputedStyle() for ::target-text at #target1]
|
||||
expected: FAIL
|
||||
|
||||
[getComputedStyle() for ::target-text at #target2]
|
||||
expected: FAIL
|
||||
|
||||
[getComputedStyle() for ::spelling-error at #target1]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[highlight-paired-cascade-004.html]
|
||||
expected: TIMEOUT
|
|
@ -1,4 +1,6 @@
|
|||
[highlight-pseudos-computed.html]
|
||||
[getComputedStyle() for ::target-text]
|
||||
expected: FAIL
|
||||
|
||||
[getComputedStyle() for ::spelling-error]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
[highlight-pseudos-visited-computed-001.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[getComputedStyle() for ::target-text at #target1]
|
||||
expected: FAIL
|
||||
|
||||
[getComputedStyle() for ::target-text at #target2]
|
||||
expected: FAIL
|
||||
|
||||
[getComputedStyle() for ::spelling-error at #target1]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,4 +1,16 @@
|
|||
[highlight-pseudos.html]
|
||||
["::target-text" should be a valid selector]
|
||||
expected: FAIL
|
||||
|
||||
[".a::target-text" should be a valid selector]
|
||||
expected: FAIL
|
||||
|
||||
["div ::target-text" should be a valid selector]
|
||||
expected: FAIL
|
||||
|
||||
["::part(my-part)::target-text" should be a valid selector]
|
||||
expected: FAIL
|
||||
|
||||
["::spelling-error" should be a valid selector]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -22,3 +34,4 @@
|
|||
|
||||
["::part(my-part)::grammar-error" should be a valid selector]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1753933
|
||||
prefs: [dom.text_fragments.enabled:true]
|
||||
implementation-status: backlog
|
||||
|
|
|
@ -1,9 +1,207 @@
|
|||
[find-range-from-text-directive.html]
|
||||
[Search invisible content between prefix and match.]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if os == "mac": [OK, TIMEOUT]
|
||||
[TIMEOUT, OK]
|
||||
[inverted range search.]
|
||||
expected:
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
if os == "linux": [PASS, NOTRUN, TIMEOUT]
|
||||
if os == "win": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[Search invisible content between |end| and suffix.]
|
||||
[Suffix comes before |end|.]
|
||||
expected:
|
||||
if os == "win": [PASS, NOTRUN, TIMEOUT]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[Multiple overlapping prefixes.]
|
||||
expected: FAIL
|
||||
|
||||
[match doesn't immediately follow prefix.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, PASS, TIMEOUT]
|
||||
FAIL
|
||||
|
||||
[match doesn't immediately follow first prefix instance.]
|
||||
expected:
|
||||
if os == "android": [FAIL, TIMEOUT, NOTRUN, PASS]
|
||||
[FAIL, PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[Suffix must be end bounded.]
|
||||
expected:
|
||||
if os == "win": [PASS, NOTRUN, TIMEOUT]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[non-existent exact match.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, PASS, TIMEOUT]
|
||||
[PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[non-existent range match.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, PASS, TIMEOUT]
|
||||
[PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[Multiple overlapping one letter prefixes.]
|
||||
expected: FAIL
|
||||
|
||||
[Content appears between match and suffix.]
|
||||
expected:
|
||||
if os == "win": [PASS, NOTRUN, TIMEOUT]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS]
|
||||
|
||||
[overlapping exact matches with suffix.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Match with no suffix.]
|
||||
expected:
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "win": [FAIL, NOTRUN, TIMEOUT]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Match text after prefix.]
|
||||
expected: FAIL
|
||||
|
||||
[Search invisible content between |end| and suffix.]
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Range with preceeding suffix.]
|
||||
expected:
|
||||
if os == "linux": [NOTRUN, TIMEOUT, PASS]
|
||||
if os == "win": [PASS, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[no suffix forces |end| to be end bounded.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, PASS, TIMEOUT]
|
||||
[PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[|end| must be start bounded even if full range is word bounded.]
|
||||
expected:
|
||||
if os == "linux": [PASS, NOTRUN, TIMEOUT]
|
||||
if os == "win": [PASS, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[non-existent |end|.]
|
||||
expected:
|
||||
if os == "win": [PASS, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[suffix means |start| need not end on word boundary.]
|
||||
expected:
|
||||
if os == "linux": [FAIL, NOTRUN]
|
||||
FAIL
|
||||
|
||||
[Search invisible content between prefix and match.]
|
||||
expected: FAIL
|
||||
|
||||
[overlapping one letter exact matches with suffix.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Basic smoke test - full word match.]
|
||||
expected: FAIL
|
||||
|
||||
[matching range search.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Prefix need not end on a word boundary.]
|
||||
expected: FAIL
|
||||
|
||||
[|start| doesn't need to start on word boundary.]
|
||||
expected:
|
||||
if os == "linux": [FAIL, NOTRUN]
|
||||
FAIL
|
||||
|
||||
[no-prefix; suffix means |start| need not end on word boundary.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[no-prefix; |end| forces |start| to end on word boundary.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, TIMEOUT, PASS]
|
||||
[PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[no-prefix; no |end| or suffix forces |start| to end on word boundary.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, PASS, TIMEOUT]
|
||||
[PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[Non-existent suffix.]
|
||||
expected:
|
||||
if os == "win": [PASS, NOTRUN, TIMEOUT]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[Suffix need not be start bounded.]
|
||||
expected:
|
||||
if os == "win": [FAIL, NOTRUN, TIMEOUT]
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[|end| must be start bounded.]
|
||||
expected:
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
if os == "linux": [PASS, NOTRUN, TIMEOUT]
|
||||
if os == "win": [PASS, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, PASS, TIMEOUT]
|
||||
|
||||
[suffix means |end| need not be end bounded.]
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "linux": [FAIL, NOTRUN, TIMEOUT]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[|start| must start on a word boundary.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, PASS, TIMEOUT]
|
||||
[PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[Non-matching suffix search continues to prefix match.]
|
||||
expected:
|
||||
if os == "mac": [FAIL, NOTRUN, TIMEOUT]
|
||||
if os == "win": [FAIL, NOTRUN, TIMEOUT]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Range end matches correct suffix.]
|
||||
expected:
|
||||
if os == "mac": [FAIL, NOTRUN, TIMEOUT]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Non-matching suffix in first potential match.]
|
||||
expected:
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "win": [FAIL, NOTRUN, TIMEOUT]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[match doesn't immediately follow prefix.]
|
||||
expected: [PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[prefix with non-existent range match.]
|
||||
expected:
|
||||
if os == "linux": [PASS, TIMEOUT, NOTRUN]
|
||||
[PASS, TIMEOUT]
|
||||
|
||||
[no |end| or suffix forces |start| to end on word boundary.]
|
||||
expected:
|
||||
if os == "linux": [PASS, TIMEOUT]
|
||||
|
||||
[prefix with non-existent exact match.]
|
||||
expected:
|
||||
if os == "linux": [PASS, NOTRUN]
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
[force-load-at-top.html]
|
||||
expected:
|
||||
if os == "linux": OK
|
||||
[OK, TIMEOUT]
|
||||
[force-load-at-top must block scroll on load from element fragment.]
|
||||
expected: [FAIL, PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
|
@ -7,6 +10,12 @@
|
|||
if os == "win": [PASS, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[no-force-load-at-top must not block scroll on load from text fragment.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[no-force-load-at-top must not block scroll on load from text fragment with element fallback.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[no-force-load-at-top must not block scroll on load from element fragment.]
|
||||
expected: [PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
|
@ -15,7 +24,4 @@
|
|||
|
||||
[force-load-at-top must block scroll on load from text fragment with element fallback.]
|
||||
expected:
|
||||
FAIL
|
||||
|
||||
[force-load-at-top must block scroll on load from text fragment.]
|
||||
expected: FAIL
|
||||
if os == "win": [PASS, TIMEOUT]
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
[idlharness.window.html]
|
||||
expected:
|
||||
if os == "android": [OK, TIMEOUT]
|
||||
[FragmentDirective interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[FragmentDirective interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[FragmentDirective interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[FragmentDirective interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[FragmentDirective interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[FragmentDirective interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[Stringification of document.fragmentDirective]
|
||||
expected: FAIL
|
||||
|
||||
[Document interface: document must inherit property "fragmentDirective" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[FragmentDirective must be primary interface of document.fragmentDirective]
|
||||
expected: FAIL
|
||||
|
||||
[Document interface: attribute fragmentDirective]
|
||||
expected: FAIL
|
|
@ -1,3 +1,7 @@
|
|||
[iframe-scroll.sub.html]
|
||||
expected: TIMEOUT
|
||||
[CROSS-ORIGIN: Text directive in iframe doesn't bubble to outer frame.]
|
||||
expected: TIMEOUT
|
||||
|
||||
[SAME-ORIGIN: Text directive in iframe bubbles to outer frame.]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
[Navigate same-origin iframe via window.location]
|
||||
expected: FAIL
|
||||
|
||||
[Navigate cross-origin iframe via window.location]
|
||||
expected: FAIL
|
||||
|
||||
[Cross-origin with element-id fallback]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -12,16 +12,10 @@
|
|||
if os == "android": [TIMEOUT, NOTRUN]
|
||||
NOTRUN
|
||||
|
||||
[Text directive blocked in non-html.js]
|
||||
expected:
|
||||
if os == "linux": [PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[Text directive blocked in non-html.css]
|
||||
expected:
|
||||
if os == "linux": [PASS, TIMEOUT]
|
||||
|
||||
[Text directive blocked in text/javascript]
|
||||
[Text directive allowed in text/html]
|
||||
expected: FAIL
|
||||
|
||||
[Text directive blocked in application/json]
|
||||
expected: TIMEOUT
|
||||
expected:
|
||||
if os == "android": PASS
|
||||
TIMEOUT
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
[percent-encoding.html]
|
||||
[Test navigation with fragment: Percent-encoding limited to two digits..]
|
||||
expected:
|
||||
if os == "win": [OK, TIMEOUT]
|
||||
[Test navigation with fragment: Percent-encoded "%" char..]
|
||||
expected: FAIL
|
||||
|
||||
[Test navigation with fragment: Single digit percent-encoding is invalid..]
|
||||
expected:
|
||||
if os == "win": [PASS, TIMEOUT]
|
||||
if os == "android": [PASS, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Percent-encoding limited to two digits..]
|
||||
expected:
|
||||
if os == "win": [FAIL, NOTRUN]
|
||||
FAIL
|
||||
|
||||
[Test navigation with fragment: Percent-encoded "%%F".]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "linux": [FAIL, TIMEOUT, NOTRUN]
|
||||
FAIL
|
||||
|
||||
[Test navigation with fragment: Percent-encoding multibyte codepoint (CHECKMARK)..]
|
||||
expected: FAIL
|
||||
|
||||
[Test navigation with fragment: Percent char without hex digits is invalid..]
|
||||
expected: [FAIL, PASS]
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "linux": [FAIL, TIMEOUT, NOTRUN]
|
||||
FAIL
|
||||
|
||||
[Test navigation with fragment: Percent char followed by percent char is invalid..]
|
||||
expected: [FAIL, PASS]
|
||||
expected:
|
||||
if os == "android": [PASS, TIMEOUT]
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
[same-document-tests.html]
|
||||
expected:
|
||||
[OK, TIMEOUT]
|
||||
if os == "android": [OK, TIMEOUT]
|
||||
[Basic text directive navigation]
|
||||
expected: FAIL
|
||||
|
||||
[Basic element id fallback]
|
||||
expected: FAIL
|
||||
|
||||
[Malformed text directive element id fallback]
|
||||
expected: [FAIL, TIMEOUT]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
[scroll-to-text-fragment-api.html]
|
||||
disabled:
|
||||
if (os == "win") and (processor == "x86_64"): random test
|
||||
[Scroll to text is feature detectable via document.fragmentDirective]
|
||||
expected: FAIL
|
||||
|
||||
[Setting document.fragmentDirective has no effect]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
[scroll-to-text-fragment-same-doc.html]
|
||||
[Activated for same-document window.location setter]
|
||||
expected: FAIL
|
||||
|
||||
[Activated for same-document window.location.replace]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
[scroll-to-text-fragment-security.sub.html]
|
||||
expected: [OK, TIMEOUT]
|
||||
expected:
|
||||
if os == "android": [TIMEOUT, OK]
|
||||
[OK, TIMEOUT]
|
||||
[Test that a text fragment directive requires a user activation (user_activation=true).]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test that a text fragment directive is not activated within an iframe.]
|
||||
expected: FAIL
|
||||
|
||||
[Test that a text fragment directive is not activated when there is a window opener (noopener=false).]
|
||||
expected: FAIL
|
||||
|
||||
[Test that a text fragment directive is not activated when there is a window opener (noopener=true).]
|
||||
expected: [FAIL, PASS]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test that a text fragment directive requires a user activation (user_activation=false).]
|
||||
expected: FAIL
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test that a text fragment directive requires a user activation (user_activation=true).]
|
||||
expected: [FAIL, PASS]
|
||||
[Test that a text fragment directive is not activated within an iframe.]
|
||||
expected:
|
||||
if os == "linux": [FAIL, NOTRUN, TIMEOUT]
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test that a text fragment directive is not activated when there is a window opener (noopener=false).]
|
||||
expected:
|
||||
if os == "linux": [FAIL, NOTRUN, TIMEOUT]
|
||||
if os == "android": [TIMEOUT, FAIL, NOTRUN]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
|
|
@ -1,84 +1,275 @@
|
|||
[scroll-to-text-fragment.html]
|
||||
[Test navigation with fragment: Text directive with invalid syntax (context terms without "-") should not parse as a text directive.]
|
||||
expected: 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
|
||||
|
||||
expected:
|
||||
if os == "mac": [OK, TIMEOUT]
|
||||
if os == "win": [OK, TIMEOUT]
|
||||
[TIMEOUT, OK]
|
||||
[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
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[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: FAIL
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Generic fragment directive with existing element fragment should scroll to element.]
|
||||
expected: FAIL
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with prefix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Non-matching text directive with existing element fragment should scroll to element.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should match when context terms are separated by node boundaries.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Non-matching text directive with nonexistent element fragment should not match and not scroll.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Non-whole-word exact text with spaces should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with prefix and suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should not scroll to hidden text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Fragment directive with percent encoded non-ASCII unicode character should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple match text directive disambiguated by prefix should match the prefixed text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with no context should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Uppercase TEXT directive should not parse as a text directive.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with prefix and nonmatching suffix should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple non-whole-word exact texts should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should match text within shadow DOM.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with non-matching startText should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive with invalid syntax (context terms without "-") should not parse as a text directive.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple match text directive disambiguated by prefix and suffix should match the text with the given context.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive with existing element fragment should match and scroll into view text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with prefix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive with nonexistent element fragment should match and scroll into view text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with nonmatching prefix and matching suffix should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with prefix and suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Fragment directive with all TextMatchChars should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with percent encoded spaces should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[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, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should not scroll to display none text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple text directives and a non-text directive should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple matching exact texts should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with non-matching endText should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with no context should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should horizontally scroll into view.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive followed by non-text directive should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Fragment directive with percent encoded syntactical characters "&,-" should match text.]
|
||||
expected:
|
||||
if os == "android": [TIMEOUT, NOTRUN, FAIL]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Non-matching text directive with existing element fragment should scroll to element.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Generic fragment directive with existing element fragment should scroll to element.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with prefix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple match text directive disambiguated by suffix should match the suffixed text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should match when context terms are separated by node boundaries.]
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Test navigation with fragment: Non-matching text directive with nonexistent element fragment should not match and not scroll.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Non-whole-word exact text with spaces should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with prefix and suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should not scroll to hidden text.]
|
||||
expected:
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Test navigation with fragment: Multiple match text directive disambiguated by prefix should match the prefixed text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with no context should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with prefix and nonmatching suffix should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple non-whole-word exact texts should not match.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple text directives and a non-text directive should match text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should match text within shadow DOM.]
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Test navigation with fragment: Text range with non-matching startText should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive with invalid syntax (context terms without "-") should not parse as a text directive.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive with existing element fragment should match and scroll into view text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple match text directive disambiguated by prefix and suffix should match the text with the given context.]
|
||||
expected:
|
||||
if os == "linux": [TIMEOUT, FAIL, NOTRUN]
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with non-matching endText should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with prefix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive with nonexistent element fragment should match and scroll into view text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text range with nonmatching prefix and matching suffix should not match.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with prefix and suffix should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Fragment directive with all TextMatchChars should match text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with percent encoded spaces should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[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:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should not scroll to display none text.]
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Test navigation with fragment: Uppercase TEXT directive should not parse as a text directive.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Multiple matching exact texts should match text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Fragment directive with percent encoded non-ASCII unicode character should match text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with no context should match text.]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Text directive should horizontally scroll into view.]
|
||||
expected:
|
||||
if os == "win": [FAIL, TIMEOUT, NOTRUN]
|
||||
if os == "mac": [FAIL, TIMEOUT, NOTRUN]
|
||||
[NOTRUN, FAIL, TIMEOUT]
|
||||
|
||||
[Test navigation with fragment: Text directive followed by non-text directive should match text.]
|
||||
expected:
|
||||
if os == "android": [NOTRUN, FAIL, TIMEOUT]
|
||||
[FAIL, TIMEOUT, NOTRUN]
|
||||
|
||||
[Test navigation with fragment: Exact text with prefix and suffix and query equals prefix..]
|
||||
expected: [FAIL, TIMEOUT, NOTRUN]
|
||||
|
|
|
@ -67,7 +67,6 @@ mozannotation_server = { path = "../../../crashreporter/mozannotation_server",
|
|||
gecko-profiler = { path = "../../../../tools/profiler/rust-api"}
|
||||
midir_impl = { path = "../../../../dom/midi/midir_impl", optional = true }
|
||||
dom = { path = "../../../../dom/base/rust" }
|
||||
dom_fragmentdirectives = { path="../../../../dom/base/fragmentdirectives" }
|
||||
origin-trials-ffi = { path = "../../../../dom/origin-trials/ffi" }
|
||||
jog = { path = "../../../components/glean/bindings/jog" }
|
||||
dap_ffi = { path = "../../../components/telemetry/dap/ffi" }
|
||||
|
|
|
@ -24,7 +24,6 @@ extern crate cubeb_coreaudio;
|
|||
#[cfg(feature = "cubeb_pulse_rust")]
|
||||
extern crate cubeb_pulse;
|
||||
extern crate data_storage;
|
||||
extern crate dom_fragmentdirectives;
|
||||
extern crate encoding_glue;
|
||||
extern crate fog_control;
|
||||
extern crate gecko_profiler;
|
||||
|
|
|
@ -2487,7 +2487,6 @@ STATIC_ATOMS = [
|
|||
PseudoElementAtom("PseudoElement_firstLine", ":first-line"),
|
||||
PseudoElementAtom("PseudoElement_highlight", ":highlight"),
|
||||
PseudoElementAtom("PseudoElement_selection", ":selection"),
|
||||
PseudoElementAtom("PseudoElement_targetText", ":target-text"),
|
||||
PseudoElementAtom("PseudoElement_mozFocusInner", ":-moz-focus-inner"),
|
||||
PseudoElementAtom("PseudoElement_mozNumberSpinBox", ":-moz-number-spin-box"),
|
||||
PseudoElementAtom("PseudoElement_mozNumberSpinUp", ":-moz-number-spin-up"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче