15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <string>
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/compiler_specific.h"
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/memory/scoped_ptr.h"
99ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch#include "base/message_loop/message_loop.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/values.h"
117dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "chrome/browser/chrome_notification_types.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/extensions/active_tab_permission_granter.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/extensions/tab_helper.h"
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/profiles/profile.h"
1503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "chrome/browser/sessions/session_tab_helper.h"
16d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)#include "chrome/common/extensions/features/feature_channel.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/test/base/chrome_render_view_host_test_harness.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/browser_thread.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/navigation_details.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/navigation_entry.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/notification_service.h"
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/notification_types.h"
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/web_contents.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/common/frame_navigate_params.h"
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/test/test_browser_thread.h"
265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "extensions/browser/extension_registry.h"
27f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "extensions/common/extension.h"
28f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "extensions/common/extension_builder.h"
293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "extensions/common/features/feature.h"
30f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "extensions/common/permissions/permissions_data.h"
31f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "extensions/common/value_builder.h"
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using base::DictionaryValue;
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using base::ListValue;
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::BrowserThread;
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::NavigationController;
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace extensions {
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)scoped_refptr<const Extension> CreateTestExtension(
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const std::string& id,
43d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    bool has_active_tab_permission,
44d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    bool has_tab_capture_permission) {
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ListBuilder permissions;
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (has_active_tab_permission)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    permissions.Append("activeTab");
48d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  if (has_tab_capture_permission)
49d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    permissions.Append("tabCapture");
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return ExtensionBuilder()
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      .SetManifest(DictionaryBuilder()
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          .Set("name", "Extension with ID " + id)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          .Set("version", "1.0")
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          .Set("manifest_version", 2)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          .Set("permissions", permissions))
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      .SetID(id)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      .Build();
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)enum PermittedFeature {
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  PERMITTED_NONE,
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  PERMITTED_SCRIPT_ONLY,
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  PERMITTED_CAPTURE_ONLY,
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  PERMITTED_BOTH
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)};
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ActiveTabTest : public ChromeRenderViewHostTestHarness {
68868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) protected:
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ActiveTabTest()
70d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      : current_channel(chrome::VersionInfo::CHANNEL_DEV),
71d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        extension(CreateTestExtension("deadbeef", true, false)),
72d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        another_extension(CreateTestExtension("feedbeef", true, false)),
73d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        extension_without_active_tab(CreateTestExtension("badbeef",
74d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)                                                         false,
75d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)                                                         false)),
76d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        extension_with_tab_capture(CreateTestExtension("cafebeef",
77d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)                                                       true,
78d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)                                                       true)) {}
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void SetUp() OVERRIDE {
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ChromeRenderViewHostTestHarness::SetUp();
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    TabHelper::CreateForWebContents(web_contents());
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int tab_id() {
8603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return SessionTabHelper::IdForTab(web_contents());
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ActiveTabPermissionGranter* active_tab_permission_granter() {
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return extensions::TabHelper::FromWebContents(web_contents())->
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        active_tab_permission_granter();
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool IsAllowed(const scoped_refptr<const Extension>& extension,
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 const GURL& url) {
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return IsAllowed(extension, url, PERMITTED_BOTH, tab_id());
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool IsAllowed(const scoped_refptr<const Extension>& extension,
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 const GURL& url,
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 PermittedFeature feature) {
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return IsAllowed(extension, url, feature, tab_id());
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  bool IsAllowed(const scoped_refptr<const Extension>& extension,
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 const GURL& url,
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 PermittedFeature feature,
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 int tab_id) {
10946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    const PermissionsData* permissions_data = extension->permissions_data();
1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    bool script = permissions_data->CanAccessPage(
1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        extension.get(), url, url, tab_id, -1, NULL);
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    bool capture = HasTabsPermission(extension, tab_id) &&
11346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                   permissions_data->CanCaptureVisiblePage(tab_id, NULL);
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    switch (feature) {
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      case PERMITTED_SCRIPT_ONLY:
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return script && !capture;
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      case PERMITTED_CAPTURE_ONLY:
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return capture && !script;
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      case PERMITTED_BOTH:
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return script && capture;
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      case PERMITTED_NONE:
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return !script && !capture;
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    NOTREACHED();
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return false;
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool IsBlocked(const scoped_refptr<const Extension>& extension,
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 const GURL& url) {
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return IsBlocked(extension, url, tab_id());
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool IsBlocked(const scoped_refptr<const Extension>& extension,
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 const GURL& url,
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 int tab_id) {
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return IsAllowed(extension, url, PERMITTED_NONE, tab_id);
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool HasTabsPermission(const scoped_refptr<const Extension>& extension) {
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return HasTabsPermission(extension, tab_id());
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool HasTabsPermission(const scoped_refptr<const Extension>& extension,
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         int tab_id) {
14546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return extension->permissions_data()->HasAPIPermissionForTab(
14646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        tab_id, APIPermission::kTab);
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
149d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  bool IsGrantedForTab(const Extension* extension,
150d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)                       const content::WebContents* web_contents) {
15146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return extension->permissions_data()->HasAPIPermissionForTab(
15203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        SessionTabHelper::IdForTab(web_contents), APIPermission::kTab);
153d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  }
154d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
155d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  // TODO(justinlin): Remove when tabCapture is moved to stable.
156d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  ScopedCurrentChannel current_channel;
157d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // An extension with the activeTab permission.
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  scoped_refptr<const Extension> extension;
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Another extension with activeTab (for good measure).
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  scoped_refptr<const Extension> another_extension;
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // An extension without the activeTab permission.
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  scoped_refptr<const Extension> extension_without_active_tab;
166d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
167d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  // An extension with both the activeTab and tabCapture permission.
168d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  scoped_refptr<const Extension> extension_with_tab_capture;
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(ActiveTabTest, GrantToSinglePage) {
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL google("http://www.google.com");
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(google);
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // No access unless it's been granted.
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension, google));
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(another_extension, google));
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(extension));
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(another_extension));
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(extension_without_active_tab));
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
184868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(
186868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      extension_without_active_tab.get());
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Granted to extension and extension_without_active_tab, but the latter
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // doesn't have the activeTab permission so not granted.
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google));
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(another_extension, google));
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Other subdomains shouldn't be given access.
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL mail_google("http://mail.google.com");
1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, mail_google, PERMITTED_CAPTURE_ONLY));
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(another_extension, mail_google));
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, mail_google));
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Reloading the page should clear the active permissions.
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Reload();
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension, google));
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(another_extension, google));
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(extension));
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(another_extension));
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(extension_without_active_tab));
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // But they should still be able to be granted again.
212868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google));
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(another_extension, google));
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // And grant a few more times redundantly for good measure.
219868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
220868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
221868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(another_extension.get());
222868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(another_extension.get());
223868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(another_extension.get());
224868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
225868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
226868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(another_extension.get());
227868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(another_extension.get());
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google));
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(another_extension, google));
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Navigating to a new URL should clear the active permissions.
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL chromium("http://www.chromium.org");
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(chromium);
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension, google));
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(another_extension, google));
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension, chromium));
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(another_extension, chromium));
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium));
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(extension));
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(another_extension));
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(extension_without_active_tab));
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Should be able to grant to multiple extensions at the same time (if they
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // have the activeTab permission, of course).
251868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
252868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(another_extension.get());
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(
254868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      extension_without_active_tab.get());
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google, PERMITTED_CAPTURE_ONLY));
2575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(another_extension, google, PERMITTED_CAPTURE_ONLY));
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, chromium));
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(another_extension, chromium));
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium));
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Should be able to go back to URLs that were previously cleared.
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(google);
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
267868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
268868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(another_extension.get());
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(
270868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      extension_without_active_tab.get());
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google));
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(another_extension, google));
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, chromium, PERMITTED_CAPTURE_ONLY));
2775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(another_extension, chromium, PERMITTED_CAPTURE_ONLY));
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium));
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(ActiveTabTest, Uninstalling) {
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Some semi-arbitrary setup.
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL google("http://www.google.com");
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(google);
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
286868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
288d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  EXPECT_TRUE(IsGrantedForTab(extension.get(), web_contents()));
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google));
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Uninstalling the extension should clear its tab permissions.
2925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  ExtensionRegistry* registry =
2935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ExtensionRegistry::Get(web_contents()->GetBrowserContext());
2945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  registry->TriggerOnUnloaded(extension.get(),
2955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                              UnloadedExtensionInfo::REASON_DISABLE);
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Note: can't EXPECT_FALSE(IsAllowed) here because uninstalled extensions
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // are just that... considered to be uninstalled, and the manager might
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // just ignore them from here on.
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Granting the extension again should give them back.
302868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
304d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  EXPECT_TRUE(IsGrantedForTab(extension.get(), web_contents()));
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google));
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(ActiveTabTest, OnlyActiveTab) {
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL google("http://www.google.com");
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(google);
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
312868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google, PERMITTED_BOTH, tab_id()));
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension, google, tab_id() + 1));
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_FALSE(HasTabsPermission(extension, tab_id() + 1));
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(ActiveTabTest, NavigateInPage) {
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL google("http://www.google.com");
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(google);
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
323868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Perform an in-page navigation. The extension should not lose the temporary
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // permission.
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL google_h1("http://www.google.com#h1");
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(google_h1);
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google));
3315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, google_h1));
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL chromium("http://www.chromium.org");
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(chromium);
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google));
3375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google_h1));
3385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, chromium));
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
340868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(extension.get());
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google));
3435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google_h1));
3445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, chromium));
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL chromium_h1("http://www.chromium.org#h1");
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateAndCommit(chromium_h1);
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google));
3505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google_h1));
3515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, chromium));
3525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension, chromium_h1));
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Reload();
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google));
3575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, google_h1));
3585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, chromium));
3595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsAllowed(extension, chromium_h1));
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
362d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)TEST_F(ActiveTabTest, ChromeUrlGrants) {
363d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  GURL internal("chrome://version");
364d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  NavigateAndCommit(internal);
365d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  active_tab_permission_granter()->GrantIfRequested(
366d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      extension_with_tab_capture.get());
367d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  // Do not grant tabs/hosts permissions for tab.
3685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsAllowed(extension_with_tab_capture, internal,
3695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        PERMITTED_CAPTURE_ONLY));
37046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const PermissionsData* permissions_data =
37146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      extension_with_tab_capture->permissions_data();
37246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  EXPECT_TRUE(permissions_data->HasAPIPermissionForTab(
37346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      tab_id(), APIPermission::kTabCaptureForTab));
374d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
375d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  EXPECT_TRUE(IsBlocked(extension_with_tab_capture, internal, tab_id() + 1));
37646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  EXPECT_FALSE(permissions_data->HasAPIPermissionForTab(
37746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      tab_id() + 1, APIPermission::kTabCaptureForTab));
378d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)}
379d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace extensions
382