- a/Source/WebDriver/CMakeLists.txt -7 / +25 lines
Lines 26-40 endif () a/Source/WebDriver/CMakeLists.txt_sec1
26
26
27
27
28
set(WebDriver_SCRIPTS
28
set(WebDriver_SCRIPTS
29
    ${WEBKIT_DIR}/UIProcess/Automation/atoms/ElementAttribute.js
29
    ${WebDriver_DERIVED_SOURCES_DIR}/ElementAttribute.js
30
    ${WEBKIT_DIR}/UIProcess/Automation/atoms/ElementDisplayed.js
30
    ${WebDriver_DERIVED_SOURCES_DIR}/ElementDisplayed.js
31
    ${WEBKIT_DIR}/UIProcess/Automation/atoms/ElementEnabled.js
31
    ${WebDriver_DERIVED_SOURCES_DIR}/ElementEnabled.js
32
    ${WEBKIT_DIR}/UIProcess/Automation/atoms/EnterFullscreen.js
32
    ${WebDriver_DERIVED_SOURCES_DIR}/ElementText.js
33
    ${WEBKIT_DIR}/UIProcess/Automation/atoms/FindNodes.js
33
    ${WebDriver_DERIVED_SOURCES_DIR}/EnterFullscreen.js
34
    ${WEBKIT_DIR}/UIProcess/Automation/atoms/FormElementClear.js
34
    ${WebDriver_DERIVED_SOURCES_DIR}/FindNodes.js
35
    ${WEBKIT_DIR}/UIProcess/Automation/atoms/FormSubmit.js
35
    ${WebDriver_DERIVED_SOURCES_DIR}/FormElementClear.js
36
    ${WebDriver_DERIVED_SOURCES_DIR}/FormSubmit.js
36
)
37
)
37
38
39
macro(GENERATE_ATOMS _inputs)
40
    foreach (_file IN ITEMS ${_inputs})
41
        get_filename_component(_name ${_file} NAME)
42
        add_custom_command(
43
            OUTPUT ${_file}
44
            MAIN_DEPENDENCY ${WEBKIT_DIR}/Scripts/generate-automation-atom.py
45
            DEPENDS
46
                ${WEBKIT_DIR}/UIProcess/Automation/atoms/${_name}
47
                ${WEBKIT_DIR}/UIProcess/Automation/atoms/utils.js
48
            COMMAND ${PYTHON_EXECUTABLE} ${WEBKIT_DIR}/Scripts/generate-automation-atom.py ${WEBKIT_DIR}/UIProcess/Automation/atoms/${_name} ${_file}
49
            VERBATIM
50
        )
51
    endforeach ()
52
endmacro()
53
54
GENERATE_ATOMS("${WebDriver_SCRIPTS}")
55
38
MAKE_JS_FILE_ARRAYS(
56
MAKE_JS_FILE_ARRAYS(
39
    ${WebDriver_DERIVED_SOURCES_DIR}/WebDriverAtoms.cpp
57
    ${WebDriver_DERIVED_SOURCES_DIR}/WebDriverAtoms.cpp
40
    ${WebDriver_DERIVED_SOURCES_DIR}/WebDriverAtoms.h
58
    ${WebDriver_DERIVED_SOURCES_DIR}/WebDriverAtoms.h
- a/Source/WebDriver/ChangeLog +14 lines
Lines 1-3 a/Source/WebDriver/ChangeLog_sec1
1
2022-02-04  Carlos Garcia Campos  <cgarcia@igalia.com>
2
3
        WebDriver: add a javascript atom to get the visible text
4
        https://bugs.webkit.org/show_bug.cgi?id=174617
5
        <rdar://problem/32307461>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        Generate the JavaScript atoms using the new command and use the new atom to get element text.
10
11
        * CMakeLists.txt:
12
        * Session.cpp:
13
        (WebDriver::Session::getElementText):
14
1
2022-02-02  Youenn Fablet  <youenn@apple.com>
15
2022-02-02  Youenn Fablet  <youenn@apple.com>
2
16
3
        Clarify that some UUID routines are dedicated to UUID v4
17
        Clarify that some UUID routines are dedicated to UUID v4
- a/Source/WebDriver/Session.cpp -1 / +1 lines
Lines 1278-1284 void Session::getElementText(const String& elementID, Function<void (CommandResu a/Source/WebDriver/Session.cpp_sec1
1278
        if (m_currentBrowsingContext)
1278
        if (m_currentBrowsingContext)
1279
            parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1279
            parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1280
        // FIXME: Add an atom to properly implement this instead of just using innerText.
1280
        // FIXME: Add an atom to properly implement this instead of just using innerText.
1281
        parameters->setString("function"_s, "function(element) { return element.innerText.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g, '') }"_s);
1281
        parameters->setString("function"_s, StringImpl::createWithoutCopying(ElementTextJavaScript, sizeof(ElementTextJavaScript)));
1282
        parameters->setArray("arguments"_s, WTFMove(arguments));
1282
        parameters->setArray("arguments"_s, WTFMove(arguments));
1283
        m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1283
        m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1284
            if (response.isError || !response.responseObject) {
1284
            if (response.isError || !response.responseObject) {
- a/Source/WebKit/ChangeLog +41 lines
Lines 1-3 a/Source/WebKit/ChangeLog_sec1
1
2022-02-04  Carlos Garcia Campos  <cgarcia@igalia.com>
2
3
        WebDriver: add a javascript atom to get the visible text
4
        https://bugs.webkit.org/show_bug.cgi?id=174617
5
        <rdar://problem/32307461>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        Add a new JavaScript atom to get the visible text according to the spec. The new atom uses code from other atoms
10
        like ElementDisplayed and it's also used now by FindNodes one. The atoms are now autogenerated using a script to
11
        include the duplicated code from a common source utils.js.
12
13
        * Scripts/generate-automation-atom.py: Added.
14
        (collect_utils):
15
        (parse_utils):
16
        (append_functions):
17
        (main):
18
        * UIProcess/Automation/atoms/ElementDisplayed.js:
19
        (isShown.nodeIsElement): Deleted.
20
        (isShown.parentElementForElement): Deleted.
21
        (isShown.enclosingNodeOrSelfMatchingPredicate): Deleted.
22
        (isShown.enclosingElementOrSelfMatchingPredicate): Deleted.
23
        (isShown.cascadedStylePropertyForElement): Deleted.
24
        (isShown.elementSubtreeHasNonZeroDimensions): Deleted.
25
        (isShown): Deleted.
26
        (isShown.isElementSubtreeHiddenByOverflow): Deleted.
27
        * UIProcess/Automation/atoms/ElementText.js: Added.
28
        * UIProcess/Automation/atoms/FindNodes.js:
29
        (tryToFindNode):
30
        * UIProcess/Automation/atoms/utils.js: Added.
31
        (utils.nodeIsElement):
32
        (utils.enclosingNodeOrSelfMatchingPredicate):
33
        (utils.parentElementForElement):
34
        (utils.cascadedStylePropertyForElement):
35
        (elementSubtreeHasNonZeroDimensions):
36
        (isElementSubtreeHiddenByOverflow):
37
        (utils.isShown):
38
        (appendLines.currentLine):
39
        (appendLines):
40
        (utils.getText):
41
1
2022-02-03  Youenn Fablet  <youenn@apple.com>
42
2022-02-03  Youenn Fablet  <youenn@apple.com>
2
43
3
        SampleBufferDiplayLayer should not need to create IOSurfaces
44
        SampleBufferDiplayLayer should not need to create IOSurfaces
- a/Source/WebKit/Scripts/generate-automation-atom.py +95 lines
Line 0 a/Source/WebKit/Scripts/generate-automation-atom.py_sec1
1
#!/usr/bin/env python3
2
#
3
# Copyright (C) 2022 Igalia S.L.
4
#
5
# Redistribution and use in source and binary forms, with or without
6
# modification, are permitted provided that the following conditions
7
# are met:
8
# 1.  Redistributions of source code must retain the above copyright
9
#     notice, this list of conditions and the following disclaimer.
10
# 2.  Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
18
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25
import os
26
import re
27
import sys
28
29
30
def collect_utils(data):
31
    match = re.findall(r'(?P<destination>utils\.[A-Za-z_0-9]+)', data)
32
    return set(match)
33
34
35
def parse_utils(utils):
36
    functions = {}
37
    current_function = None
38
    with open(utils, "r") as fd:
39
        for line in fd.readlines():
40
            if line.startswith("utils."):
41
                current_function = line.split(" ", 1)[0]
42
                functions.setdefault(current_function, "")
43
            elif line.startswith("}"):
44
                functions[current_function] += line
45
                current_function = None
46
47
            if current_function is not None:
48
                functions[current_function] += line
49
    return functions
50
51
52
def append_functions(utils_data, util_functions, util_functions_impl, functions_written):
53
    for function in util_functions:
54
        if function in functions_written:
55
            continue
56
57
        function_impl = util_functions_impl[function]
58
        dependencies = collect_utils(function_impl)
59
        if function in dependencies:
60
            dependencies.remove(function)
61
        append_functions(utils_data, dependencies, util_functions_impl, functions_written)
62
63
        for line in function_impl.split("\n"):
64
            if not line.strip():
65
                utils_data.append(line)
66
            else:
67
                utils_data.append("    " + line)
68
        functions_written.append(function)
69
70
71
def main(args):
72
    input = args[0]
73
    output = args[1]
74
    utils = os.path.join(os.path.dirname(input), "utils.js")
75
76
    with open(input, "r") as fd:
77
        input_data = fd.read()
78
79
    util_functions = collect_utils(input_data)
80
81
    if util_functions:
82
        utils_data = ["    var utils = { };", ""]
83
        util_functions_impl = parse_utils(utils)
84
        functions_written = []
85
        append_functions(utils_data, util_functions, util_functions_impl, functions_written)
86
        input_data = input_data.replace('"use strict";\n', '"use strict";\n\n' + "\n".join(utils_data))
87
88
    with open(output, "w") as fd:
89
        fd.write(input_data)
90
91
    return 0
92
93
94
if __name__ == '__main__':
95
    sys.exit(main(sys.argv[1:]))
- a/Source/WebKit/UIProcess/Automation/atoms/ElementDisplayed.js -172 / +2 lines
Lines 23-200 a/Source/WebKit/UIProcess/Automation/atoms/ElementDisplayed.js_sec1
23
 * THE POSSIBILITY OF SUCH DAMAGE.
23
 * THE POSSIBILITY OF SUCH DAMAGE.
24
 */
24
 */
25
25
26
function isShown(element) {
26
function(element) {
27
    "use strict";
27
    "use strict";
28
28
29
    function nodeIsElement(node) {
29
    return utils.isShown(element);
30
        return !!node && node.nodeType === Node.ELEMENT_NODE;
31
        
32
    }
33
34
    function parentElementForElement(element) {
35
        if (!element)
36
            return null;
37
38
        return enclosingNodeOrSelfMatchingPredicate(element.parentNode, nodeIsElement);
39
    }
40
41
    function enclosingNodeOrSelfMatchingPredicate(targetNode, predicate) {
42
        for (let node = targetNode; node && node !== targetNode.getRootNode(); node = node.parentNode)
43
            if (predicate(node))
44
                return node;
45
46
        return null;
47
    }
48
49
    function enclosingElementOrSelfMatchingPredicate(targetElement, predicate) {
50
        for (let element = targetElement; element && element !== targetElement.getRootNode(); element = parentElementForElement(element))
51
            if (predicate(element))
52
                return element;
53
54
        return null;
55
    }
56
57
    function cascadedStylePropertyForElement(element, property) {
58
        if (!element || !property)
59
            return null;
60
61
        let computedStyle = window.getComputedStyle(element);
62
        let computedStyleProperty = computedStyle.getPropertyValue(property);
63
        if (computedStyleProperty && computedStyleProperty !== "inherit")
64
            return computedStyleProperty;
65
66
        // Ideally getPropertyValue would return the 'used' or 'actual' value, but
67
        // it doesn't for legacy reasons. So we need to do our own poor man's cascade.
68
        // Fall back to the first non-'inherit' value found in an ancestor.
69
        // In any case, getPropertyValue will not return 'initial'.
70
71
        // FIXME: will this incorrectly inherit non-inheritable CSS properties?
72
        // I think all important non-inheritable properties (width, height, etc.)
73
        // for our purposes here are specially resolved, so this may not be an issue.
74
        // Specification is here: https://drafts.csswg.org/cssom/#resolved-values
75
        let parentElement = parentElementForElement(element);
76
        return cascadedStylePropertyForElement(parentElement, property);
77
    }
78
79
    function elementSubtreeHasNonZeroDimensions(element) {
80
        let boundingBox = element.getBoundingClientRect();
81
        if (boundingBox.width > 0 && boundingBox.height > 0)
82
            return true;
83
84
        // Paths can have a zero width or height. Treat them as shown if the stroke width is positive.
85
        if (element.tagName.toUpperCase() === "PATH" && boundingBox.width + boundingBox.height > 0) {
86
            let strokeWidth = cascadedStylePropertyForElement(element, "stroke-width");
87
            return !!strokeWidth && (parseInt(strokeWidth, 10) > 0);
88
        }
89
90
        let cascadedOverflow = cascadedStylePropertyForElement(element, "overflow");
91
        if (cascadedOverflow === "hidden")
92
            return false;
93
94
        // If the container's overflow is not hidden and it has zero size, consider the
95
        // container to have non-zero dimensions if a child node has non-zero dimensions.
96
        return Array.from(element.childNodes).some((childNode) => {
97
            if (childNode.nodeType === Node.TEXT_NODE)
98
                return true;
99
100
            if (nodeIsElement(childNode))
101
                return elementSubtreeHasNonZeroDimensions(childNode);
102
103
            return false;
104
        });
105
    }
106
107
    function elementOverflowsContainer(element) {
108
        let cascadedOverflow = cascadedStylePropertyForElement(element, "overflow");
109
        if (cascadedOverflow !== "hidden")
110
            return false;
111
112
        // FIXME: this needs to take into account the scroll position of the element,
113
        // the display modes of it and its ancestors, and the container it overflows.
114
        // See Selenium's bot.dom.getOverflowState atom for an exhaustive list of edge cases.
115
        return true;
116
    }
117
118
    function isElementSubtreeHiddenByOverflow(element) {
119
        if (!element)
120
            return false;
121
122
        if (!elementOverflowsContainer(element))
123
            return false;
124
125
        if (!element.childNodes.length)
126
            return false;
127
128
        // This element's subtree is hidden by overflow if all child subtrees are as well.
129
        return Array.from(element.childNodes).every((childNode) => {
130
            // Returns true if the child node is overflowed or otherwise hidden.
131
            // Base case: not an element, has zero size, scrolled out, or doesn't overflow container.
132
            if (!nodeIsElement(childNode))
133
                return true;
134
135
            if (!elementSubtreeHasNonZeroDimensions(childNode))
136
                return true;
137
138
            // Recurse.
139
            return isElementSubtreeHiddenByOverflow(childNode);
140
        });
141
    }
142
143
    // This is a partial reimplementation of Selenium's "element is displayed" algorithm.
144
    // When the W3C specification's algorithm stabilizes, we should implement that.
145
146
    if (!(element instanceof Element))
147
        throw new Error("Cannot check the displayedness of a non-Element argument.");
148
149
    // If this command is misdirected to the wrong document, treat it as not shown.
150
    if (!document.contains(element))
151
        return false;
152
153
    // Special cases for specific tag names.
154
    switch (element.tagName.toUpperCase()) {
155
    case "BODY":
156
        return true;
157
158
    case "SCRIPT":
159
    case "NOSCRIPT":
160
        return false;
161
162
    case "OPTGROUP":
163
    case "OPTION":
164
        // Option/optgroup are considered shown if the containing <select> is shown.
165
        let enclosingSelectElement = enclosingNodeOrSelfMatchingPredicate(element, (e) => e.tagName.toUpperCase() === "SELECT");
166
        return isShown(enclosingSelectElement);
167
168
    case "INPUT":
169
        // <input type="hidden"> is considered not shown.
170
        if (element.type === "hidden")
171
            return false;
172
        break;
173
174
    case "MAP":
175
        // FIXME: Selenium has special handling for <map> elements. We don't do anything now.
176
177
    default:
178
        break;
179
    }
180
181
    if (cascadedStylePropertyForElement(element, "visibility") !== "visible")
182
        return false;
183
184
    let hasAncestorWithZeroOpacity = !!enclosingElementOrSelfMatchingPredicate(element, (e) => {
185
        return Number(cascadedStylePropertyForElement(e, "opacity")) === 0;
186
    });
187
    let hasAncestorWithDisplayNone = !!enclosingElementOrSelfMatchingPredicate(element, (e) => {
188
        return cascadedStylePropertyForElement(e, "display") === "none";
189
    });
190
    if (hasAncestorWithZeroOpacity || hasAncestorWithDisplayNone)
191
        return false;
192
193
    if (!elementSubtreeHasNonZeroDimensions(element))
194
        return false;
195
196
    if (isElementSubtreeHiddenByOverflow(element))
197
        return false;
198
199
    return true;
200
}
30
}
- a/Source/WebKit/UIProcess/Automation/atoms/ElementText.js +30 lines
Line 0 a/Source/WebKit/UIProcess/Automation/atoms/ElementText.js_sec1
1
/*
2
 * Copyright (C) 2022 Igalia S.L.
3
 *
4
 * Redistribution and use in source and binary forms, with or without
5
 * modification, are permitted provided that the following conditions
6
 * are met:
7
 * 1. Redistributions of source code must retain the above copyright
8
 *    notice, this list of conditions and the following disclaimer.
9
 * 2. Redistributions in binary form must reproduce the above copyright
10
 *    notice, this list of conditions and the following disclaimer in the
11
 *    documentation and/or other materials provided with the distribution.
12
 *
13
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23
 * THE POSSIBILITY OF SUCH DAMAGE.
24
 */
25
26
function(element) {
27
    "use strict";
28
29
    return utils.getText(element);
30
}
- a/Source/WebKit/UIProcess/Automation/atoms/FindNodes.js -2 / +4 lines
Lines 24-29 a/Source/WebKit/UIProcess/Automation/atoms/FindNodes.js_sec1
24
 */
24
 */
25
25
26
function(strategy, ancestorElement, query, firstResultOnly, timeoutDuration, callback) {
26
function(strategy, ancestorElement, query, firstResultOnly, timeoutDuration, callback) {
27
    "use strict";
28
27
    ancestorElement = ancestorElement || document;
29
    ancestorElement = ancestorElement || document;
28
30
29
    switch (strategy) {
31
    switch (strategy) {
Lines 67-73 function(strategy, ancestorElement, query, firstResultOnly, timeoutDuration, cal a/Source/WebKit/UIProcess/Automation/atoms/FindNodes.js_sec2
67
            case "link text":
69
            case "link text":
68
                let linkTextResult = [];
70
                let linkTextResult = [];
69
                for (let link of ancestorElement.getElementsByTagName("a")) {
71
                for (let link of ancestorElement.getElementsByTagName("a")) {
70
                    if (link.text.trim() == query) {
72
                    if (utils.getText(link).trim() == query) {
71
                        linkTextResult.push(link);
73
                        linkTextResult.push(link);
72
                        if (firstResultOnly)
74
                        if (firstResultOnly)
73
                            break;
75
                            break;
Lines 80-86 function(strategy, ancestorElement, query, firstResultOnly, timeoutDuration, cal a/Source/WebKit/UIProcess/Automation/atoms/FindNodes.js_sec3
80
            case "partial link text":
82
            case "partial link text":
81
                let partialLinkResult = [];
83
                let partialLinkResult = [];
82
                for (let link of ancestorElement.getElementsByTagName("a")) {
84
                for (let link of ancestorElement.getElementsByTagName("a")) {
83
                    if (link.text.includes(query)) {
85
                    if (utils.getText(link).includes(query)) {
84
                        partialLinkResult.push(link);
86
                        partialLinkResult.push(link);
85
                        if (firstResultOnly)
87
                        if (firstResultOnly)
86
                            break;
88
                            break;
- a/Source/WebKit/UIProcess/Automation/atoms/utils.js +313 lines
Line 0 a/Source/WebKit/UIProcess/Automation/atoms/utils.js_sec1
1
/*
2
 * Copyright (C) 2022 Igalia S.L.
3
 * Copyright (C) 2017 Apple Inc. All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions
7
 * are met:
8
 * 1. Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 * 2. Redistributions in binary form must reproduce the above copyright
11
 *    notice, this list of conditions and the following disclaimer in the
12
 *    documentation and/or other materials provided with the distribution.
13
 *
14
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24
 * THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
27
var utils = { };
28
29
utils.nodeIsElement = function(node) {
30
    return !!node && node.nodeType === Node.ELEMENT_NODE;
31
}
32
33
utils.enclosingNodeOrSelfMatchingPredicate = function(targetNode, predicate) {
34
    for (let node = targetNode; node && node !== targetNode.getRootNode(); node = node.parentNode)
35
        if (predicate(node))
36
            return node;
37
38
    return null;
39
}
40
41
utils.parentElementForElement = function(element) {
42
    if (!element)
43
        return null;
44
45
    return utils.enclosingNodeOrSelfMatchingPredicate(element.parentNode, utils.nodeIsElement);
46
}
47
48
utils.cascadedStylePropertyForElement = function(element, property) {
49
    if (!element || !property)
50
        return null;
51
52
    let computedStyle = window.getComputedStyle(element);
53
    let computedStyleProperty = computedStyle.getPropertyValue(property);
54
    if (computedStyleProperty && computedStyleProperty !== "inherit")
55
        return computedStyleProperty;
56
57
    // Ideally getPropertyValue would return the 'used' or 'actual' value, but
58
    // it doesn't for legacy reasons. So we need to do our own poor man's cascade.
59
    // Fall back to the first non-'inherit' value found in an ancestor.
60
    // In any case, getPropertyValue will not return 'initial'.
61
62
    // FIXME: will this incorrectly inherit non-inheritable CSS properties?
63
    // I think all important non-inheritable properties (width, height, etc.)
64
    // for our purposes here are specially resolved, so this may not be an issue.
65
    // Specification is here: https://drafts.csswg.org/cssom/#resolved-values
66
    let parentElement = utils.parentElementForElement(element);
67
    return utils.cascadedStylePropertyForElement(parentElement, property);
68
}
69
70
utils.isShown = function(element) {
71
    function enclosingElementOrSelfMatchingPredicate(targetElement, predicate) {
72
        for (let element = targetElement; element && element !== targetElement.getRootNode(); element = utils.parentElementForElement(element))
73
            if (predicate(element))
74
                return element;
75
76
        return null;
77
    }
78
79
    function elementSubtreeHasNonZeroDimensions(element) {
80
        let boundingBox = element.getBoundingClientRect();
81
        if (boundingBox.width > 0 && boundingBox.height > 0)
82
            return true;
83
84
        // Paths can have a zero width or height. Treat them as shown if the stroke width is positive.
85
        if (element.tagName.toUpperCase() === "PATH" && boundingBox.width + boundingBox.height > 0) {
86
            let strokeWidth = utils.cascadedStylePropertyForElement(element, "stroke-width");
87
            return !!strokeWidth && (parseInt(strokeWidth, 10) > 0);
88
        }
89
90
        let cascadedOverflow = utils.cascadedStylePropertyForElement(element, "overflow");
91
        if (cascadedOverflow === "hidden")
92
            return false;
93
94
        // If the container's overflow is not hidden and it has zero size, consider the
95
        // container to have non-zero dimensions if a child node has non-zero dimensions.
96
        return Array.from(element.childNodes).some((childNode) => {
97
            if (childNode.nodeType === Node.TEXT_NODE)
98
                return true;
99
100
            if (utils.nodeIsElement(childNode))
101
                return elementSubtreeHasNonZeroDimensions(childNode);
102
103
            return false;
104
        });
105
    }
106
107
    function elementOverflowsContainer(element) {
108
        let cascadedOverflow = utils.cascadedStylePropertyForElement(element, "overflow");
109
        if (cascadedOverflow !== "hidden")
110
            return false;
111
112
        // FIXME: this needs to take into account the scroll position of the element,
113
        // the display modes of it and its ancestors, and the container it overflows.
114
        // See Selenium's bot.dom.getOverflowState atom for an exhaustive list of edge cases.
115
        return true;
116
    }
117
118
    function isElementSubtreeHiddenByOverflow(element) {
119
        if (!element)
120
            return false;
121
122
        if (!elementOverflowsContainer(element))
123
            return false;
124
125
        if (!element.childNodes.length)
126
            return false;
127
128
        // This element's subtree is hidden by overflow if all child subtrees are as well.
129
        return Array.from(element.childNodes).every((childNode) => {
130
            // Returns true if the child node is overflowed or otherwise hidden.
131
            // Base case: not an element, has zero size, scrolled out, or doesn't overflow container.
132
            if (!utils.nodeIsElement(childNode))
133
                return true;
134
135
            if (!elementSubtreeHasNonZeroDimensions(childNode))
136
                return true;
137
138
            // Recurse.
139
            return isElementSubtreeHiddenByOverflow(childNode);
140
        });
141
    }
142
143
    // This is a partial reimplementation of Selenium's "element is displayed" algorithm.
144
    // When the W3C specification's algorithm stabilizes, we should implement that.
145
146
    if (!(element instanceof Element))
147
        throw new Error("Cannot check the displayedness of a non-Element argument.");
148
149
    // If this command is misdirected to the wrong document, treat it as not shown.
150
    if (!document.contains(element))
151
        return false;
152
153
    // Special cases for specific tag names.
154
    switch (element.tagName.toUpperCase()) {
155
    case "BODY":
156
        return true;
157
158
    case "SCRIPT":
159
    case "NOSCRIPT":
160
        return false;
161
162
    case "OPTGROUP":
163
    case "OPTION":
164
        // Option/optgroup are considered shown if the containing <select> is shown.
165
        let enclosingSelectElement = utils.enclosingNodeOrSelfMatchingPredicate(element, (e) => e.tagName.toUpperCase() === "SELECT");
166
        return utils.isShown(enclosingSelectElement);
167
168
    case "INPUT":
169
        // <input type="hidden"> is considered not shown.
170
        if (element.type === "hidden")
171
            return false;
172
        break;
173
174
    case "MAP":
175
        // FIXME: Selenium has special handling for <map> elements. We don't do anything now.
176
177
    default:
178
        break;
179
    }
180
181
    if (utils.cascadedStylePropertyForElement(element, "visibility") !== "visible")
182
        return false;
183
184
    let hasAncestorWithZeroOpacity = !!enclosingElementOrSelfMatchingPredicate(element, (e) => {
185
        return Number(utils.cascadedStylePropertyForElement(e, "opacity")) === 0;
186
    });
187
    let hasAncestorWithDisplayNone = !!enclosingElementOrSelfMatchingPredicate(element, (e) => {
188
        return utils.cascadedStylePropertyForElement(e, "display") === "none";
189
    });
190
    if (hasAncestorWithZeroOpacity || hasAncestorWithDisplayNone)
191
        return false;
192
193
    if (!elementSubtreeHasNonZeroDimensions(element))
194
        return false;
195
196
    if (isElementSubtreeHiddenByOverflow(element))
197
        return false;
198
199
    return true;
200
}
201
202
utils.getText = function(element) {
203
    var lines = [];
204
205
    function appendLinesFromTextNode(textNode, whitespace, textTransform) {
206
        // Remove zero-width characters.
207
        let text = textNode.nodeValue.replace(/[\u200b\u200e\u200f]/g, '');
208
209
        // Canonicalize the new lines.
210
        text = text.replace(/(\r\n|\r|\n)/g, '\n');
211
212
        let collapseAll = true;
213
        switch (whitespace) {
214
        case "normal":
215
        case "nowrap":
216
            // Collapse new lines.
217
            text = text.replace(/\n/g, ' ');
218
            break;
219
        case "pre":
220
        case "pre-wrap":
221
            // Convert all breaking spaces to be non-breaking.
222
            text = text.replace(/[ \f\t\v\u2028\u2029]/g, '\xa0');
223
            collapseAll = false;
224
            break;
225
        }
226
227
        if (collapseAll) {
228
            // Collapse all breaking spaces.
229
            text = text.replace(/[\ \f\t\v\u2028\u2029]+/g, ' ');
230
        }
231
232
        switch (textTransform) {
233
        case "capitalize":
234
            text = text.replace(/(^|[^\d\p{L}\p{S}])([\p{Ll}|\p{S}])/gu, function() {
235
                return arguments[1] + arguments[2].toUpperCase();
236
            });
237
            break;
238
        case "uppercase":
239
            text = text.toUpperCase();
240
            break;
241
        case "lowercase":
242
            text = text.toLowerCase();
243
            break;
244
        }
245
246
        let currentLine = lines.pop() || '';
247
        if (currentLine.endsWith(' ') && text.startsWith(' '))
248
            text = text.substr(1);
249
        lines.push(currentLine + text);
250
    }
251
252
    function appendLines(element) {
253
        function currentLine() {
254
            return lines[lines.length - 1] || '';
255
        }
256
257
        let isTableCell = false, display = null, isBlock = false;
258
        if (element.tagName.toUpperCase() === "BR")
259
            lines.push('');
260
        else {
261
            isTableCell = element.tagName.toUpperCase() === "TD";
262
            display = utils.cascadedStylePropertyForElement(element, "display");
263
            isBlock = !isTableCell
264
                && !(display === "inline"
265
                     || display === "inline-block"
266
                     || display === "inline-table"
267
                     || display === "none"
268
                     || display === "table-cell"
269
                     || display === "table-column"
270
                     || display === "table-column-group");
271
            let previousElementSibling = element.previousElementSibling;
272
            let previousDisplay = previousElementSibling ? utils.cascadedStylePropertyForElement(previousElementSibling, "display") : '';
273
            let elementFloat = utils.cascadedStylePropertyForElement(element, "float") || utils.cascadedStylePropertyForElement(element, "cssFloat");
274
            let runIntoThis = previousDisplay == "run-in" && elementFloat == "none";
275
            if (isBlock && !runIntoThis && /^[\s\xa0]*$/.test(currentLine()))
276
                lines.push('');
277
        }
278
279
        let shown = utils.isShown(element);
280
        let whitespace = null, textTransform = null;
281
        if (shown) {
282
            whitespace = utils.cascadedStylePropertyForElement(element, "white-space");
283
            textTransform = utils.cascadedStylePropertyForElement(element, "text-transform");
284
        }
285
286
        Array.from(element.childNodes).forEach((childNode) => {
287
            switch (childNode.nodeType) {
288
            case Node.TEXT_NODE:
289
                if (shown)
290
                    appendLinesFromTextNode(childNode, whitespace, textTransform);
291
                break;
292
            case Node.ELEMENT_NODE:
293
                appendLines(childNode);
294
                break;
295
            }
296
        });
297
298
        let line = currentLine();
299
        if ((isTableCell || display == "table-cell") && line && line.endsWith(' '))
300
            lines[lines.length - 1] += ' ';
301
302
        if (isBlock && display != "run-in" && /^[\s\xa0]*$/.test(line))
303
            lines.push('');
304
    }
305
306
    appendLines(element);
307
308
    lines = lines.map((str) => str.replace(/^[^\S\xa0]+|[^\S\xa0]+$/g, ''));
309
    let trimmed = lines.join('\n').replace(/^[^\S\xa0]+|[^\S\xa0]+$/g, '');
310
311
    // Replace non-breakable spaces with regular ones.
312
    return trimmed.replace(/\xa0/g, ' ');
313
}
- a/WebDriverTests/ChangeLog +12 lines
Lines 1-3 a/WebDriverTests/ChangeLog_sec1
1
2022-02-04  Carlos Garcia Campos  <cgarcia@igalia.com>
2
3
        WebDriver: add a javascript atom to get the visible text
4
        https://bugs.webkit.org/show_bug.cgi?id=174617
5
        <rdar://problem/32307461>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        Remove expectations of tests that are now passing.
10
11
        * TestExpectations.json:
12
1
2022-02-03  Carlos Garcia Campos  <cgarcia@igalia.com>
13
2022-02-03  Carlos Garcia Campos  <cgarcia@igalia.com>
2
14
3
        Unreviewed. Add another missing file after r288871.
15
        Unreviewed. Add another missing file after r288871.
- a/WebDriverTests/TestExpectations.json -60 lines
Lines 133-141 a/WebDriverTests/TestExpectations.json_sec1
133
    },
133
    },
134
    "imported/selenium/py/test/selenium/webdriver/common/select_class_tests.py": {
134
    "imported/selenium/py/test/selenium/webdriver/common/select_class_tests.py": {
135
        "subtests": {
135
        "subtests": {
136
            "testSelectByVisibleTextShouldNormalizeSpaces": {
137
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
138
            },
139
            "testSelectByIndexMultiple": {
136
            "testSelectByIndexMultiple": {
140
                "expected": {"all": {"slow": true}}
137
                "expected": {"all": {"slow": true}}
141
            },
138
            },
Lines 149-168 a/WebDriverTests/TestExpectations.json_sec2
149
    },
146
    },
150
    "imported/selenium/py/test/selenium/webdriver/common/text_handling_tests.py": {
147
    "imported/selenium/py/test/selenium/webdriver/common/text_handling_tests.py": {
151
        "subtests": {
148
        "subtests": {
152
            "testShouldConvertANonBreakingSpaceIntoANormalSpaceCharacter": {
153
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
154
            },
155
            "testShouldTreatANonBreakingSpaceAsAnyOtherWhitespaceCharacterWhenCollapsingWhitespace": {
156
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
157
            },
158
            "testShouldBeAbleToSetMoreThanOneLineOfTextInATextArea": {
149
            "testShouldBeAbleToSetMoreThanOneLineOfTextInATextArea": {
159
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
150
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
160
            },
151
            },
161
            "testShouldHandleSiblingBlockLevelElements": {
152
            "testShouldHandleSiblingBlockLevelElements": {
162
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
153
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
163
            },
164
            "testShouldOnlyIncludeVisibleText": {
165
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/174617"}}
166
            }
154
            }
167
        }
155
        }
168
    },
156
    },
Lines 839-856 a/WebDriverTests/TestExpectations.json_sec3
839
    },
827
    },
840
    "imported/w3c/webdriver/tests/find_element/find.py": {
828
    "imported/w3c/webdriver/tests/find_element/find.py": {
841
        "subtests": {
829
        "subtests": {
842
            "test_find_element_link_text[<a href=#>link<br>text</a>-link\\ntext]": {
843
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
844
            },
845
            "test_find_element_link_text[<a href=# style='text-transform: uppercase'>link text</a>-LINK TEXT]": {
846
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
847
            },
848
            "test_find_element_partial_link_text[<a href=#>partial link<br>text</a>-k\\nt]": {
849
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
850
            },
851
            "test_find_element_partial_link_text[<a href=# style='text-transform: uppercase'>partial link text</a>-LINK]": {
852
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
853
            },
854
            "test_no_top_browsing_context": {
830
            "test_no_top_browsing_context": {
855
                "expected": {"wpe": {"status": ["FAIL"], "bug": "webkit.org/b/212950"}}
831
                "expected": {"wpe": {"status": ["FAIL"], "bug": "webkit.org/b/212950"}}
856
            },
832
            },
Lines 861-878 a/WebDriverTests/TestExpectations.json_sec4
861
    },
837
    },
862
    "imported/w3c/webdriver/tests/find_elements/find.py": {
838
    "imported/w3c/webdriver/tests/find_elements/find.py": {
863
        "subtests": {
839
        "subtests": {
864
            "test_find_elements_link_text[<a href=#>link<br>text</a>-link\\ntext]": {
865
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
866
            },
867
            "test_find_elements_link_text[<a href=# style='text-transform: uppercase'>link text</a>-LINK TEXT]": {
868
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
869
            },
870
            "test_find_elements_partial_link_text[<a href=#>partial link<br>text</a>-k\\nt]": {
871
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
872
            },
873
            "test_find_elements_partial_link_text[<a href=# style='text-transform: uppercase'>partial link text</a>-LINK]": {
874
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
875
            },
876
            "test_no_top_browsing_context": {
840
            "test_no_top_browsing_context": {
877
                "expected": {"wpe": {"status": ["FAIL"], "bug": "webkit.org/b/212950"}}
841
                "expected": {"wpe": {"status": ["FAIL"], "bug": "webkit.org/b/212950"}}
878
            },
842
            },
Lines 883-900 a/WebDriverTests/TestExpectations.json_sec5
883
    },
847
    },
884
    "imported/w3c/webdriver/tests/find_elements_from_element/find.py": {
848
    "imported/w3c/webdriver/tests/find_elements_from_element/find.py": {
885
        "subtests": {
849
        "subtests": {
886
            "test_find_elements_link_text[<a href=#>link<br>text</a>-link\\ntext]": {
887
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
888
            },
889
            "test_find_elements_link_text[<a href=# style='text-transform: uppercase'>link text</a>-LINK TEXT]": {
890
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
891
            },
892
            "test_find_elements_partial_link_text[<a href=#>partial link<br>text</a>-k\\nt]": {
893
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
894
            },
895
            "test_find_elements_partial_link_text[<a href=# style='text-transform: uppercase'>partial link text</a>-LINK]": {
896
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
897
            },
898
            "test_parent_of_document_node_errors": {
850
            "test_parent_of_document_node_errors": {
899
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
851
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
900
            },
852
            },
Lines 908-925 a/WebDriverTests/TestExpectations.json_sec6
908
    },
860
    },
909
    "imported/w3c/webdriver/tests/find_element_from_element/find.py": {
861
    "imported/w3c/webdriver/tests/find_element_from_element/find.py": {
910
        "subtests": {
862
        "subtests": {
911
            "test_find_element_link_text[<a href=#>link<br>text</a>-link\\ntext]": {
912
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
913
            },
914
            "test_find_element_link_text[<a href=# style='text-transform: uppercase'>link text</a>-LINK TEXT]": {
915
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
916
            },
917
            "test_find_element_partial_link_text[<a href=#>partial link<br>text</a>-k\\nt]": {
918
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
919
            },
920
            "test_find_element_partial_link_text[<a href=# style='text-transform: uppercase'>partial link text</a>-LINK]": {
921
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
922
            },
923
            "test_parent_of_document_node_errors": {
863
            "test_parent_of_document_node_errors": {
924
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
864
                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/184964"}}
925
            },
865
            },

Return to Bug 174617