OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include <atlbase.h> | 5 #include <atlbase.h> |
6 #include <atlcom.h> | 6 #include <atlcom.h> |
7 #include <limits.h> | 7 #include <limits.h> |
8 #include <oleacc.h> | 8 #include <oleacc.h> |
9 #include <stdint.h> | 9 #include <stdint.h> |
10 | 10 |
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
197 // | 197 // |
198 // AXPlatformNodeWin | 198 // AXPlatformNodeWin |
199 // | 199 // |
200 | 200 |
201 AXPlatformNodeWin::AXPlatformNodeWin() { | 201 AXPlatformNodeWin::AXPlatformNodeWin() { |
202 } | 202 } |
203 | 203 |
204 AXPlatformNodeWin::~AXPlatformNodeWin() { | 204 AXPlatformNodeWin::~AXPlatformNodeWin() { |
205 } | 205 } |
206 | 206 |
| 207 // Static |
| 208 void AXPlatformNodeWin::SanitizeStringAttributeForIA2( |
| 209 const base::string16& input, |
| 210 base::string16* output) { |
| 211 DCHECK(output); |
| 212 // According to the IA2 Spec, these characters need to be escaped with a |
| 213 // backslash: backslash, colon, comma, equals and semicolon. |
| 214 // Note that backslash must be replaced first. |
| 215 base::ReplaceChars(input, L"\\", L"\\\\", output); |
| 216 base::ReplaceChars(*output, L":", L"\\:", output); |
| 217 base::ReplaceChars(*output, L",", L"\\,", output); |
| 218 base::ReplaceChars(*output, L"=", L"\\=", output); |
| 219 base::ReplaceChars(*output, L";", L"\\;", output); |
| 220 } |
| 221 |
| 222 void AXPlatformNodeWin::StringAttributeToIA2( |
| 223 std::vector<base::string16>& attributes, |
| 224 ui::AXStringAttribute attribute, |
| 225 const char* ia2_attr) { |
| 226 base::string16 value; |
| 227 if (GetString16Attribute(attribute, &value)) { |
| 228 SanitizeStringAttributeForIA2(value, &value); |
| 229 attributes.push_back(base::ASCIIToUTF16(ia2_attr) + L":" + value); |
| 230 } |
| 231 } |
| 232 |
| 233 void AXPlatformNodeWin::BoolAttributeToIA2( |
| 234 std::vector<base::string16>& attributes, |
| 235 ui::AXBoolAttribute attribute, |
| 236 const char* ia2_attr) { |
| 237 bool value; |
| 238 if (GetBoolAttribute(attribute, &value)) { |
| 239 attributes.push_back((base::ASCIIToUTF16(ia2_attr) + L":") + |
| 240 (value ? L"true" : L"false")); |
| 241 } |
| 242 } |
| 243 |
| 244 void AXPlatformNodeWin::IntAttributeToIA2( |
| 245 std::vector<base::string16>& attributes, |
| 246 ui::AXIntAttribute attribute, |
| 247 const char* ia2_attr) { |
| 248 int value; |
| 249 if (GetIntAttribute(attribute, &value)) { |
| 250 attributes.push_back(base::ASCIIToUTF16(ia2_attr) + L":" + |
| 251 base::IntToString16(value)); |
| 252 } |
| 253 } |
| 254 |
207 // | 255 // |
208 // AXPlatformNodeBase implementation. | 256 // AXPlatformNodeBase implementation. |
209 // | 257 // |
210 | 258 |
211 void AXPlatformNodeWin::Dispose() { | 259 void AXPlatformNodeWin::Dispose() { |
212 Release(); | 260 Release(); |
213 } | 261 } |
214 | 262 |
215 void AXPlatformNodeWin::Destroy() { | 263 void AXPlatformNodeWin::Destroy() { |
216 RemoveAlertTarget(); | 264 RemoveAlertTarget(); |
(...skipping 574 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
791 return E_NOTIMPL; | 839 return E_NOTIMPL; |
792 } | 840 } |
793 | 841 |
794 // | 842 // |
795 // IAccessible2 implementation. | 843 // IAccessible2 implementation. |
796 // | 844 // |
797 | 845 |
798 STDMETHODIMP AXPlatformNodeWin::role(LONG* role) { | 846 STDMETHODIMP AXPlatformNodeWin::role(LONG* role) { |
799 COM_OBJECT_VALIDATE_1_ARG(role); | 847 COM_OBJECT_VALIDATE_1_ARG(role); |
800 | 848 |
801 *role = IA2Role(); | 849 *role = ComputeIA2Role(); |
802 // If we didn't explicitly set the IAccessible2 role, make it the same | 850 // If we didn't explicitly set the IAccessible2 role, make it the same |
803 // as the MSAA role. | 851 // as the MSAA role. |
804 if (!*role) | 852 if (!*role) |
805 *role = MSAARole(); | 853 *role = MSAARole(); |
806 return S_OK; | 854 return S_OK; |
807 } | 855 } |
808 | 856 |
809 STDMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) { | 857 STDMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) { |
810 COM_OBJECT_VALIDATE_1_ARG(states); | 858 COM_OBJECT_VALIDATE_1_ARG(states); |
811 *states = IA2State(); | 859 *states = ComputeIA2State(); |
812 return S_OK; | 860 return S_OK; |
813 } | 861 } |
814 | 862 |
815 STDMETHODIMP AXPlatformNodeWin::get_uniqueID(LONG* unique_id) { | 863 STDMETHODIMP AXPlatformNodeWin::get_uniqueID(LONG* unique_id) { |
816 COM_OBJECT_VALIDATE_1_ARG(unique_id); | 864 COM_OBJECT_VALIDATE_1_ARG(unique_id); |
817 *unique_id = -unique_id_; | 865 *unique_id = -unique_id_; |
818 return S_OK; | 866 return S_OK; |
819 } | 867 } |
820 | 868 |
821 STDMETHODIMP AXPlatformNodeWin::get_windowHandle(HWND* window_handle) { | 869 STDMETHODIMP AXPlatformNodeWin::get_windowHandle(HWND* window_handle) { |
(...skipping 1391 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2213 return false; | 2261 return false; |
2214 } | 2262 } |
2215 | 2263 |
2216 auto* parent = FromNativeViewAccessible(GetParent()); | 2264 auto* parent = FromNativeViewAccessible(GetParent()); |
2217 if (!parent) | 2265 if (!parent) |
2218 return false; | 2266 return false; |
2219 | 2267 |
2220 return parent->GetData().role == ui::AX_ROLE_IFRAME_PRESENTATIONAL; | 2268 return parent->GetData().role == ui::AX_ROLE_IFRAME_PRESENTATIONAL; |
2221 } | 2269 } |
2222 | 2270 |
2223 int32_t AXPlatformNodeWin::IA2State() { | 2271 int32_t AXPlatformNodeWin::ComputeIA2State() { |
2224 const AXNodeData& data = GetData(); | 2272 const AXNodeData& data = GetData(); |
2225 | 2273 |
2226 int32_t ia2_state = IA2_STATE_OPAQUE; | 2274 int32_t ia2_state = IA2_STATE_OPAQUE; |
2227 | 2275 |
2228 const auto checked_state = static_cast<ui::AXCheckedState>( | 2276 const auto checked_state = static_cast<ui::AXCheckedState>( |
2229 GetIntAttribute(ui::AX_ATTR_CHECKED_STATE)); | 2277 GetIntAttribute(ui::AX_ATTR_CHECKED_STATE)); |
2230 if (checked_state) { | 2278 if (checked_state) { |
2231 ia2_state |= IA2_STATE_CHECKABLE; | 2279 ia2_state |= IA2_STATE_CHECKABLE; |
2232 } | 2280 } |
2233 | 2281 |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2281 ia2_state |= IA2_STATE_SINGLE_LINE; | 2329 ia2_state |= IA2_STATE_SINGLE_LINE; |
2282 } | 2330 } |
2283 ia2_state |= IA2_STATE_SELECTABLE_TEXT; | 2331 ia2_state |= IA2_STATE_SELECTABLE_TEXT; |
2284 break; | 2332 break; |
2285 default: | 2333 default: |
2286 break; | 2334 break; |
2287 } | 2335 } |
2288 return ia2_state; | 2336 return ia2_state; |
2289 } | 2337 } |
2290 | 2338 |
2291 // IA2Role() only returns a role if the MSAA role doesn't suffice, | 2339 // ComputeIA2Role() only returns a role if the MSAA role doesn't suffice, |
2292 // otherwise this method returns 0. See AXPlatformNodeWin::role(). | 2340 // otherwise this method returns 0. See AXPlatformNodeWin::role(). |
2293 int32_t AXPlatformNodeWin::IA2Role() { | 2341 int32_t AXPlatformNodeWin::ComputeIA2Role() { |
2294 // If this is a web area for a presentational iframe, give it a role of | 2342 // If this is a web area for a presentational iframe, give it a role of |
2295 // something other than DOCUMENT so that the fact that it's a separate doc | 2343 // something other than DOCUMENT so that the fact that it's a separate doc |
2296 // is not exposed to AT. | 2344 // is not exposed to AT. |
2297 if (IsWebAreaForPresentationalIframe()) { | 2345 if (IsWebAreaForPresentationalIframe()) { |
2298 return ROLE_SYSTEM_GROUPING; | 2346 return ROLE_SYSTEM_GROUPING; |
2299 } | 2347 } |
2300 | 2348 |
2301 int32_t ia2_role = 0; | 2349 int32_t ia2_role = 0; |
2302 | 2350 |
2303 switch (GetData().role) { | 2351 switch (GetData().role) { |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2419 case ui::AX_ROLE_ABBR: | 2467 case ui::AX_ROLE_ABBR: |
2420 case ui::AX_ROLE_TIME: | 2468 case ui::AX_ROLE_TIME: |
2421 ia2_role = IA2_ROLE_TEXT_FRAME; | 2469 ia2_role = IA2_ROLE_TEXT_FRAME; |
2422 break; | 2470 break; |
2423 default: | 2471 default: |
2424 break; | 2472 break; |
2425 } | 2473 } |
2426 return ia2_role; | 2474 return ia2_role; |
2427 } | 2475 } |
2428 | 2476 |
| 2477 std::vector<base::string16> AXPlatformNodeWin::ComputeIA2Attributes() { |
| 2478 std::vector<base::string16> result; |
| 2479 // Expose some HTLM and ARIA attributes in the IAccessible2 attributes string. |
| 2480 // "display", "tag", and "xml-roles" have somewhat unusual names for |
| 2481 // historical reasons. Aside from that virtually every ARIA attribute |
| 2482 // is exposed in a really straightforward way, i.e. "aria-foo" is exposed |
| 2483 // as "foo". |
| 2484 StringAttributeToIA2(result, ui::AX_ATTR_DISPLAY, "display"); |
| 2485 StringAttributeToIA2(result, ui::AX_ATTR_HTML_TAG, "tag"); |
| 2486 StringAttributeToIA2(result, ui::AX_ATTR_ROLE, "xml-roles"); |
| 2487 StringAttributeToIA2(result, ui::AX_ATTR_PLACEHOLDER, "placeholder"); |
| 2488 |
| 2489 StringAttributeToIA2(result, ui::AX_ATTR_AUTO_COMPLETE, "autocomplete"); |
| 2490 StringAttributeToIA2(result, ui::AX_ATTR_ROLE_DESCRIPTION, "roledescription"); |
| 2491 StringAttributeToIA2(result, ui::AX_ATTR_KEY_SHORTCUTS, "keyshortcuts"); |
| 2492 |
| 2493 IntAttributeToIA2(result, ui::AX_ATTR_HIERARCHICAL_LEVEL, "level"); |
| 2494 IntAttributeToIA2(result, ui::AX_ATTR_SET_SIZE, "setsize"); |
| 2495 IntAttributeToIA2(result, ui::AX_ATTR_POS_IN_SET, "posinset"); |
| 2496 |
| 2497 if (HasIntAttribute(ui::AX_ATTR_CHECKED_STATE)) |
| 2498 result.push_back(L"checkable:true"); |
| 2499 |
| 2500 // Expose live region attributes. |
| 2501 StringAttributeToIA2(result, ui::AX_ATTR_LIVE_STATUS, "live"); |
| 2502 StringAttributeToIA2(result, ui::AX_ATTR_LIVE_RELEVANT, "relevant"); |
| 2503 BoolAttributeToIA2(result, ui::AX_ATTR_LIVE_ATOMIC, "atomic"); |
| 2504 BoolAttributeToIA2(result, ui::AX_ATTR_LIVE_BUSY, "busy"); |
| 2505 |
| 2506 // Expose container live region attributes. |
| 2507 StringAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_STATUS, |
| 2508 "container-live"); |
| 2509 StringAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_RELEVANT, |
| 2510 "container-relevant"); |
| 2511 BoolAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_ATOMIC, |
| 2512 "container-atomic"); |
| 2513 BoolAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_BUSY, "container-busy"); |
| 2514 |
| 2515 // Expose the non-standard explicit-name IA2 attribute. |
| 2516 int name_from; |
| 2517 if (GetIntAttribute(ui::AX_ATTR_NAME_FROM, &name_from) && |
| 2518 name_from != ui::AX_NAME_FROM_CONTENTS) { |
| 2519 result.push_back(L"explicit-name:true"); |
| 2520 } |
| 2521 |
| 2522 // Expose the aria-current attribute. |
| 2523 int32_t aria_current_state; |
| 2524 if (GetIntAttribute(ui::AX_ATTR_ARIA_CURRENT_STATE, &aria_current_state)) { |
| 2525 switch (static_cast<ui::AXAriaCurrentState>(aria_current_state)) { |
| 2526 case ui::AX_ARIA_CURRENT_STATE_NONE: |
| 2527 break; |
| 2528 case ui::AX_ARIA_CURRENT_STATE_FALSE: |
| 2529 result.push_back(L"current:false"); |
| 2530 break; |
| 2531 case ui::AX_ARIA_CURRENT_STATE_TRUE: |
| 2532 result.push_back(L"current:true"); |
| 2533 break; |
| 2534 case ui::AX_ARIA_CURRENT_STATE_PAGE: |
| 2535 result.push_back(L"current:page"); |
| 2536 break; |
| 2537 case ui::AX_ARIA_CURRENT_STATE_STEP: |
| 2538 result.push_back(L"current:step"); |
| 2539 break; |
| 2540 case ui::AX_ARIA_CURRENT_STATE_LOCATION: |
| 2541 result.push_back(L"current:location"); |
| 2542 break; |
| 2543 case ui::AX_ARIA_CURRENT_STATE_DATE: |
| 2544 result.push_back(L"current:date"); |
| 2545 break; |
| 2546 case ui::AX_ARIA_CURRENT_STATE_TIME: |
| 2547 result.push_back(L"current:time"); |
| 2548 break; |
| 2549 } |
| 2550 } |
| 2551 |
| 2552 // Expose table cell index. |
| 2553 if (ui::IsCellOrTableHeaderRole(GetData().role)) { |
| 2554 AXPlatformNodeBase* table = FromNativeViewAccessible(GetParent()); |
| 2555 |
| 2556 while (table && !ui::IsTableLikeRole(table->GetData().role)) |
| 2557 table = FromNativeViewAccessible(table->GetParent()); |
| 2558 |
| 2559 if (table) { |
| 2560 const std::vector<int32_t>& unique_cell_ids = |
| 2561 table->GetIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS); |
| 2562 for (size_t i = 0; i < unique_cell_ids.size(); ++i) { |
| 2563 if (unique_cell_ids[i] == GetData().id) { |
| 2564 result.push_back(base::string16(L"table-cell-index:") + |
| 2565 base::IntToString16(static_cast<int>(i))); |
| 2566 } |
| 2567 } |
| 2568 } |
| 2569 } |
| 2570 |
| 2571 // Expose aria-colcount and aria-rowcount in a table, grid or treegrid. |
| 2572 if (ui::IsTableLikeRole(GetData().role)) { |
| 2573 IntAttributeToIA2(result, ui::AX_ATTR_ARIA_COLUMN_COUNT, "colcount"); |
| 2574 IntAttributeToIA2(result, ui::AX_ATTR_ARIA_ROW_COUNT, "rowcount"); |
| 2575 } |
| 2576 |
| 2577 // Expose aria-colindex and aria-rowindex in a cell or row. |
| 2578 if (ui::IsCellOrTableHeaderRole(GetData().role) || |
| 2579 GetData().role == ui::AX_ROLE_ROW) { |
| 2580 if (GetData().role != ui::AX_ROLE_ROW) |
| 2581 IntAttributeToIA2(result, ui::AX_ATTR_ARIA_CELL_COLUMN_INDEX, "colindex"); |
| 2582 IntAttributeToIA2(result, ui::AX_ATTR_ARIA_CELL_ROW_INDEX, "rowindex"); |
| 2583 } |
| 2584 |
| 2585 // Expose row or column header sort direction. |
| 2586 int32_t sort_direction; |
| 2587 if ((MSAARole() == ROLE_SYSTEM_COLUMNHEADER || |
| 2588 MSAARole() == ROLE_SYSTEM_ROWHEADER) && |
| 2589 GetIntAttribute(ui::AX_ATTR_SORT_DIRECTION, &sort_direction)) { |
| 2590 switch (static_cast<ui::AXSortDirection>(sort_direction)) { |
| 2591 case ui::AX_SORT_DIRECTION_NONE: |
| 2592 break; |
| 2593 case ui::AX_SORT_DIRECTION_UNSORTED: |
| 2594 result.push_back(L"sort:none"); |
| 2595 break; |
| 2596 case ui::AX_SORT_DIRECTION_ASCENDING: |
| 2597 result.push_back(L"sort:ascending"); |
| 2598 break; |
| 2599 case ui::AX_SORT_DIRECTION_DESCENDING: |
| 2600 result.push_back(L"sort:descending"); |
| 2601 break; |
| 2602 case ui::AX_SORT_DIRECTION_OTHER: |
| 2603 result.push_back(L"sort:other"); |
| 2604 break; |
| 2605 } |
| 2606 } |
| 2607 |
| 2608 if (ui::IsCellOrTableHeaderRole(GetData().role)) { |
| 2609 // Expose colspan attribute. |
| 2610 base::string16 colspan; |
| 2611 if (GetData().GetHtmlAttribute("aria-colspan", &colspan)) { |
| 2612 SanitizeStringAttributeForIA2(colspan, &colspan); |
| 2613 result.push_back(L"colspan:" + colspan); |
| 2614 } |
| 2615 // Expose rowspan attribute. |
| 2616 base::string16 rowspan; |
| 2617 if (GetData().GetHtmlAttribute("aria-rowspan", &rowspan)) { |
| 2618 SanitizeStringAttributeForIA2(rowspan, &rowspan); |
| 2619 result.push_back(L"rowspan:" + rowspan); |
| 2620 } |
| 2621 } |
| 2622 |
| 2623 // Expose slider value. |
| 2624 if (IsRangeValueSupported()) { |
| 2625 base::string16 value = GetRangeValueText(); |
| 2626 SanitizeStringAttributeForIA2(value, &value); |
| 2627 result.push_back(L"valuetext:" + value); |
| 2628 } |
| 2629 |
| 2630 // Expose dropeffect attribute. |
| 2631 base::string16 drop_effect; |
| 2632 if (GetData().GetHtmlAttribute("aria-dropeffect", &drop_effect)) { |
| 2633 SanitizeStringAttributeForIA2(drop_effect, &drop_effect); |
| 2634 result.push_back(L"dropeffect:" + drop_effect); |
| 2635 } |
| 2636 |
| 2637 // Expose grabbed attribute. |
| 2638 base::string16 grabbed; |
| 2639 if (GetData().GetHtmlAttribute("aria-grabbed", &grabbed)) { |
| 2640 SanitizeStringAttributeForIA2(grabbed, &grabbed); |
| 2641 result.push_back(L"grabbed:" + grabbed); |
| 2642 } |
| 2643 |
| 2644 // Expose class attribute. |
| 2645 base::string16 class_attr; |
| 2646 if (GetData().GetHtmlAttribute("class", &class_attr)) { |
| 2647 SanitizeStringAttributeForIA2(class_attr, &class_attr); |
| 2648 result.push_back(L"class:" + class_attr); |
| 2649 } |
| 2650 |
| 2651 // Expose datetime attribute. |
| 2652 base::string16 datetime; |
| 2653 if (GetData().role == ui::AX_ROLE_TIME && |
| 2654 GetData().GetHtmlAttribute("datetime", &datetime)) { |
| 2655 SanitizeStringAttributeForIA2(datetime, &datetime); |
| 2656 result.push_back(L"datetime:" + datetime); |
| 2657 } |
| 2658 |
| 2659 // Expose id attribute. |
| 2660 base::string16 id; |
| 2661 if (GetData().GetHtmlAttribute("id", &id)) { |
| 2662 SanitizeStringAttributeForIA2(id, &id); |
| 2663 result.push_back(L"id:" + id); |
| 2664 } |
| 2665 |
| 2666 // Expose src attribute. |
| 2667 base::string16 src; |
| 2668 if (GetData().role == ui::AX_ROLE_IMAGE && |
| 2669 GetData().GetHtmlAttribute("src", &src)) { |
| 2670 SanitizeStringAttributeForIA2(src, &src); |
| 2671 result.push_back(L"src:" + src); |
| 2672 } |
| 2673 |
| 2674 // Expose input-text type attribute. |
| 2675 base::string16 type; |
| 2676 base::string16 html_tag = GetString16Attribute(ui::AX_ATTR_HTML_TAG); |
| 2677 if (IsSimpleTextControl() && html_tag == L"input" && |
| 2678 GetData().GetHtmlAttribute("type", &type)) { |
| 2679 SanitizeStringAttributeForIA2(type, &type); |
| 2680 result.push_back(L"text-input-type:" + type); |
| 2681 } |
| 2682 |
| 2683 return result; |
| 2684 } |
| 2685 |
2429 bool AXPlatformNodeWin::ShouldNodeHaveReadonlyState( | 2686 bool AXPlatformNodeWin::ShouldNodeHaveReadonlyState( |
2430 const AXNodeData& data) const { | 2687 const AXNodeData& data) const { |
2431 if (data.GetBoolAttribute(ui::AX_ATTR_ARIA_READONLY)) | 2688 if (data.GetBoolAttribute(ui::AX_ATTR_ARIA_READONLY)) |
2432 return true; | 2689 return true; |
2433 | 2690 |
2434 if (!data.HasState(ui::AX_STATE_READ_ONLY)) | 2691 if (!data.HasState(ui::AX_STATE_READ_ONLY)) |
2435 return false; | 2692 return false; |
2436 | 2693 |
2437 switch (data.role) { | 2694 switch (data.role) { |
2438 case ui::AX_ROLE_ARTICLE: | 2695 case ui::AX_ROLE_ARTICLE: |
(...skipping 321 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2760 if (container && container->GetData().role == ui::AX_ROLE_GROUP) | 3017 if (container && container->GetData().role == ui::AX_ROLE_GROUP) |
2761 container = FromNativeViewAccessible(container->GetParent()); | 3018 container = FromNativeViewAccessible(container->GetParent()); |
2762 | 3019 |
2763 if (!container) | 3020 if (!container) |
2764 return false; | 3021 return false; |
2765 | 3022 |
2766 return container->GetData().role == ui::AX_ROLE_TREE_GRID; | 3023 return container->GetData().role == ui::AX_ROLE_TREE_GRID; |
2767 } | 3024 } |
2768 | 3025 |
2769 } // namespace ui | 3026 } // namespace ui |
OLD | NEW |