appium-novawindows2-driver 1.1.21 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +716 -716
- package/build/lib/commands/app.js +14 -14
- package/build/lib/commands/extension.js +14 -14
- package/build/lib/commands/file.js +26 -26
- package/build/lib/commands/functions.js +380 -380
- package/build/lib/commands/index.d.ts +3 -3
- package/build/lib/commands/index.d.ts.map +1 -1
- package/build/lib/driver.js +9 -9
- package/build/lib/powershell/elements.js +544 -544
- package/build/lib/powershell/win32.js +807 -807
- package/package.json +86 -85
|
@@ -2,388 +2,388 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PAGE_SOURCE = exports.FIND_CHILDREN_RECURSIVELY = exports.FIND_DESCENDANTS_FUNCTIONS = exports.GET_LEGACY_PROPERTY_SAFE = void 0;
|
|
4
4
|
const powershell_1 = require("../powershell");
|
|
5
|
-
exports.GET_LEGACY_PROPERTY_SAFE = (0, powershell_1.pwsh /* ps1 */) `
|
|
6
|
-
function Get-LegacyPropertySafe {
|
|
7
|
-
param (
|
|
8
|
-
[Parameter(Mandatory=$false)]
|
|
9
|
-
[AutomationElement]$element,
|
|
10
|
-
[string]$propName,
|
|
11
|
-
[string]$accPropName
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
if ($null -eq $element) { return $null };
|
|
15
|
-
|
|
16
|
-
# 1. Try PowerShell: UIA LegacyIAccessiblePattern
|
|
17
|
-
try {
|
|
18
|
-
$val = $element.GetCurrentPattern([System.Windows.Automation.LegacyIAccessiblePattern]::Pattern).Current.$propName;
|
|
19
|
-
if ($null -ne $val) { return $val };
|
|
20
|
-
} catch {};
|
|
21
|
-
|
|
22
|
-
# 2. Try C#: Win32 MSAA with PID validation
|
|
23
|
-
try {
|
|
24
|
-
$hwnd = 0; try { $hwnd = [int]$element.Current.NativeWindowHandle; } catch { }
|
|
25
|
-
$rect = $element.Current.BoundingRectangle;
|
|
26
|
-
$cx = 0; $cy = 0;
|
|
27
|
-
try { $cx = [int]($rect.Left + $rect.Width / 2); $cy = [int]($rect.Top + $rect.Height / 2); } catch { }
|
|
28
|
-
try { [Win32Helper]::SetExpectedPid([uint32]$element.Current.ProcessId); } catch { }
|
|
29
|
-
return [Win32Helper]::GetLegacyPropertyWithFallback([IntPtr]$hwnd, $cx, $cy, $accPropName);
|
|
30
|
-
} catch {};
|
|
31
|
-
|
|
32
|
-
return $null;
|
|
33
|
-
}
|
|
5
|
+
exports.GET_LEGACY_PROPERTY_SAFE = (0, powershell_1.pwsh /* ps1 */) `
|
|
6
|
+
function Get-LegacyPropertySafe {
|
|
7
|
+
param (
|
|
8
|
+
[Parameter(Mandatory=$false)]
|
|
9
|
+
[AutomationElement]$element,
|
|
10
|
+
[string]$propName,
|
|
11
|
+
[string]$accPropName
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
if ($null -eq $element) { return $null };
|
|
15
|
+
|
|
16
|
+
# 1. Try PowerShell: UIA LegacyIAccessiblePattern
|
|
17
|
+
try {
|
|
18
|
+
$val = $element.GetCurrentPattern([System.Windows.Automation.LegacyIAccessiblePattern]::Pattern).Current.$propName;
|
|
19
|
+
if ($null -ne $val) { return $val };
|
|
20
|
+
} catch {};
|
|
21
|
+
|
|
22
|
+
# 2. Try C#: Win32 MSAA with PID validation
|
|
23
|
+
try {
|
|
24
|
+
$hwnd = 0; try { $hwnd = [int]$element.Current.NativeWindowHandle; } catch { }
|
|
25
|
+
$rect = $element.Current.BoundingRectangle;
|
|
26
|
+
$cx = 0; $cy = 0;
|
|
27
|
+
try { $cx = [int]($rect.Left + $rect.Width / 2); $cy = [int]($rect.Top + $rect.Height / 2); } catch { }
|
|
28
|
+
try { [Win32Helper]::SetExpectedPid([uint32]$element.Current.ProcessId); } catch { }
|
|
29
|
+
return [Win32Helper]::GetLegacyPropertyWithFallback([IntPtr]$hwnd, $cx, $cy, $accPropName);
|
|
30
|
+
} catch {};
|
|
31
|
+
|
|
32
|
+
return $null;
|
|
33
|
+
}
|
|
34
34
|
`;
|
|
35
|
-
exports.FIND_DESCENDANTS_FUNCTIONS = (0, powershell_1.pwsh /* ps1 */) `
|
|
36
|
-
# Streaming, memory-bounded subtree walk used as the safe fallback for
|
|
37
|
-
# Find-AllDescendants when the fast-path FindAll(Subtree) would either
|
|
38
|
-
# OutOfMemoryException (observed on desktop-rooted sessions: UIA's
|
|
39
|
-
# FindAll on a large tree materialises too much intermediate state) or
|
|
40
|
-
# take pathologically long.
|
|
41
|
-
#
|
|
42
|
-
# Iterative DFS via TreeWalker. At any moment, only a stack-depth's
|
|
43
|
-
# worth of AutomationElement references + the matches list is in memory
|
|
44
|
-
# - not the entire visited set. Each element is filter-tested
|
|
45
|
-
# individually via FindFirst([Element], $condition), which is O(1) in
|
|
46
|
-
# the size of the subtree.
|
|
47
|
-
#
|
|
48
|
-
# The walker respects the active $cacheRequest's TreeFilter (same as
|
|
49
|
-
# the upstream walker behaviour FindAll uses), so excluded views (e.g.
|
|
50
|
-
# Chrome content) stay excluded.
|
|
51
|
-
function Find-AllDescendantsStreaming {
|
|
52
|
-
param (
|
|
53
|
-
[Parameter(Mandatory=$false)]
|
|
54
|
-
[AutomationElement]$element,
|
|
55
|
-
[Parameter(Mandatory=$false)]
|
|
56
|
-
[Condition]$condition,
|
|
57
|
-
[switch]$includeSelf
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
if ($null -eq $element -or $null -eq $condition) { return @() };
|
|
61
|
-
|
|
62
|
-
$walker = [TreeWalker]::new($cacheRequest.TreeFilter);
|
|
63
|
-
$matches = New-Object System.Collections.Generic.List[AutomationElement];
|
|
64
|
-
|
|
65
|
-
if ($includeSelf) {
|
|
66
|
-
try {
|
|
67
|
-
$selfMatch = $element.FindFirst([TreeScope]::Element, $condition);
|
|
68
|
-
if ($null -ne $selfMatch) { $matches.Add($selfMatch); }
|
|
69
|
-
} catch { }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
$stack = New-Object System.Collections.Generic.Stack[AutomationElement];
|
|
73
|
-
try {
|
|
74
|
-
$first = $walker.GetFirstChild($element);
|
|
75
|
-
if ($null -ne $first) { $stack.Push($first); }
|
|
76
|
-
} catch { }
|
|
77
|
-
|
|
78
|
-
while ($stack.Count -gt 0) {
|
|
79
|
-
$current = $stack.Pop();
|
|
80
|
-
try {
|
|
81
|
-
if ($null -ne $current.FindFirst([TreeScope]::Element, $condition)) {
|
|
82
|
-
$matches.Add($current);
|
|
83
|
-
}
|
|
84
|
-
$sibling = $walker.GetNextSibling($current);
|
|
85
|
-
if ($null -ne $sibling) { $stack.Push($sibling); }
|
|
86
|
-
$firstChild = $walker.GetFirstChild($current);
|
|
87
|
-
if ($null -ne $firstChild) { $stack.Push($firstChild); }
|
|
88
|
-
} catch {
|
|
89
|
-
# Dead element / COM disconnect / property fetch failed.
|
|
90
|
-
# Skip it and keep walking - a single bad node shouldn't
|
|
91
|
-
# abort the entire search.
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return $matches;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function Find-Descendant {
|
|
100
|
-
param (
|
|
101
|
-
[Parameter(Mandatory=$false)]
|
|
102
|
-
[AutomationElement]$element,
|
|
103
|
-
[Parameter(Mandatory=$false)]
|
|
104
|
-
[Condition]$condition,
|
|
105
|
-
[switch]$includeSelf
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
if ($null -eq $element -or $null -eq $condition) { return $null };
|
|
109
|
-
|
|
110
|
-
$scope = if ($includeSelf) { [TreeScope]::Subtree } else { [TreeScope]::Descendants };
|
|
111
|
-
return $element.FindFirst($scope, $condition);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function Find-AllDescendants {
|
|
115
|
-
param (
|
|
116
|
-
[Parameter(Mandatory=$false)]
|
|
117
|
-
[AutomationElement]$element,
|
|
118
|
-
[Parameter(Mandatory=$false)]
|
|
119
|
-
[Condition]$condition,
|
|
120
|
-
[switch]$includeSelf
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if ($null -eq $element -or $null -eq $condition) { return @() };
|
|
124
|
-
|
|
125
|
-
# Fast vs safe path selection.
|
|
126
|
-
#
|
|
127
|
-
# The built-in UIA FindAll([TreeScope]::Subtree, ...) is dramatically
|
|
128
|
-
# faster than walking with TreeWalker for bounded subtrees, but on
|
|
129
|
-
# the desktop root it materialises so much intermediate state that
|
|
130
|
-
# it throws System.OutOfMemoryException on busy hosts. That kills
|
|
131
|
-
# the PowerShell subprocess and (under memory pressure) can cascade
|
|
132
|
-
# into Appium's Node.js process being killed by the Windows OOM
|
|
133
|
-
# handler.
|
|
134
|
-
#
|
|
135
|
-
# Route any search starting at [AutomationElement]::RootElement
|
|
136
|
-
# through the streaming walker. All other (window-scoped) searches
|
|
137
|
-
# keep the fast path.
|
|
138
|
-
$isDesktopRoot = $false;
|
|
139
|
-
try {
|
|
140
|
-
$isDesktopRoot = [object]::ReferenceEquals($element, [AutomationElement]::RootElement) -or $element.Equals([AutomationElement]::RootElement);
|
|
141
|
-
} catch { }
|
|
142
|
-
|
|
143
|
-
if ($isDesktopRoot) {
|
|
144
|
-
return Find-AllDescendantsStreaming -element $element -condition $condition -includeSelf:$includeSelf;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
$scope = if ($includeSelf) { [TreeScope]::Subtree } else { [TreeScope]::Descendants };
|
|
148
|
-
try {
|
|
149
|
-
return $element.FindAll($scope, $condition);
|
|
150
|
-
} catch [System.OutOfMemoryException] {
|
|
151
|
-
# Defence in depth: if FindAll OOMs on a non-root scope (huge
|
|
152
|
-
# Edge window with many tabs, complex dashboards, etc.), fall
|
|
153
|
-
# back to streaming. Note CLR OOM isn't always catchable; this
|
|
154
|
-
# is best-effort.
|
|
155
|
-
return Find-AllDescendantsStreaming -element $element -condition $condition -includeSelf:$includeSelf;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
35
|
+
exports.FIND_DESCENDANTS_FUNCTIONS = (0, powershell_1.pwsh /* ps1 */) `
|
|
36
|
+
# Streaming, memory-bounded subtree walk used as the safe fallback for
|
|
37
|
+
# Find-AllDescendants when the fast-path FindAll(Subtree) would either
|
|
38
|
+
# OutOfMemoryException (observed on desktop-rooted sessions: UIA's
|
|
39
|
+
# FindAll on a large tree materialises too much intermediate state) or
|
|
40
|
+
# take pathologically long.
|
|
41
|
+
#
|
|
42
|
+
# Iterative DFS via TreeWalker. At any moment, only a stack-depth's
|
|
43
|
+
# worth of AutomationElement references + the matches list is in memory
|
|
44
|
+
# - not the entire visited set. Each element is filter-tested
|
|
45
|
+
# individually via FindFirst([Element], $condition), which is O(1) in
|
|
46
|
+
# the size of the subtree.
|
|
47
|
+
#
|
|
48
|
+
# The walker respects the active $cacheRequest's TreeFilter (same as
|
|
49
|
+
# the upstream walker behaviour FindAll uses), so excluded views (e.g.
|
|
50
|
+
# Chrome content) stay excluded.
|
|
51
|
+
function Find-AllDescendantsStreaming {
|
|
52
|
+
param (
|
|
53
|
+
[Parameter(Mandatory=$false)]
|
|
54
|
+
[AutomationElement]$element,
|
|
55
|
+
[Parameter(Mandatory=$false)]
|
|
56
|
+
[Condition]$condition,
|
|
57
|
+
[switch]$includeSelf
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if ($null -eq $element -or $null -eq $condition) { return @() };
|
|
61
|
+
|
|
62
|
+
$walker = [TreeWalker]::new($cacheRequest.TreeFilter);
|
|
63
|
+
$matches = New-Object System.Collections.Generic.List[AutomationElement];
|
|
64
|
+
|
|
65
|
+
if ($includeSelf) {
|
|
66
|
+
try {
|
|
67
|
+
$selfMatch = $element.FindFirst([TreeScope]::Element, $condition);
|
|
68
|
+
if ($null -ne $selfMatch) { $matches.Add($selfMatch); }
|
|
69
|
+
} catch { }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$stack = New-Object System.Collections.Generic.Stack[AutomationElement];
|
|
73
|
+
try {
|
|
74
|
+
$first = $walker.GetFirstChild($element);
|
|
75
|
+
if ($null -ne $first) { $stack.Push($first); }
|
|
76
|
+
} catch { }
|
|
77
|
+
|
|
78
|
+
while ($stack.Count -gt 0) {
|
|
79
|
+
$current = $stack.Pop();
|
|
80
|
+
try {
|
|
81
|
+
if ($null -ne $current.FindFirst([TreeScope]::Element, $condition)) {
|
|
82
|
+
$matches.Add($current);
|
|
83
|
+
}
|
|
84
|
+
$sibling = $walker.GetNextSibling($current);
|
|
85
|
+
if ($null -ne $sibling) { $stack.Push($sibling); }
|
|
86
|
+
$firstChild = $walker.GetFirstChild($current);
|
|
87
|
+
if ($null -ne $firstChild) { $stack.Push($firstChild); }
|
|
88
|
+
} catch {
|
|
89
|
+
# Dead element / COM disconnect / property fetch failed.
|
|
90
|
+
# Skip it and keep walking - a single bad node shouldn't
|
|
91
|
+
# abort the entire search.
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return $matches;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Find-Descendant {
|
|
100
|
+
param (
|
|
101
|
+
[Parameter(Mandatory=$false)]
|
|
102
|
+
[AutomationElement]$element,
|
|
103
|
+
[Parameter(Mandatory=$false)]
|
|
104
|
+
[Condition]$condition,
|
|
105
|
+
[switch]$includeSelf
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if ($null -eq $element -or $null -eq $condition) { return $null };
|
|
109
|
+
|
|
110
|
+
$scope = if ($includeSelf) { [TreeScope]::Subtree } else { [TreeScope]::Descendants };
|
|
111
|
+
return $element.FindFirst($scope, $condition);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Find-AllDescendants {
|
|
115
|
+
param (
|
|
116
|
+
[Parameter(Mandatory=$false)]
|
|
117
|
+
[AutomationElement]$element,
|
|
118
|
+
[Parameter(Mandatory=$false)]
|
|
119
|
+
[Condition]$condition,
|
|
120
|
+
[switch]$includeSelf
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if ($null -eq $element -or $null -eq $condition) { return @() };
|
|
124
|
+
|
|
125
|
+
# Fast vs safe path selection.
|
|
126
|
+
#
|
|
127
|
+
# The built-in UIA FindAll([TreeScope]::Subtree, ...) is dramatically
|
|
128
|
+
# faster than walking with TreeWalker for bounded subtrees, but on
|
|
129
|
+
# the desktop root it materialises so much intermediate state that
|
|
130
|
+
# it throws System.OutOfMemoryException on busy hosts. That kills
|
|
131
|
+
# the PowerShell subprocess and (under memory pressure) can cascade
|
|
132
|
+
# into Appium's Node.js process being killed by the Windows OOM
|
|
133
|
+
# handler.
|
|
134
|
+
#
|
|
135
|
+
# Route any search starting at [AutomationElement]::RootElement
|
|
136
|
+
# through the streaming walker. All other (window-scoped) searches
|
|
137
|
+
# keep the fast path.
|
|
138
|
+
$isDesktopRoot = $false;
|
|
139
|
+
try {
|
|
140
|
+
$isDesktopRoot = [object]::ReferenceEquals($element, [AutomationElement]::RootElement) -or $element.Equals([AutomationElement]::RootElement);
|
|
141
|
+
} catch { }
|
|
142
|
+
|
|
143
|
+
if ($isDesktopRoot) {
|
|
144
|
+
return Find-AllDescendantsStreaming -element $element -condition $condition -includeSelf:$includeSelf;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
$scope = if ($includeSelf) { [TreeScope]::Subtree } else { [TreeScope]::Descendants };
|
|
148
|
+
try {
|
|
149
|
+
return $element.FindAll($scope, $condition);
|
|
150
|
+
} catch [System.OutOfMemoryException] {
|
|
151
|
+
# Defence in depth: if FindAll OOMs on a non-root scope (huge
|
|
152
|
+
# Edge window with many tabs, complex dashboards, etc.), fall
|
|
153
|
+
# back to streaming. Note CLR OOM isn't always catchable; this
|
|
154
|
+
# is best-effort.
|
|
155
|
+
return Find-AllDescendantsStreaming -element $element -condition $condition -includeSelf:$includeSelf;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
158
|
`;
|
|
159
|
-
exports.FIND_CHILDREN_RECURSIVELY = (0, powershell_1.pwsh /* ps1 */) `
|
|
160
|
-
function Find-ChildrenRecursively {
|
|
161
|
-
param (
|
|
162
|
-
[Parameter(Mandatory=$false)]
|
|
163
|
-
[AutomationElement]$element,
|
|
164
|
-
[Parameter(Mandatory=$false)]
|
|
165
|
-
[Condition]$condition,
|
|
166
|
-
[Parameter(Mandatory=$false)]
|
|
167
|
-
[bool]$includeSelf = $false
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
if ($null -eq $element -or $null -eq $condition) { return $null };
|
|
171
|
-
|
|
172
|
-
$scope = if ($includeSelf) {
|
|
173
|
-
[TreeScope]::Element -bor [TreeScope]::Children;
|
|
174
|
-
} else {
|
|
175
|
-
[TreeScope]::Children;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
$validChild = $element.FindFirst($scope, $condition);
|
|
179
|
-
|
|
180
|
-
if ($validChild -ne $null) {
|
|
181
|
-
return $validChild;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
$children = $element.FindAll([TreeScope]::Children, [Condition]::TrueCondition);
|
|
185
|
-
foreach ($child in $children) {
|
|
186
|
-
$result = Find-AllChildrenRecursively -element $child -condition $condition -returnFirstResult $true;
|
|
187
|
-
if ($result -ne $null) {
|
|
188
|
-
return $result[0];
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return $null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function Find-AllChildrenRecursively {
|
|
196
|
-
param (
|
|
197
|
-
[Parameter(Mandatory=$false)]
|
|
198
|
-
[AutomationElement]$element,
|
|
199
|
-
[Parameter(Mandatory=$false)]
|
|
200
|
-
[Condition]$condition,
|
|
201
|
-
[bool]$returnFirstResult = $false,
|
|
202
|
-
[Parameter(Mandatory=$false)]
|
|
203
|
-
[bool]$includeSelf = $false
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
if ($null -eq $element -or $null -eq $condition) { return @() };
|
|
207
|
-
|
|
208
|
-
$children = $element.FindAll([TreeScope]::Children, [Condition]::TrueCondition);
|
|
209
|
-
$validChildren = @($children | Where-Object { $_.FindFirst([TreeScope]::Element, $condition) -ne $null });
|
|
210
|
-
|
|
211
|
-
if ($includeSelf) {
|
|
212
|
-
$self = $element.FindFirst([TreeScope]::Element, $condition);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if ($null -ne $self) {
|
|
216
|
-
$validChildren += $self;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
foreach ($child in $children) {
|
|
220
|
-
$Allresults = Find-AllChildrenRecursively -element $child -condition $condition;
|
|
221
|
-
if ($returnFirstResult -and $Allresults.Count -gt 0) {
|
|
222
|
-
return $Allresults;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
foreach ($result in $Allresults) {
|
|
226
|
-
$validChildren += ($result | Where-Object { $_.FindFirst([TreeScope]::Element, $condition) -ne $null });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return $validChildren;
|
|
231
|
-
}
|
|
159
|
+
exports.FIND_CHILDREN_RECURSIVELY = (0, powershell_1.pwsh /* ps1 */) `
|
|
160
|
+
function Find-ChildrenRecursively {
|
|
161
|
+
param (
|
|
162
|
+
[Parameter(Mandatory=$false)]
|
|
163
|
+
[AutomationElement]$element,
|
|
164
|
+
[Parameter(Mandatory=$false)]
|
|
165
|
+
[Condition]$condition,
|
|
166
|
+
[Parameter(Mandatory=$false)]
|
|
167
|
+
[bool]$includeSelf = $false
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if ($null -eq $element -or $null -eq $condition) { return $null };
|
|
171
|
+
|
|
172
|
+
$scope = if ($includeSelf) {
|
|
173
|
+
[TreeScope]::Element -bor [TreeScope]::Children;
|
|
174
|
+
} else {
|
|
175
|
+
[TreeScope]::Children;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
$validChild = $element.FindFirst($scope, $condition);
|
|
179
|
+
|
|
180
|
+
if ($validChild -ne $null) {
|
|
181
|
+
return $validChild;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
$children = $element.FindAll([TreeScope]::Children, [Condition]::TrueCondition);
|
|
185
|
+
foreach ($child in $children) {
|
|
186
|
+
$result = Find-AllChildrenRecursively -element $child -condition $condition -returnFirstResult $true;
|
|
187
|
+
if ($result -ne $null) {
|
|
188
|
+
return $result[0];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return $null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function Find-AllChildrenRecursively {
|
|
196
|
+
param (
|
|
197
|
+
[Parameter(Mandatory=$false)]
|
|
198
|
+
[AutomationElement]$element,
|
|
199
|
+
[Parameter(Mandatory=$false)]
|
|
200
|
+
[Condition]$condition,
|
|
201
|
+
[bool]$returnFirstResult = $false,
|
|
202
|
+
[Parameter(Mandatory=$false)]
|
|
203
|
+
[bool]$includeSelf = $false
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if ($null -eq $element -or $null -eq $condition) { return @() };
|
|
207
|
+
|
|
208
|
+
$children = $element.FindAll([TreeScope]::Children, [Condition]::TrueCondition);
|
|
209
|
+
$validChildren = @($children | Where-Object { $_.FindFirst([TreeScope]::Element, $condition) -ne $null });
|
|
210
|
+
|
|
211
|
+
if ($includeSelf) {
|
|
212
|
+
$self = $element.FindFirst([TreeScope]::Element, $condition);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if ($null -ne $self) {
|
|
216
|
+
$validChildren += $self;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
foreach ($child in $children) {
|
|
220
|
+
$Allresults = Find-AllChildrenRecursively -element $child -condition $condition;
|
|
221
|
+
if ($returnFirstResult -and $Allresults.Count -gt 0) {
|
|
222
|
+
return $Allresults;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
foreach ($result in $Allresults) {
|
|
226
|
+
$validChildren += ($result | Where-Object { $_.FindFirst([TreeScope]::Element, $condition) -ne $null });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return $validChildren;
|
|
231
|
+
}
|
|
232
232
|
`;
|
|
233
|
-
exports.PAGE_SOURCE = (0, powershell_1.pwsh /* ps1 */) `
|
|
234
|
-
# Builds the XML node for a single AutomationElement (no traversal).
|
|
235
|
-
# Pulled out of Get-PageSource so the iterative walker below can call it
|
|
236
|
-
# per node without each call adding a PS stack frame.
|
|
237
|
-
function Build-PageSourceNode {
|
|
238
|
-
param (
|
|
239
|
-
[Parameter(Mandatory = $true)]
|
|
240
|
-
[AutomationElement]$element,
|
|
241
|
-
[Parameter(Mandatory = $true)]
|
|
242
|
-
[Xml.XmlDocument]$xmlDoc
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
$localizedControlType = $element.GetCurrentPropertyValue([AutomationElement]::LocalizedControlTypeProperty);
|
|
246
|
-
$controlType = $element.GetCurrentPropertyValue([AutomationElement]::ControlTypeProperty);
|
|
247
|
-
|
|
248
|
-
$tagName = '';
|
|
249
|
-
try {
|
|
250
|
-
$tagName = $controlType.ProgrammaticName.Split('.')[-1];
|
|
251
|
-
} catch {
|
|
252
|
-
# fallback to LocalizedControlType if ControlType is empty
|
|
253
|
-
$tagName = -join ($localizedControlType -split ' ' | ForEach-Object {
|
|
254
|
-
$_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower();
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
if ($tagName -eq 'DataGrid') { $tagName = 'List' }
|
|
258
|
-
elseif ($tagName -eq 'DataItem') { $tagName = 'ListItem' };
|
|
259
|
-
|
|
260
|
-
$acceleratorKey = $element.GetCurrentPropertyValue([AutomationElement]::AcceleratorKeyProperty);
|
|
261
|
-
$accessKey = $element.GetCurrentPropertyValue([AutomationElement]::AccessKeyProperty);
|
|
262
|
-
$automationId = $element.GetCurrentPropertyValue([AutomationElement]::AutomationIdProperty);
|
|
263
|
-
$className = $element.GetCurrentPropertyValue([AutomationElement]::ClassNameProperty);
|
|
264
|
-
$frameworkId = $element.GetCurrentPropertyValue([AutomationElement]::FrameworkIdProperty);
|
|
265
|
-
$hasKeyboardfocus = $element.GetCurrentPropertyValue([AutomationElement]::HasKeyboardfocusProperty);
|
|
266
|
-
$helpText = $element.GetCurrentPropertyValue([AutomationElement]::HelpTextProperty);
|
|
267
|
-
$isContentelement = $element.GetCurrentPropertyValue([AutomationElement]::IsContentelementProperty);
|
|
268
|
-
$isControlelement = $element.GetCurrentPropertyValue([AutomationElement]::IsControlelementProperty);
|
|
269
|
-
$isEnabled = $element.GetCurrentPropertyValue([AutomationElement]::IsEnabledProperty);
|
|
270
|
-
$isKeyboardfocusable = $element.GetCurrentPropertyValue([AutomationElement]::IsKeyboardfocusableProperty);
|
|
271
|
-
$isOffscreen = $element.GetCurrentPropertyValue([AutomationElement]::IsOffscreenProperty);
|
|
272
|
-
$isPassword = $element.GetCurrentPropertyValue([AutomationElement]::IsPasswordProperty);
|
|
273
|
-
$isRequiredforform = $element.GetCurrentPropertyValue([AutomationElement]::IsRequiredforformProperty);
|
|
274
|
-
$itemStatus = $element.GetCurrentPropertyValue([AutomationElement]::ItemStatusProperty);
|
|
275
|
-
$itemType = $element.GetCurrentPropertyValue([AutomationElement]::ItemTypeProperty);
|
|
276
|
-
$name = $element.GetCurrentPropertyValue([AutomationElement]::NameProperty);
|
|
277
|
-
$orientation = $element.GetCurrentPropertyValue([AutomationElement]::OrientationProperty);
|
|
278
|
-
$processId = $element.GetCurrentPropertyValue([AutomationElement]::ProcessIdProperty);
|
|
279
|
-
$runtimeId = $element.GetCurrentPropertyValue([AutomationElement]::RuntimeIdProperty) -join '.';
|
|
280
|
-
$boundingRectangle = $element.Current.BoundingRectangle;
|
|
281
|
-
$x = $boundingRectangle.X - $rootElement.Current.BoundingRectangle.X;
|
|
282
|
-
$y = $boundingRectangle.Y - $rootElement.Current.BoundingRectangle.Y;
|
|
283
|
-
$width = $boundingRectangle.Width;
|
|
284
|
-
$height = $boundingRectangle.Height;
|
|
285
|
-
|
|
286
|
-
$newXmlElement = $xmlDoc.CreateElement($tagName);
|
|
287
|
-
$newXmlElement.SetAttribute("AcceleratorKey", $acceleratorKey);
|
|
288
|
-
$newXmlElement.SetAttribute("AccessKey", $accessKey);
|
|
289
|
-
$newXmlElement.SetAttribute("AutomationId", $automationId);
|
|
290
|
-
$newXmlElement.SetAttribute("ClassName", $className);
|
|
291
|
-
$newXmlElement.SetAttribute("FrameworkId", $frameworkId);
|
|
292
|
-
$newXmlElement.SetAttribute("HasKeyboardfocus", $hasKeyboardfocus);
|
|
293
|
-
$newXmlElement.SetAttribute("HelpText", $helpText);
|
|
294
|
-
$newXmlElement.SetAttribute("IsContentelement", $isContentelement);
|
|
295
|
-
$newXmlElement.SetAttribute("IsControlelement", $isControlelement);
|
|
296
|
-
$newXmlElement.SetAttribute("IsEnabled", $isEnabled);
|
|
297
|
-
$newXmlElement.SetAttribute("IsKeyboardfocusable", $isKeyboardfocusable);
|
|
298
|
-
$newXmlElement.SetAttribute("IsOffscreen", $isOffscreen);
|
|
299
|
-
$newXmlElement.SetAttribute("IsPassword", $isPassword);
|
|
300
|
-
$newXmlElement.SetAttribute("IsRequiredforform", $isRequiredforform);
|
|
301
|
-
$newXmlElement.SetAttribute("ItemStatus", $itemStatus);
|
|
302
|
-
$newXmlElement.SetAttribute("ItemType", $itemType);
|
|
303
|
-
$newXmlElement.SetAttribute("LocalizedControlType", $localizedControlType);
|
|
304
|
-
$newXmlElement.SetAttribute("Name", $name);
|
|
305
|
-
$newXmlElement.SetAttribute("Orientation", $orientation);
|
|
306
|
-
$newXmlElement.SetAttribute("ProcessId", $processId);
|
|
307
|
-
$newXmlElement.SetAttribute("RuntimeId", $runtimeId);
|
|
308
|
-
$newXmlElement.SetAttribute("x", $x);
|
|
309
|
-
$newXmlElement.SetAttribute("y", $y);
|
|
310
|
-
$newXmlElement.SetAttribute("width", $width);
|
|
311
|
-
$newXmlElement.SetAttribute("height", $height);
|
|
312
|
-
|
|
313
|
-
$pattern = $null;
|
|
314
|
-
if ($element.TryGetCurrentPattern([WindowPattern]::Pattern, [ref]$pattern)) {
|
|
315
|
-
$newXmlElement.SetAttribute("CanMaximize", $pattern.Current.CanMaximize);
|
|
316
|
-
$newXmlElement.SetAttribute("CanMinimize", $pattern.Current.CanMinimize);
|
|
317
|
-
$newXmlElement.SetAttribute("IsModal", $pattern.Current.IsModal);
|
|
318
|
-
$newXmlElement.SetAttribute("WindowVisualState", $pattern.Current.WindowVisualState);
|
|
319
|
-
$newXmlElement.SetAttribute("WindowInteractionState", $pattern.Current.WindowInteractionState);
|
|
320
|
-
$newXmlElement.SetAttribute("IsTopmost", $pattern.Current.IsTopmost);
|
|
321
|
-
}
|
|
322
|
-
if ($element.TryGetCurrentPattern([TransformPattern]::Pattern, [ref]$pattern)) {
|
|
323
|
-
$newXmlElement.SetAttribute("CanRotate", $pattern.Current.CanRotate);
|
|
324
|
-
$newXmlElement.SetAttribute("CanResize", $pattern.Current.CanResize);
|
|
325
|
-
$newXmlElement.SetAttribute("CanMove", $pattern.Current.CanMove);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
# TODO: more to be added depending on the available patterns
|
|
329
|
-
|
|
330
|
-
return $newXmlElement;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
# Iterative BFS over the UIA subtree, producing the same XML output as
|
|
334
|
-
# the previous recursive Get-PageSource. The recursive version blew the
|
|
335
|
-
# PS function-call stack on deep desktop-rooted trees, which crashed the
|
|
336
|
-
# PS subprocess (~5000-frame ceiling). The iterative version keeps stack
|
|
337
|
-
# depth at O(1) regardless of tree depth; memory at any moment is bounded
|
|
338
|
-
# by the queue size (siblings still to visit), not by tree depth.
|
|
339
|
-
#
|
|
340
|
-
# Public signature unchanged so callers don't break:
|
|
341
|
-
# Get-PageSource $element - new document, return root XML element
|
|
342
|
-
# Get-PageSource $element $xmlDoc $xmlElement - append under $xmlElement (legacy)
|
|
343
|
-
function Get-PageSource {
|
|
344
|
-
param (
|
|
345
|
-
[Parameter(Mandatory = $true)]
|
|
346
|
-
[AutomationElement]$element,
|
|
347
|
-
[Xml.XmlDocument]$xmlDoc,
|
|
348
|
-
[Xml.XmlElement]$xmlElement
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
if ($null -eq $xmlDoc) { $xmlDoc = [Xml.XmlDocument]::new() };
|
|
352
|
-
|
|
353
|
-
# Queue of (element, parentXmlElementOrNull). parentXmlElement = $null
|
|
354
|
-
# for the seed entry means "append to xmlDoc directly".
|
|
355
|
-
$queue = New-Object System.Collections.Queue;
|
|
356
|
-
$queue.Enqueue(@($element, $xmlElement));
|
|
357
|
-
|
|
358
|
-
$resultXmlElement = $null;
|
|
359
|
-
|
|
360
|
-
while ($queue.Count -gt 0) {
|
|
361
|
-
$pair = $queue.Dequeue();
|
|
362
|
-
$curElement = $pair[0];
|
|
363
|
-
$curParent = $pair[1];
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
$newXmlElement = Build-PageSourceNode -element $curElement -xmlDoc $xmlDoc;
|
|
367
|
-
|
|
368
|
-
if ($null -eq $curParent) {
|
|
369
|
-
$appended = $xmlDoc.AppendChild($newXmlElement);
|
|
370
|
-
} else {
|
|
371
|
-
$appended = $curParent.AppendChild($newXmlElement);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if ($null -eq $resultXmlElement) { $resultXmlElement = $appended };
|
|
375
|
-
|
|
376
|
-
# Enqueue children with the freshly-appended node as their parent.
|
|
377
|
-
$curElement.FindAll([TreeScope]::Children, $cacheRequest.TreeFilter) | ForEach-Object {
|
|
378
|
-
$queue.Enqueue(@($_, $appended));
|
|
379
|
-
};
|
|
380
|
-
} catch {
|
|
381
|
-
# Skip this node and keep walking the rest of the tree.
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return $resultXmlElement;
|
|
387
|
-
}
|
|
233
|
+
exports.PAGE_SOURCE = (0, powershell_1.pwsh /* ps1 */) `
|
|
234
|
+
# Builds the XML node for a single AutomationElement (no traversal).
|
|
235
|
+
# Pulled out of Get-PageSource so the iterative walker below can call it
|
|
236
|
+
# per node without each call adding a PS stack frame.
|
|
237
|
+
function Build-PageSourceNode {
|
|
238
|
+
param (
|
|
239
|
+
[Parameter(Mandatory = $true)]
|
|
240
|
+
[AutomationElement]$element,
|
|
241
|
+
[Parameter(Mandatory = $true)]
|
|
242
|
+
[Xml.XmlDocument]$xmlDoc
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
$localizedControlType = $element.GetCurrentPropertyValue([AutomationElement]::LocalizedControlTypeProperty);
|
|
246
|
+
$controlType = $element.GetCurrentPropertyValue([AutomationElement]::ControlTypeProperty);
|
|
247
|
+
|
|
248
|
+
$tagName = '';
|
|
249
|
+
try {
|
|
250
|
+
$tagName = $controlType.ProgrammaticName.Split('.')[-1];
|
|
251
|
+
} catch {
|
|
252
|
+
# fallback to LocalizedControlType if ControlType is empty
|
|
253
|
+
$tagName = -join ($localizedControlType -split ' ' | ForEach-Object {
|
|
254
|
+
$_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower();
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if ($tagName -eq 'DataGrid') { $tagName = 'List' }
|
|
258
|
+
elseif ($tagName -eq 'DataItem') { $tagName = 'ListItem' };
|
|
259
|
+
|
|
260
|
+
$acceleratorKey = $element.GetCurrentPropertyValue([AutomationElement]::AcceleratorKeyProperty);
|
|
261
|
+
$accessKey = $element.GetCurrentPropertyValue([AutomationElement]::AccessKeyProperty);
|
|
262
|
+
$automationId = $element.GetCurrentPropertyValue([AutomationElement]::AutomationIdProperty);
|
|
263
|
+
$className = $element.GetCurrentPropertyValue([AutomationElement]::ClassNameProperty);
|
|
264
|
+
$frameworkId = $element.GetCurrentPropertyValue([AutomationElement]::FrameworkIdProperty);
|
|
265
|
+
$hasKeyboardfocus = $element.GetCurrentPropertyValue([AutomationElement]::HasKeyboardfocusProperty);
|
|
266
|
+
$helpText = $element.GetCurrentPropertyValue([AutomationElement]::HelpTextProperty);
|
|
267
|
+
$isContentelement = $element.GetCurrentPropertyValue([AutomationElement]::IsContentelementProperty);
|
|
268
|
+
$isControlelement = $element.GetCurrentPropertyValue([AutomationElement]::IsControlelementProperty);
|
|
269
|
+
$isEnabled = $element.GetCurrentPropertyValue([AutomationElement]::IsEnabledProperty);
|
|
270
|
+
$isKeyboardfocusable = $element.GetCurrentPropertyValue([AutomationElement]::IsKeyboardfocusableProperty);
|
|
271
|
+
$isOffscreen = $element.GetCurrentPropertyValue([AutomationElement]::IsOffscreenProperty);
|
|
272
|
+
$isPassword = $element.GetCurrentPropertyValue([AutomationElement]::IsPasswordProperty);
|
|
273
|
+
$isRequiredforform = $element.GetCurrentPropertyValue([AutomationElement]::IsRequiredforformProperty);
|
|
274
|
+
$itemStatus = $element.GetCurrentPropertyValue([AutomationElement]::ItemStatusProperty);
|
|
275
|
+
$itemType = $element.GetCurrentPropertyValue([AutomationElement]::ItemTypeProperty);
|
|
276
|
+
$name = $element.GetCurrentPropertyValue([AutomationElement]::NameProperty);
|
|
277
|
+
$orientation = $element.GetCurrentPropertyValue([AutomationElement]::OrientationProperty);
|
|
278
|
+
$processId = $element.GetCurrentPropertyValue([AutomationElement]::ProcessIdProperty);
|
|
279
|
+
$runtimeId = $element.GetCurrentPropertyValue([AutomationElement]::RuntimeIdProperty) -join '.';
|
|
280
|
+
$boundingRectangle = $element.Current.BoundingRectangle;
|
|
281
|
+
$x = $boundingRectangle.X - $rootElement.Current.BoundingRectangle.X;
|
|
282
|
+
$y = $boundingRectangle.Y - $rootElement.Current.BoundingRectangle.Y;
|
|
283
|
+
$width = $boundingRectangle.Width;
|
|
284
|
+
$height = $boundingRectangle.Height;
|
|
285
|
+
|
|
286
|
+
$newXmlElement = $xmlDoc.CreateElement($tagName);
|
|
287
|
+
$newXmlElement.SetAttribute("AcceleratorKey", $acceleratorKey);
|
|
288
|
+
$newXmlElement.SetAttribute("AccessKey", $accessKey);
|
|
289
|
+
$newXmlElement.SetAttribute("AutomationId", $automationId);
|
|
290
|
+
$newXmlElement.SetAttribute("ClassName", $className);
|
|
291
|
+
$newXmlElement.SetAttribute("FrameworkId", $frameworkId);
|
|
292
|
+
$newXmlElement.SetAttribute("HasKeyboardfocus", $hasKeyboardfocus);
|
|
293
|
+
$newXmlElement.SetAttribute("HelpText", $helpText);
|
|
294
|
+
$newXmlElement.SetAttribute("IsContentelement", $isContentelement);
|
|
295
|
+
$newXmlElement.SetAttribute("IsControlelement", $isControlelement);
|
|
296
|
+
$newXmlElement.SetAttribute("IsEnabled", $isEnabled);
|
|
297
|
+
$newXmlElement.SetAttribute("IsKeyboardfocusable", $isKeyboardfocusable);
|
|
298
|
+
$newXmlElement.SetAttribute("IsOffscreen", $isOffscreen);
|
|
299
|
+
$newXmlElement.SetAttribute("IsPassword", $isPassword);
|
|
300
|
+
$newXmlElement.SetAttribute("IsRequiredforform", $isRequiredforform);
|
|
301
|
+
$newXmlElement.SetAttribute("ItemStatus", $itemStatus);
|
|
302
|
+
$newXmlElement.SetAttribute("ItemType", $itemType);
|
|
303
|
+
$newXmlElement.SetAttribute("LocalizedControlType", $localizedControlType);
|
|
304
|
+
$newXmlElement.SetAttribute("Name", $name);
|
|
305
|
+
$newXmlElement.SetAttribute("Orientation", $orientation);
|
|
306
|
+
$newXmlElement.SetAttribute("ProcessId", $processId);
|
|
307
|
+
$newXmlElement.SetAttribute("RuntimeId", $runtimeId);
|
|
308
|
+
$newXmlElement.SetAttribute("x", $x);
|
|
309
|
+
$newXmlElement.SetAttribute("y", $y);
|
|
310
|
+
$newXmlElement.SetAttribute("width", $width);
|
|
311
|
+
$newXmlElement.SetAttribute("height", $height);
|
|
312
|
+
|
|
313
|
+
$pattern = $null;
|
|
314
|
+
if ($element.TryGetCurrentPattern([WindowPattern]::Pattern, [ref]$pattern)) {
|
|
315
|
+
$newXmlElement.SetAttribute("CanMaximize", $pattern.Current.CanMaximize);
|
|
316
|
+
$newXmlElement.SetAttribute("CanMinimize", $pattern.Current.CanMinimize);
|
|
317
|
+
$newXmlElement.SetAttribute("IsModal", $pattern.Current.IsModal);
|
|
318
|
+
$newXmlElement.SetAttribute("WindowVisualState", $pattern.Current.WindowVisualState);
|
|
319
|
+
$newXmlElement.SetAttribute("WindowInteractionState", $pattern.Current.WindowInteractionState);
|
|
320
|
+
$newXmlElement.SetAttribute("IsTopmost", $pattern.Current.IsTopmost);
|
|
321
|
+
}
|
|
322
|
+
if ($element.TryGetCurrentPattern([TransformPattern]::Pattern, [ref]$pattern)) {
|
|
323
|
+
$newXmlElement.SetAttribute("CanRotate", $pattern.Current.CanRotate);
|
|
324
|
+
$newXmlElement.SetAttribute("CanResize", $pattern.Current.CanResize);
|
|
325
|
+
$newXmlElement.SetAttribute("CanMove", $pattern.Current.CanMove);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# TODO: more to be added depending on the available patterns
|
|
329
|
+
|
|
330
|
+
return $newXmlElement;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Iterative BFS over the UIA subtree, producing the same XML output as
|
|
334
|
+
# the previous recursive Get-PageSource. The recursive version blew the
|
|
335
|
+
# PS function-call stack on deep desktop-rooted trees, which crashed the
|
|
336
|
+
# PS subprocess (~5000-frame ceiling). The iterative version keeps stack
|
|
337
|
+
# depth at O(1) regardless of tree depth; memory at any moment is bounded
|
|
338
|
+
# by the queue size (siblings still to visit), not by tree depth.
|
|
339
|
+
#
|
|
340
|
+
# Public signature unchanged so callers don't break:
|
|
341
|
+
# Get-PageSource $element - new document, return root XML element
|
|
342
|
+
# Get-PageSource $element $xmlDoc $xmlElement - append under $xmlElement (legacy)
|
|
343
|
+
function Get-PageSource {
|
|
344
|
+
param (
|
|
345
|
+
[Parameter(Mandatory = $true)]
|
|
346
|
+
[AutomationElement]$element,
|
|
347
|
+
[Xml.XmlDocument]$xmlDoc,
|
|
348
|
+
[Xml.XmlElement]$xmlElement
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if ($null -eq $xmlDoc) { $xmlDoc = [Xml.XmlDocument]::new() };
|
|
352
|
+
|
|
353
|
+
# Queue of (element, parentXmlElementOrNull). parentXmlElement = $null
|
|
354
|
+
# for the seed entry means "append to xmlDoc directly".
|
|
355
|
+
$queue = New-Object System.Collections.Queue;
|
|
356
|
+
$queue.Enqueue(@($element, $xmlElement));
|
|
357
|
+
|
|
358
|
+
$resultXmlElement = $null;
|
|
359
|
+
|
|
360
|
+
while ($queue.Count -gt 0) {
|
|
361
|
+
$pair = $queue.Dequeue();
|
|
362
|
+
$curElement = $pair[0];
|
|
363
|
+
$curParent = $pair[1];
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
$newXmlElement = Build-PageSourceNode -element $curElement -xmlDoc $xmlDoc;
|
|
367
|
+
|
|
368
|
+
if ($null -eq $curParent) {
|
|
369
|
+
$appended = $xmlDoc.AppendChild($newXmlElement);
|
|
370
|
+
} else {
|
|
371
|
+
$appended = $curParent.AppendChild($newXmlElement);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if ($null -eq $resultXmlElement) { $resultXmlElement = $appended };
|
|
375
|
+
|
|
376
|
+
# Enqueue children with the freshly-appended node as their parent.
|
|
377
|
+
$curElement.FindAll([TreeScope]::Children, $cacheRequest.TreeFilter) | ForEach-Object {
|
|
378
|
+
$queue.Enqueue(@($_, $appended));
|
|
379
|
+
};
|
|
380
|
+
} catch {
|
|
381
|
+
# Skip this node and keep walking the rest of the tree.
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return $resultXmlElement;
|
|
387
|
+
}
|
|
388
388
|
`;
|
|
389
389
|
//# sourceMappingURL=functions.js.map
|