OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "base/command_line.h" | 5 #include "base/command_line.h" |
6 #include "base/compiler_specific.h" | 6 #include "base/compiler_specific.h" |
7 #include "base/stl_util.h" | 7 #include "base/stl_util.h" |
8 #include "base/string16.h" | 8 #include "base/string16.h" |
9 #include "content/browser/browser_thread_impl.h" | 9 #include "content/browser/browser_thread_impl.h" |
10 #include "content/browser/browsing_instance.h" | 10 #include "content/browser/browsing_instance.h" |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
128 url_util::AddStandardScheme(kPrivilegedScheme); | 128 url_util::AddStandardScheme(kPrivilegedScheme); |
129 url_util::AddStandardScheme(chrome::kChromeUIScheme); | 129 url_util::AddStandardScheme(chrome::kChromeUIScheme); |
130 } | 130 } |
131 | 131 |
132 virtual void TearDown() { | 132 virtual void TearDown() { |
133 // Ensure that no RenderProcessHosts are left over after the tests. | 133 // Ensure that no RenderProcessHosts are left over after the tests. |
134 EXPECT_TRUE(content::RenderProcessHost::AllHostsIterator().IsAtEnd()); | 134 EXPECT_TRUE(content::RenderProcessHost::AllHostsIterator().IsAtEnd()); |
135 | 135 |
136 content::GetContentClient()->set_browser_for_testing(old_browser_client_); | 136 content::GetContentClient()->set_browser_for_testing(old_browser_client_); |
137 content::SetContentClient(old_client_); | 137 content::SetContentClient(old_client_); |
138 MessageLoop::current()->RunAllPending(); | |
139 message_loop_.RunAllPending(); | |
140 } | 138 } |
141 | 139 |
142 void set_privileged_process_id(int process_id) { | 140 void set_privileged_process_id(int process_id) { |
143 browser_client_.set_privileged_process_id(process_id); | 141 browser_client_.set_privileged_process_id(process_id); |
144 } | 142 } |
145 | 143 |
| 144 void DrainMessageLoops() { |
| 145 // We don't just do this in TearDown() because we create TestBrowserContext |
| 146 // objects in each test, which will be destructed before |
| 147 // TearDown() is called. |
| 148 MessageLoop::current()->RunAllPending(); |
| 149 message_loop_.RunAllPending(); |
| 150 } |
| 151 |
146 private: | 152 private: |
147 MessageLoopForUI message_loop_; | 153 MessageLoopForUI message_loop_; |
148 content::TestBrowserThread ui_thread_; | 154 content::TestBrowserThread ui_thread_; |
149 content::TestBrowserThread file_user_blocking_thread_; | 155 content::TestBrowserThread file_user_blocking_thread_; |
150 content::TestBrowserThread io_thread_; | 156 content::TestBrowserThread io_thread_; |
151 | 157 |
152 SiteInstanceTestClient client_; | 158 SiteInstanceTestClient client_; |
153 SiteInstanceTestBrowserClient browser_client_; | 159 SiteInstanceTestBrowserClient browser_client_; |
154 content::ContentClient* old_client_; | 160 content::ContentClient* old_client_; |
155 content::ContentBrowserClient* old_browser_client_; | 161 content::ContentBrowserClient* old_browser_client_; |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
251 { | 257 { |
252 scoped_ptr<WebContentsImpl> web_contents( | 258 scoped_ptr<WebContentsImpl> web_contents( |
253 WebContentsImpl::Create(browser_context.get(), instance, | 259 WebContentsImpl::Create(browser_context.get(), instance, |
254 MSG_ROUTING_NONE, NULL)); | 260 MSG_ROUTING_NONE, NULL)); |
255 EXPECT_EQ(1, site_delete_counter); | 261 EXPECT_EQ(1, site_delete_counter); |
256 EXPECT_EQ(1, browsing_delete_counter); | 262 EXPECT_EQ(1, browsing_delete_counter); |
257 } | 263 } |
258 | 264 |
259 // Make sure that we flush any messages related to the above WebContentsImpl | 265 // Make sure that we flush any messages related to the above WebContentsImpl |
260 // destruction. | 266 // destruction. |
261 MessageLoop::current()->RunAllPending(); | 267 DrainMessageLoops(); |
262 | 268 |
263 EXPECT_EQ(2, site_delete_counter); | 269 EXPECT_EQ(2, site_delete_counter); |
264 EXPECT_EQ(2, browsing_delete_counter); | 270 EXPECT_EQ(2, browsing_delete_counter); |
265 // contents is now deleted, along with instance and browsing_instance | 271 // contents is now deleted, along with instance and browsing_instance |
266 } | 272 } |
267 | 273 |
268 // Test that NavigationEntries with SiteInstances can be cloned, but that their | 274 // Test that NavigationEntries with SiteInstances can be cloned, but that their |
269 // SiteInstances can be changed afterwards. Also tests that the ref counts are | 275 // SiteInstances can be changed afterwards. Also tests that the ref counts are |
270 // updated properly after the change. | 276 // updated properly after the change. |
271 TEST_F(SiteInstanceTest, CloneNavigationEntry) { | 277 TEST_F(SiteInstanceTest, CloneNavigationEntry) { |
(...skipping 24 matching lines...) Expand all Loading... |
296 EXPECT_EQ(1, site_delete_counter1); | 302 EXPECT_EQ(1, site_delete_counter1); |
297 EXPECT_EQ(0, site_delete_counter2); | 303 EXPECT_EQ(0, site_delete_counter2); |
298 | 304 |
299 // The second SiteInstance should go away after deleting e2. | 305 // The second SiteInstance should go away after deleting e2. |
300 delete e2; | 306 delete e2; |
301 EXPECT_EQ(1, site_delete_counter1); | 307 EXPECT_EQ(1, site_delete_counter1); |
302 EXPECT_EQ(1, site_delete_counter2); | 308 EXPECT_EQ(1, site_delete_counter2); |
303 | 309 |
304 // Both BrowsingInstances are also now deleted | 310 // Both BrowsingInstances are also now deleted |
305 EXPECT_EQ(2, browsing_delete_counter); | 311 EXPECT_EQ(2, browsing_delete_counter); |
| 312 |
| 313 DrainMessageLoops(); |
306 } | 314 } |
307 | 315 |
308 // Test to ensure GetProcess returns and creates processes correctly. | 316 // Test to ensure GetProcess returns and creates processes correctly. |
309 TEST_F(SiteInstanceTest, GetProcess) { | 317 TEST_F(SiteInstanceTest, GetProcess) { |
310 // Ensure that GetProcess returns a process. | 318 // Ensure that GetProcess returns a process. |
311 scoped_ptr<content::TestBrowserContext> browser_context( | 319 scoped_ptr<content::TestBrowserContext> browser_context( |
312 new content::TestBrowserContext()); | 320 new content::TestBrowserContext()); |
313 scoped_ptr<content::RenderProcessHost> host1; | 321 scoped_ptr<content::RenderProcessHost> host1; |
314 scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>( | 322 scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>( |
315 SiteInstance::Create(browser_context.get()))); | 323 SiteInstance::Create(browser_context.get()))); |
316 host1.reset(instance->GetProcess()); | 324 host1.reset(instance->GetProcess()); |
317 EXPECT_TRUE(host1.get() != NULL); | 325 EXPECT_TRUE(host1.get() != NULL); |
318 | 326 |
319 // Ensure that GetProcess creates a new process. | 327 // Ensure that GetProcess creates a new process. |
320 scoped_refptr<SiteInstanceImpl> instance2(static_cast<SiteInstanceImpl*>( | 328 scoped_refptr<SiteInstanceImpl> instance2(static_cast<SiteInstanceImpl*>( |
321 SiteInstance::Create(browser_context.get()))); | 329 SiteInstance::Create(browser_context.get()))); |
322 scoped_ptr<content::RenderProcessHost> host2(instance2->GetProcess()); | 330 scoped_ptr<content::RenderProcessHost> host2(instance2->GetProcess()); |
323 EXPECT_TRUE(host2.get() != NULL); | 331 EXPECT_TRUE(host2.get() != NULL); |
324 EXPECT_NE(host1.get(), host2.get()); | 332 EXPECT_NE(host1.get(), host2.get()); |
| 333 |
| 334 DrainMessageLoops(); |
325 } | 335 } |
326 | 336 |
327 // Test to ensure SetSite and site() work properly. | 337 // Test to ensure SetSite and site() work properly. |
328 TEST_F(SiteInstanceTest, SetSite) { | 338 TEST_F(SiteInstanceTest, SetSite) { |
329 scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>( | 339 scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>( |
330 SiteInstance::Create(NULL))); | 340 SiteInstance::Create(NULL))); |
331 EXPECT_FALSE(instance->HasSite()); | 341 EXPECT_FALSE(instance->HasSite()); |
332 EXPECT_TRUE(instance->GetSite().is_empty()); | 342 EXPECT_TRUE(instance->GetSite().is_empty()); |
333 | 343 |
334 instance->SetSite(GURL("http://www.google.com/index.html")); | 344 instance->SetSite(GURL("http://www.google.com/index.html")); |
335 EXPECT_EQ(GURL("http://google.com"), instance->GetSite()); | 345 EXPECT_EQ(GURL("http://google.com"), instance->GetSite()); |
336 | 346 |
337 EXPECT_TRUE(instance->HasSite()); | 347 EXPECT_TRUE(instance->HasSite()); |
| 348 |
| 349 DrainMessageLoops(); |
338 } | 350 } |
339 | 351 |
340 // Test to ensure GetSiteForURL properly returns sites for URLs. | 352 // Test to ensure GetSiteForURL properly returns sites for URLs. |
341 TEST_F(SiteInstanceTest, GetSiteForURL) { | 353 TEST_F(SiteInstanceTest, GetSiteForURL) { |
342 // Pages are irrelevant. | 354 // Pages are irrelevant. |
343 GURL test_url = GURL("http://www.google.com/index.html"); | 355 GURL test_url = GURL("http://www.google.com/index.html"); |
344 EXPECT_EQ(GURL("http://google.com"), | 356 EXPECT_EQ(GURL("http://google.com"), |
345 SiteInstanceImpl::GetSiteForURL(NULL, test_url)); | 357 SiteInstanceImpl::GetSiteForURL(NULL, test_url)); |
346 | 358 |
347 // Ports are irrlevant. | 359 // Ports are irrlevant. |
(...skipping 13 matching lines...) Expand all Loading... |
361 EXPECT_EQ(GURL(), SiteInstanceImpl::GetSiteForURL(NULL, test_url)); | 373 EXPECT_EQ(GURL(), SiteInstanceImpl::GetSiteForURL(NULL, test_url)); |
362 | 374 |
363 test_url = GURL("guest://abc123"); | 375 test_url = GURL("guest://abc123"); |
364 EXPECT_EQ(GURL("guest://abc123"), SiteInstanceImpl::GetSiteForURL( | 376 EXPECT_EQ(GURL("guest://abc123"), SiteInstanceImpl::GetSiteForURL( |
365 NULL, test_url)); | 377 NULL, test_url)); |
366 // TODO(creis): Do we want to special case file URLs to ensure they have | 378 // TODO(creis): Do we want to special case file URLs to ensure they have |
367 // either no site or a special "file://" site? We currently return | 379 // either no site or a special "file://" site? We currently return |
368 // "file://home/" as the site, which seems broken. | 380 // "file://home/" as the site, which seems broken. |
369 // test_url = GURL("file://home/"); | 381 // test_url = GURL("file://home/"); |
370 // EXPECT_EQ(GURL(), SiteInstanceImpl::GetSiteForURL(NULL, test_url)); | 382 // EXPECT_EQ(GURL(), SiteInstanceImpl::GetSiteForURL(NULL, test_url)); |
| 383 |
| 384 DrainMessageLoops(); |
371 } | 385 } |
372 | 386 |
373 // Test of distinguishing URLs from different sites. Most of this logic is | 387 // Test of distinguishing URLs from different sites. Most of this logic is |
374 // tested in RegistryControlledDomainTest. This test focuses on URLs with | 388 // tested in RegistryControlledDomainTest. This test focuses on URLs with |
375 // different schemes or ports. | 389 // different schemes or ports. |
376 TEST_F(SiteInstanceTest, IsSameWebSite) { | 390 TEST_F(SiteInstanceTest, IsSameWebSite) { |
377 GURL url_foo = GURL("http://foo/a.html"); | 391 GURL url_foo = GURL("http://foo/a.html"); |
378 GURL url_foo2 = GURL("http://foo/b.html"); | 392 GURL url_foo2 = GURL("http://foo/b.html"); |
379 GURL url_foo_https = GURL("https://foo/a.html"); | 393 GURL url_foo_https = GURL("https://foo/a.html"); |
380 GURL url_foo_port = GURL("http://foo:8080/a.html"); | 394 GURL url_foo_port = GURL("http://foo:8080/a.html"); |
381 GURL url_javascript = GURL("javascript:alert(1);"); | 395 GURL url_javascript = GURL("javascript:alert(1);"); |
382 | 396 |
383 // Same scheme and port -> same site. | 397 // Same scheme and port -> same site. |
384 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo2)); | 398 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo2)); |
385 | 399 |
386 // Different scheme -> different site. | 400 // Different scheme -> different site. |
387 EXPECT_FALSE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_https)); | 401 EXPECT_FALSE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_https)); |
388 | 402 |
389 // Different port -> same site. | 403 // Different port -> same site. |
390 // (Changes to document.domain make renderer ignore the port.) | 404 // (Changes to document.domain make renderer ignore the port.) |
391 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_port)); | 405 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_port)); |
392 | 406 |
393 // JavaScript links should be considered same site for anything. | 407 // JavaScript links should be considered same site for anything. |
394 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo)); | 408 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo)); |
395 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_https)); | 409 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_https)); |
396 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_port)); | 410 EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_port)); |
| 411 |
| 412 DrainMessageLoops(); |
397 } | 413 } |
398 | 414 |
399 // Test to ensure that there is only one SiteInstance per site in a given | 415 // Test to ensure that there is only one SiteInstance per site in a given |
400 // BrowsingInstance, when process-per-site is not in use. | 416 // BrowsingInstance, when process-per-site is not in use. |
401 TEST_F(SiteInstanceTest, OneSiteInstancePerSite) { | 417 TEST_F(SiteInstanceTest, OneSiteInstancePerSite) { |
402 ASSERT_FALSE(CommandLine::ForCurrentProcess()->HasSwitch( | 418 ASSERT_FALSE(CommandLine::ForCurrentProcess()->HasSwitch( |
403 switches::kProcessPerSite)); | 419 switches::kProcessPerSite)); |
404 int delete_counter = 0; | 420 int delete_counter = 0; |
405 scoped_ptr<content::TestBrowserContext> browser_context( | 421 scoped_ptr<content::TestBrowserContext> browser_context( |
406 new content::TestBrowserContext()); | 422 new content::TestBrowserContext()); |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
461 GURL("http://mail.yahoo.com"))); | 477 GURL("http://mail.yahoo.com"))); |
462 | 478 |
463 // Should be able to see that we don't have SiteInstances. | 479 // Should be able to see that we don't have SiteInstances. |
464 EXPECT_FALSE(browsing_instance->HasSiteInstance( | 480 EXPECT_FALSE(browsing_instance->HasSiteInstance( |
465 GURL("https://www.google.com"))); | 481 GURL("https://www.google.com"))); |
466 EXPECT_FALSE(browsing_instance2->HasSiteInstance( | 482 EXPECT_FALSE(browsing_instance2->HasSiteInstance( |
467 GURL("http://www.yahoo.com"))); | 483 GURL("http://www.yahoo.com"))); |
468 | 484 |
469 // browsing_instances will be deleted when their SiteInstances are deleted. | 485 // browsing_instances will be deleted when their SiteInstances are deleted. |
470 // The processes will be unregistered when the RPH scoped_ptrs go away. | 486 // The processes will be unregistered when the RPH scoped_ptrs go away. |
| 487 |
| 488 DrainMessageLoops(); |
471 } | 489 } |
472 | 490 |
473 // Test to ensure that there is only one RenderProcessHost per site for an | 491 // Test to ensure that there is only one RenderProcessHost per site for an |
474 // entire BrowserContext, if process-per-site is in use. | 492 // entire BrowserContext, if process-per-site is in use. |
475 TEST_F(SiteInstanceTest, OneSiteInstancePerSiteInBrowserContext) { | 493 TEST_F(SiteInstanceTest, OneSiteInstancePerSiteInBrowserContext) { |
476 CommandLine::ForCurrentProcess()->AppendSwitch( | 494 CommandLine::ForCurrentProcess()->AppendSwitch( |
477 switches::kProcessPerSite); | 495 switches::kProcessPerSite); |
478 int delete_counter = 0; | 496 int delete_counter = 0; |
479 scoped_ptr<content::TestBrowserContext> browser_context( | 497 scoped_ptr<content::TestBrowserContext> browser_context( |
480 new content::TestBrowserContext()); | 498 new content::TestBrowserContext()); |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
546 // Should be able to see that we don't have SiteInstances. | 564 // Should be able to see that we don't have SiteInstances. |
547 EXPECT_FALSE(browsing_instance2->HasSiteInstance( | 565 EXPECT_FALSE(browsing_instance2->HasSiteInstance( |
548 GURL("http://www.yahoo.com"))); // different BI, same browser context | 566 GURL("http://www.yahoo.com"))); // different BI, same browser context |
549 EXPECT_FALSE(browsing_instance->HasSiteInstance( | 567 EXPECT_FALSE(browsing_instance->HasSiteInstance( |
550 GURL("https://www.google.com"))); // not visited before | 568 GURL("https://www.google.com"))); // not visited before |
551 EXPECT_FALSE(browsing_instance3->HasSiteInstance( | 569 EXPECT_FALSE(browsing_instance3->HasSiteInstance( |
552 GURL("http://www.yahoo.com"))); // different BI, different context | 570 GURL("http://www.yahoo.com"))); // different BI, different context |
553 | 571 |
554 // browsing_instances will be deleted when their SiteInstances are deleted. | 572 // browsing_instances will be deleted when their SiteInstances are deleted. |
555 // The processes will be unregistered when the RPH scoped_ptrs go away. | 573 // The processes will be unregistered when the RPH scoped_ptrs go away. |
| 574 |
| 575 DrainMessageLoops(); |
556 } | 576 } |
557 | 577 |
558 static SiteInstanceImpl* CreateSiteInstance( | 578 static SiteInstanceImpl* CreateSiteInstance( |
559 content::BrowserContext* browser_context, | 579 content::BrowserContext* browser_context, |
560 content::RenderProcessHostFactory* factory, | 580 content::RenderProcessHostFactory* factory, |
561 const GURL& url) { | 581 const GURL& url) { |
562 SiteInstanceImpl* instance = | 582 SiteInstanceImpl* instance = |
563 reinterpret_cast<SiteInstanceImpl*>( | 583 reinterpret_cast<SiteInstanceImpl*>( |
564 SiteInstance::CreateForURL(browser_context, url)); | 584 SiteInstance::CreateForURL(browser_context, url)); |
565 instance->set_render_process_host_factory(factory); | 585 instance->set_render_process_host_factory(factory); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
611 | 631 |
612 // Make sure none of differing privilege processes are mixed. | 632 // Make sure none of differing privilege processes are mixed. |
613 EXPECT_NE(extension1_instance->GetProcess(), webui1_instance->GetProcess()); | 633 EXPECT_NE(extension1_instance->GetProcess(), webui1_instance->GetProcess()); |
614 | 634 |
615 for (size_t i = 0; i < content::kMaxRendererProcessCount; ++i) { | 635 for (size_t i = 0; i < content::kMaxRendererProcessCount; ++i) { |
616 EXPECT_NE(extension1_instance->GetProcess(), hosts[i]); | 636 EXPECT_NE(extension1_instance->GetProcess(), hosts[i]); |
617 EXPECT_NE(webui1_instance->GetProcess(), hosts[i]); | 637 EXPECT_NE(webui1_instance->GetProcess(), hosts[i]); |
618 } | 638 } |
619 | 639 |
620 STLDeleteContainerPointers(hosts.begin(), hosts.end()); | 640 STLDeleteContainerPointers(hosts.begin(), hosts.end()); |
| 641 |
| 642 DrainMessageLoops(); |
621 } | 643 } |
622 | 644 |
623 // Test to ensure that HasWrongProcessForURL behaves properly for different | 645 // Test to ensure that HasWrongProcessForURL behaves properly for different |
624 // types of URLs. | 646 // types of URLs. |
625 TEST_F(SiteInstanceTest, HasWrongProcessForURL) { | 647 TEST_F(SiteInstanceTest, HasWrongProcessForURL) { |
626 scoped_ptr<content::TestBrowserContext> browser_context( | 648 scoped_ptr<content::TestBrowserContext> browser_context( |
627 new content::TestBrowserContext()); | 649 new content::TestBrowserContext()); |
628 scoped_ptr<content::RenderProcessHost> host; | 650 scoped_ptr<content::RenderProcessHost> host; |
629 scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>( | 651 scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>( |
630 SiteInstance::Create(browser_context.get()))); | 652 SiteInstance::Create(browser_context.get()))); |
(...skipping 12 matching lines...) Expand all Loading... |
643 // fine, but might be a cause for problems in different contexts. | 665 // fine, but might be a cause for problems in different contexts. |
644 host.reset(instance->GetProcess()); | 666 host.reset(instance->GetProcess()); |
645 EXPECT_TRUE(host.get() != NULL); | 667 EXPECT_TRUE(host.get() != NULL); |
646 EXPECT_TRUE(instance->HasProcess()); | 668 EXPECT_TRUE(instance->HasProcess()); |
647 | 669 |
648 EXPECT_FALSE(instance->HasWrongProcessForURL(GURL("http://evernote.com"))); | 670 EXPECT_FALSE(instance->HasWrongProcessForURL(GURL("http://evernote.com"))); |
649 EXPECT_FALSE(instance->HasWrongProcessForURL( | 671 EXPECT_FALSE(instance->HasWrongProcessForURL( |
650 GURL("javascript:alert(document.location.href);"))); | 672 GURL("javascript:alert(document.location.href);"))); |
651 | 673 |
652 EXPECT_TRUE(instance->HasWrongProcessForURL(GURL("chrome://settings"))); | 674 EXPECT_TRUE(instance->HasWrongProcessForURL(GURL("chrome://settings"))); |
| 675 |
| 676 DrainMessageLoops(); |
653 } | 677 } |
OLD | NEW |