Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 <string> | 5 #include <string> |
| 6 | 6 |
| 7 #include "base/bind.h" | |
| 7 #include "base/command_line.h" | 8 #include "base/command_line.h" |
| 8 #include "base/macros.h" | 9 #include "base/macros.h" |
| 9 #include "base/memory/ref_counted.h" | 10 #include "base/memory/ref_counted.h" |
| 10 #include "base/memory/scoped_ptr.h" | 11 #include "base/memory/scoped_ptr.h" |
| 11 #include "base/strings/string16.h" | 12 #include "base/strings/string16.h" |
| 12 #include "base/strings/utf_string_conversions.h" | 13 #include "base/strings/utf_string_conversions.h" |
| 13 #include "chrome/app/chrome_command_ids.h" | 14 #include "chrome/app/chrome_command_ids.h" |
| 14 #include "chrome/browser/chrome_notification_types.h" | 15 #include "chrome/browser/chrome_notification_types.h" |
| 15 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h" | 16 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h" |
| 16 #include "chrome/browser/renderer_context_menu/render_view_context_menu_browsert est_util.h" | 17 #include "chrome/browser/renderer_context_menu/render_view_context_menu_browsert est_util.h" |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 27 #include "content/public/browser/browser_message_filter.h" | 28 #include "content/public/browser/browser_message_filter.h" |
| 28 #include "content/public/browser/browser_thread.h" | 29 #include "content/public/browser/browser_thread.h" |
| 29 #include "content/public/browser/navigation_controller.h" | 30 #include "content/public/browser/navigation_controller.h" |
| 30 #include "content/public/browser/navigation_entry.h" | 31 #include "content/public/browser/navigation_entry.h" |
| 31 #include "content/public/browser/notification_service.h" | 32 #include "content/public/browser/notification_service.h" |
| 32 #include "content/public/browser/render_process_host.h" | 33 #include "content/public/browser/render_process_host.h" |
| 33 #include "content/public/browser/render_view_host.h" | 34 #include "content/public/browser/render_view_host.h" |
| 34 #include "content/public/browser/web_contents.h" | 35 #include "content/public/browser/web_contents.h" |
| 35 #include "content/public/test/browser_test_utils.h" | 36 #include "content/public/test/browser_test_utils.h" |
| 36 #include "content/public/test/test_utils.h" | 37 #include "content/public/test/test_utils.h" |
| 38 #include "net/base/load_flags.h" | |
| 39 #include "net/url_request/url_request.h" | |
| 40 #include "net/url_request/url_request_filter.h" | |
| 41 #include "net/url_request/url_request_interceptor.h" | |
| 37 #include "third_party/WebKit/public/web/WebContextMenuData.h" | 42 #include "third_party/WebKit/public/web/WebContextMenuData.h" |
| 38 #include "third_party/WebKit/public/web/WebInputEvent.h" | 43 #include "third_party/WebKit/public/web/WebInputEvent.h" |
| 39 | 44 |
| 40 using content::WebContents; | 45 using content::WebContents; |
| 41 | 46 |
| 42 namespace { | 47 namespace { |
| 43 | 48 |
| 44 class ContextMenuBrowserTest : public InProcessBrowserTest { | 49 class ContextMenuBrowserTest : public InProcessBrowserTest { |
| 45 public: | 50 public: |
| 46 ContextMenuBrowserTest() {} | 51 ContextMenuBrowserTest() {} |
| 47 | 52 |
| 48 TestRenderViewContextMenu* CreateContextMenu( | 53 content::ContextMenuParams CreateContextMenuParams( |
| 49 const GURL& unfiltered_url, | 54 const GURL& unfiltered_url, |
| 50 const GURL& url, | 55 const GURL& url, |
| 51 blink::WebContextMenuData::MediaType media_type) { | 56 blink::WebContextMenuData::MediaType media_type) { |
| 52 content::ContextMenuParams params; | 57 content::ContextMenuParams params; |
| 53 params.media_type = media_type; | 58 params.media_type = media_type; |
| 54 params.unfiltered_link_url = unfiltered_url; | 59 params.unfiltered_link_url = unfiltered_url; |
| 55 params.link_url = url; | 60 params.link_url = url; |
| 56 params.src_url = url; | 61 params.src_url = url; |
| 57 WebContents* web_contents = | 62 WebContents* web_contents = |
| 58 browser()->tab_strip_model()->GetActiveWebContents(); | 63 browser()->tab_strip_model()->GetActiveWebContents(); |
| 59 params.page_url = web_contents->GetController().GetActiveEntry()->GetURL(); | 64 params.page_url = web_contents->GetController().GetActiveEntry()->GetURL(); |
| 60 #if defined(OS_MACOSX) | 65 #if defined(OS_MACOSX) |
| 61 params.writing_direction_default = 0; | 66 params.writing_direction_default = 0; |
| 62 params.writing_direction_left_to_right = 0; | 67 params.writing_direction_left_to_right = 0; |
| 63 params.writing_direction_right_to_left = 0; | 68 params.writing_direction_right_to_left = 0; |
| 64 #endif // OS_MACOSX | 69 #endif // OS_MACOSX |
| 70 return params; | |
| 71 } | |
| 72 | |
| 73 TestRenderViewContextMenu* CreateContextMenu( | |
| 74 content::ContextMenuParams params) { | |
| 65 TestRenderViewContextMenu* menu = new TestRenderViewContextMenu( | 75 TestRenderViewContextMenu* menu = new TestRenderViewContextMenu( |
| 66 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(), | 76 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(), |
| 67 params); | 77 params); |
| 68 menu->Init(); | 78 menu->Init(); |
| 69 return menu; | 79 return menu; |
| 70 } | 80 } |
| 71 | 81 |
| 72 TestRenderViewContextMenu* CreateContextMenuMediaTypeNone( | 82 TestRenderViewContextMenu* CreateContextMenuMediaTypeNone( |
| 73 const GURL& unfiltered_url, | 83 const GURL& unfiltered_url, |
| 74 const GURL& url) { | 84 const GURL& url) { |
| 75 return CreateContextMenu(unfiltered_url, url, | 85 return CreateContextMenu(CreateContextMenuParams( |
| 76 blink::WebContextMenuData::MediaTypeNone); | 86 unfiltered_url, url, blink::WebContextMenuData::MediaTypeNone)); |
| 77 } | 87 } |
| 78 }; | 88 }; |
| 79 | 89 |
| 80 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, | 90 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, |
| 81 OpenEntryPresentForNormalURLs) { | 91 OpenEntryPresentForNormalURLs) { |
| 82 scoped_ptr<TestRenderViewContextMenu> menu(CreateContextMenuMediaTypeNone( | 92 scoped_ptr<TestRenderViewContextMenu> menu(CreateContextMenuMediaTypeNone( |
| 83 GURL("http://www.google.com/"), GURL("http://www.google.com/"))); | 93 GURL("http://www.google.com/"), GURL("http://www.google.com/"))); |
| 84 | 94 |
| 85 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB)); | 95 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB)); |
| 86 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW)); | 96 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW)); |
| (...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 301 TestRenderViewContextMenu menu(tab->GetMainFrame(), context_menu_params); | 311 TestRenderViewContextMenu menu(tab->GetMainFrame(), context_menu_params); |
| 302 menu.Init(); | 312 menu.Init(); |
| 303 | 313 |
| 304 // The item shouldn't be enabled in the menu. | 314 // The item shouldn't be enabled in the menu. |
| 305 EXPECT_FALSE(menu.IsCommandIdEnabled(IDC_CONTENT_CONTEXT_VIEWPAGEINFO)); | 315 EXPECT_FALSE(menu.IsCommandIdEnabled(IDC_CONTENT_CONTEXT_VIEWPAGEINFO)); |
| 306 | 316 |
| 307 // Ensure that viewing page info doesn't crash even if you can get to it. | 317 // Ensure that viewing page info doesn't crash even if you can get to it. |
| 308 menu.ExecuteCommand(IDC_CONTENT_CONTEXT_VIEWPAGEINFO, 0); | 318 menu.ExecuteCommand(IDC_CONTENT_CONTEXT_VIEWPAGEINFO, 0); |
| 309 } | 319 } |
| 310 | 320 |
| 321 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, DataSaverLoadImage) { | |
|
Lei Zhang
2015/05/11 20:16:53
Can this be a unit test instead?
megjablon
2015/05/11 22:58:40
I looked into writing it as a unit test, but the I
Lei Zhang
2015/05/12 21:16:39
It's probably possible, just a pain to set up all
megjablon
2015/05/13 23:36:38
Just found out someone is already doing this work
Lei Zhang
2015/05/13 23:50:19
Yay! Can you add a TODO to convert ContextMenuBrow
megjablon
2015/05/14 19:26:24
Done.
| |
| 322 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | |
| 323 command_line->AppendSwitch( | |
| 324 data_reduction_proxy::switches::kEnableDataReductionProxyLoFi); | |
| 325 | |
| 326 content::ContextMenuParams params = CreateContextMenuParams( | |
| 327 GURL(), GURL("http://url.com/image.png"), | |
| 328 blink::WebContextMenuData::MediaTypeImage); | |
| 329 params.image_was_fetched_lo_fi = true; | |
| 330 | |
| 331 scoped_ptr<TestRenderViewContextMenu> menu(CreateContextMenu(params)); | |
| 332 | |
| 333 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_RELOAD_ORIGINAL_IMAGE)); | |
| 334 } | |
| 335 | |
| 311 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, DataSaverOpenOrigImageInNewTab) { | 336 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, DataSaverOpenOrigImageInNewTab) { |
| 312 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | 337 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| 313 command_line->AppendSwitch( | 338 command_line->AppendSwitch( |
| 314 data_reduction_proxy::switches::kEnableDataReductionProxy); | 339 data_reduction_proxy::switches::kEnableDataReductionProxy); |
| 315 | 340 |
| 316 scoped_ptr<TestRenderViewContextMenu> menu( | 341 scoped_ptr<TestRenderViewContextMenu> menu( |
| 317 CreateContextMenu(GURL(), GURL("http://url.com/image.png"), | 342 CreateContextMenu(CreateContextMenuParams( |
| 318 blink::WebContextMenuData::MediaTypeImage)); | 343 GURL(), GURL("http://url.com/image.png"), |
| 344 blink::WebContextMenuData::MediaTypeImage))); | |
| 319 | 345 |
| 320 ASSERT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB)); | 346 ASSERT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB)); |
| 321 ASSERT_TRUE(menu->IsItemPresent( | 347 ASSERT_TRUE(menu->IsItemPresent( |
| 322 IDC_CONTENT_CONTEXT_OPEN_ORIGINAL_IMAGE_NEW_TAB)); | 348 IDC_CONTENT_CONTEXT_OPEN_ORIGINAL_IMAGE_NEW_TAB)); |
| 323 } | 349 } |
| 324 | 350 |
| 325 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, | 351 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, |
| 326 DataSaverHttpsOpenImageInNewTab) { | 352 DataSaverHttpsOpenImageInNewTab) { |
| 327 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | 353 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| 328 command_line->AppendSwitch( | 354 command_line->AppendSwitch( |
| 329 data_reduction_proxy::switches::kEnableDataReductionProxy); | 355 data_reduction_proxy::switches::kEnableDataReductionProxy); |
| 330 | 356 |
| 331 scoped_ptr<TestRenderViewContextMenu> menu( | 357 scoped_ptr<TestRenderViewContextMenu> menu( |
| 332 CreateContextMenu(GURL(), GURL("https://url.com/image.png"), | 358 CreateContextMenu(CreateContextMenuParams( |
| 333 blink::WebContextMenuData::MediaTypeImage)); | 359 GURL(), GURL("https://url.com/image.png"), |
| 360 blink::WebContextMenuData::MediaTypeImage))); | |
| 334 | 361 |
| 335 ASSERT_FALSE( | 362 ASSERT_FALSE( |
| 336 menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPEN_ORIGINAL_IMAGE_NEW_TAB)); | 363 menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPEN_ORIGINAL_IMAGE_NEW_TAB)); |
| 337 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB)); | 364 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB)); |
| 338 } | 365 } |
| 339 | 366 |
| 340 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, OpenImageInNewTab) { | 367 IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, OpenImageInNewTab) { |
| 341 scoped_ptr<TestRenderViewContextMenu> menu( | 368 scoped_ptr<TestRenderViewContextMenu> menu( |
| 342 CreateContextMenu(GURL(), GURL("http://url.com/image.png"), | 369 CreateContextMenu(CreateContextMenuParams( |
| 343 blink::WebContextMenuData::MediaTypeImage)); | 370 GURL(), GURL("http://url.com/image.png"), |
| 371 blink::WebContextMenuData::MediaTypeImage))); | |
| 344 ASSERT_FALSE( | 372 ASSERT_FALSE( |
| 345 menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPEN_ORIGINAL_IMAGE_NEW_TAB)); | 373 menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPEN_ORIGINAL_IMAGE_NEW_TAB)); |
| 346 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB)); | 374 ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB)); |
| 347 } | 375 } |
| 348 | 376 |
| 349 class ThumbnailResponseWatcher : public content::NotificationObserver { | 377 class ThumbnailResponseWatcher : public content::NotificationObserver { |
| 350 public: | 378 public: |
| 351 enum QuitReason { | 379 enum QuitReason { |
| 352 STILL_RUNNING = 0, | 380 STILL_RUNNING = 0, |
| 353 THUMBNAIL_RECEIVED, | 381 THUMBNAIL_RECEIVED, |
| (...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 518 content::WebContents* tab = | 546 content::WebContents* tab = |
| 519 browser()->tab_strip_model()->GetActiveWebContents(); | 547 browser()->tab_strip_model()->GetActiveWebContents(); |
| 520 ThumbnailResponseWatcher watcher(tab->GetRenderProcessHost()); | 548 ThumbnailResponseWatcher watcher(tab->GetRenderProcessHost()); |
| 521 AttemptImageSearch(); | 549 AttemptImageSearch(); |
| 522 | 550 |
| 523 // The browser should receive a response from the renderer, because the | 551 // The browser should receive a response from the renderer, because the |
| 524 // renderer should not crash. | 552 // renderer should not crash. |
| 525 EXPECT_EQ(ThumbnailResponseWatcher::THUMBNAIL_RECEIVED, watcher.Wait()); | 553 EXPECT_EQ(ThumbnailResponseWatcher::THUMBNAIL_RECEIVED, watcher.Wait()); |
| 526 } | 554 } |
| 527 | 555 |
| 556 class LoadImageRequestInterceptor : public net::URLRequestInterceptor { | |
| 557 public: | |
| 558 LoadImageRequestInterceptor() : num_requests_(0), | |
| 559 requests_to_wait_for_(-1), | |
| 560 weak_factory_(this) { | |
| 561 } | |
| 562 | |
| 563 ~LoadImageRequestInterceptor() override {} | |
| 564 | |
| 565 // net::URLRequestInterceptor implementation | |
| 566 net::URLRequestJob* MaybeInterceptRequest( | |
| 567 net::URLRequest* request, | |
| 568 net::NetworkDelegate* network_delegate) const override { | |
| 569 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
|
Lei Zhang
2015/05/11 20:16:53
Use DCHECK_CURRENTLY_ON()
megjablon
2015/05/11 22:58:40
Done.
| |
| 570 EXPECT_TRUE(request->load_flags() & net::LOAD_BYPASS_CACHE); | |
| 571 content::BrowserThread::PostTask( | |
| 572 content::BrowserThread::UI, FROM_HERE, | |
| 573 base::Bind(&LoadImageRequestInterceptor::RequestCreated, | |
| 574 weak_factory_.GetWeakPtr())); | |
| 575 return nullptr; | |
| 576 } | |
| 577 | |
| 578 void WaitForRequests(int requests_to_wait_for) { | |
| 579 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 580 DCHECK_EQ(-1, requests_to_wait_for_); | |
| 581 DCHECK(!run_loop_); | |
| 582 | |
| 583 if (num_requests_ >= requests_to_wait_for) | |
| 584 return; | |
| 585 | |
| 586 requests_to_wait_for_ = requests_to_wait_for; | |
| 587 run_loop_.reset(new base::RunLoop()); | |
| 588 run_loop_->Run(); | |
| 589 run_loop_.reset(); | |
| 590 requests_to_wait_for_ = -1; | |
| 591 EXPECT_EQ(num_requests_, requests_to_wait_for); | |
| 592 } | |
| 593 | |
| 594 // It is up to the caller to wait until all relevant requests has been | |
| 595 // created, either through calling WaitForRequests or some other manner, | |
| 596 // before calling this method. | |
| 597 int num_requests() const { | |
| 598 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 599 return num_requests_; | |
| 600 } | |
| 601 | |
| 602 private: | |
| 603 void RequestCreated() { | |
| 604 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 605 | |
| 606 num_requests_++; | |
| 607 if (num_requests_ == requests_to_wait_for_) | |
| 608 run_loop_->Quit(); | |
| 609 } | |
| 610 | |
| 611 // These are only used on the UI thread. | |
| 612 int num_requests_; | |
| 613 int requests_to_wait_for_; | |
| 614 scoped_ptr<base::RunLoop> run_loop_; | |
| 615 | |
| 616 // This prevents any risk of flake if any test doesn't wait for a request | |
| 617 // it sent. Mutable so it can be accessed from a const function. | |
| 618 mutable base::WeakPtrFactory<LoadImageRequestInterceptor> weak_factory_; | |
| 619 | |
| 620 DISALLOW_COPY_AND_ASSIGN(LoadImageRequestInterceptor); | |
| 621 }; | |
| 622 | |
| 623 class LoadImageBrowserTest : public InProcessBrowserTest { | |
| 624 protected: | |
| 625 void SetupAndLoadImagePage(const std::string& image_path) { | |
| 626 // Go to a page with an image in it. The test server doesn't serve the image | |
| 627 // with the right MIME type, so use a data URL to make a page containing it. | |
| 628 GURL image_url(test_server()->GetURL(image_path)); | |
| 629 GURL page("data:text/html,<img src='" + image_url.spec() + "'>"); | |
| 630 ui_test_utils::NavigateToURL(browser(), page); | |
| 631 } | |
| 632 | |
| 633 void AddLoadImageInterceptor(const std::string& image_path) { | |
| 634 interceptor_ = new LoadImageRequestInterceptor(); | |
| 635 scoped_ptr<net::URLRequestInterceptor> owned_interceptor(interceptor_); | |
| 636 // Ownership of the |interceptor_| is passed to an object the IO thread, but | |
| 637 // a pointer is kept in the test fixture. As soon as anything calls | |
| 638 // URLRequestFilter::ClearHandlers(), |interceptor_| can become invalid. | |
|
Lei Zhang
2015/05/11 20:16:53
I realized this is copied over from another test,
megjablon
2015/05/11 22:58:40
Looks like ClearHandlers is only called by tests a
| |
| 639 content::BrowserThread::PostTask( | |
| 640 content::BrowserThread::IO, FROM_HERE, | |
| 641 base::Bind(&LoadImageBrowserTest::AddInterceptorForURL, | |
| 642 base::Unretained(this), | |
| 643 GURL(test_server()->GetURL(image_path).spec()), | |
| 644 base::Passed(&owned_interceptor))); | |
| 645 } | |
| 646 | |
| 647 void AttemptImageLoad() { | |
| 648 // Right-click where the image should be. | |
| 649 // |menu_observer_| will cause the load-image menu item to be clicked. | |
| 650 menu_observer_.reset(new ContextMenuNotificationObserver( | |
| 651 IDC_CONTENT_CONTEXT_RELOAD_ORIGINAL_IMAGE)); | |
| 652 content::WebContents* tab = | |
| 653 browser()->tab_strip_model()->GetActiveWebContents(); | |
| 654 content::SimulateMouseClickAt(tab, 0, blink::WebMouseEvent::ButtonRight, | |
| 655 gfx::Point(15, 15)); | |
| 656 } | |
| 657 | |
| 658 void AddInterceptorForURL( | |
| 659 const GURL& url, scoped_ptr<net::URLRequestInterceptor> handler) { | |
| 660 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
| 661 net::URLRequestFilter::GetInstance()->AddUrlInterceptor( | |
| 662 url, handler.Pass()); | |
| 663 } | |
| 664 | |
| 665 LoadImageRequestInterceptor* interceptor_; | |
| 666 | |
| 667 private: | |
| 668 scoped_ptr<ContextMenuNotificationObserver> menu_observer_; | |
| 669 }; | |
| 670 | |
| 671 IN_PROC_BROWSER_TEST_F(LoadImageBrowserTest, LoadImage) { | |
| 672 static const char kValidImage[] = "files/load_image/image.png"; | |
| 673 SetupAndLoadImagePage(kValidImage); | |
| 674 AddLoadImageInterceptor(kValidImage); | |
| 675 AttemptImageLoad(); | |
| 676 interceptor_->WaitForRequests(1); | |
| 677 EXPECT_EQ(1, interceptor_->num_requests()); | |
| 678 } | |
| 679 | |
| 528 } // namespace | 680 } // namespace |
| OLD | NEW |