diff options
author | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-01 17:29:13 +0000 |
---|---|---|
committer | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-01 17:29:13 +0000 |
commit | e6893672ec403d9dbf7fc5aab64ba6109ffd625a (patch) | |
tree | 25d46598e07af97d4f284663a3e3d53412e4eb2b /extensions | |
parent | 3dfb202d7b2cbdb92b1fe9d6274211d823efebe8 (diff) | |
download | chromium_src-e6893672ec403d9dbf7fc5aab64ba6109ffd625a.zip chromium_src-e6893672ec403d9dbf7fc5aab64ba6109ffd625a.tar.gz chromium_src-e6893672ec403d9dbf7fc5aab64ba6109ffd625a.tar.bz2 |
Pull chrome stuff out of (and move) extensions::Dispatcher.
This moves Dispatcher into //extensions along with
several of its remaining dependencies.
This also introduces a DispatcherDelegate interface along with implementations for chrome and app_shell.
BUG=359836
TBR=sky for CCRC and ChromeRenderViewTest
Review URL: https://codereview.chromium.org/260083006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@267564 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
46 files changed, 4340 insertions, 31 deletions
diff --git a/extensions/DEPS b/extensions/DEPS index 5d3a4d5..4dabc14 100644 --- a/extensions/DEPS +++ b/extensions/DEPS @@ -2,6 +2,7 @@ include_rules = [ "+components/url_matcher", "+content/public/common", "+crypto", + "+grit/extensions_resources.h", "+testing", "+ui", @@ -12,10 +13,12 @@ include_rules = [ # # TODO(jamescook): Remove these. http://crbug.com/162530 "!chrome/browser/chrome_notification_types.h", - "!chrome/renderer/extensions/dispatcher.h", "!chrome/renderer/extensions/extension_helper.h", "!grit/common_resources.h", - "!grit/extensions_api_resources.h", + '!grit/extensions_api_resources.h', + # This is needed for renderer JS sources which should eventually move to + # the extensions_resources target. + "!grit/renderer_resources.h", ] specific_include_rules = { @@ -28,12 +31,12 @@ specific_include_rules = { "+chrome/browser/extensions/extension_service_unittest.h", "+chrome/browser/extensions/test_extension_system.h", "+chrome/common/chrome_paths.h", + "+chrome/common/extensions/features/feature_channel.h", "+chrome/common/extensions/manifest_tests/extension_manifest_test.h", "+chrome/test/base/testing_profile.h", ], "(simple|complex)_feature_unittest\.cc|base_feature_provider_unittest\.cc": [ "+chrome/common/extensions/features/chrome_channel_feature_filter.h", - "+chrome/common/extensions/features/feature_channel.h", ], "permissions_data_unittest\.cc": [ "+chrome/common/chrome_version_info.h", diff --git a/extensions/common/api/extensions_manifest_types.json b/extensions/common/api/extensions_manifest_types.json index 24c2df5..00262692 100644 --- a/extensions/common/api/extensions_manifest_types.json +++ b/extensions/common/api/extensions_manifest_types.json @@ -13,6 +13,32 @@ }, "types": [ { + "id": "ExternallyConnectable", + "type": "object", + // Note: description commented out because externally_connectable.html + // already describes it, and the repetition looks odd. + // "description": "The <code>externally_connectable</code> manifest property declares which extensions, apps, and web pages can connect to your extension via $(ref:runtime.connect) and $(ref:runtime.sendMessage).", + "properties": { + "ids": { + "description": "<p>The IDs of extensions or apps that are allowed to connect. If left empty or unspecified, no extensions or apps can connect.</p><p>The wildcard <code>\"*\"</code> will allow all extensions and apps to connect.</p>", + "optional": true, + "type": "array", + "items": {"type": "string"} + }, + "matches": { + "description": "<p>The URL patterns for <em>web pages</em> that are allowed to connect. <em>This does not affect content scripts.</em> If left empty or unspecified, no web pages can connect.</p><p>Patterns cannot include wildcard domains nor subdomains of <a href=\"http://publicsuffix.org/list/\">(effective) top level domains</a>; <code>*://google.com/*</code> and <code>http://*.chromium.org/*</code> are valid, while <code><all_urls></code>, <code>http://*/*</code>, <code>*://*.com/*</code>, and even <code>http://*.appspot.com/*</code> are not.</p>", + "optional": true, + "type": "array", + "items": {"type": "string"} + }, + "accepts_tls_channel_id": { + "description": "If <code>true</code>, messages sent via $(ref:runtime.connect) or $(ref:runtime.sendMessage) will set $(ref:runtime.MessageSender.tlsChannelId) if those methods request it to be. If <code>false</code>, $(ref:runtime.MessageSender.tlsChannelId) will never be set under any circumstance.", + "optional": true, + "type": "boolean" + } + } + }, + { "id": "SocketHostPatterns", "description": "<p>A single string or a list of strings representing host:port patterns.</p>", "choices": [ diff --git a/extensions/common/manifest_handlers/externally_connectable.cc b/extensions/common/manifest_handlers/externally_connectable.cc new file mode 100644 index 0000000..1261ad8 --- /dev/null +++ b/extensions/common/manifest_handlers/externally_connectable.cc @@ -0,0 +1,217 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/manifest_handlers/externally_connectable.h" + +#include <algorithm> + +#include "base/stl_util.h" +#include "base/strings/utf_string_conversions.h" +#include "extensions/common/api/extensions_manifest_types.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/permissions/api_permission_set.h" +#include "extensions/common/permissions/permissions_data.h" +#include "extensions/common/url_pattern.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" +#include "url/gurl.h" + +namespace rcd = net::registry_controlled_domains; + +namespace extensions { + +namespace externally_connectable_errors { +const char kErrorInvalidMatchPattern[] = "Invalid match pattern '*'"; +const char kErrorInvalidId[] = "Invalid ID '*'"; +const char kErrorNothingSpecified[] = + "'externally_connectable' specifies neither 'matches' nor 'ids'; " + "nothing will be able to connect"; +const char kErrorTopLevelDomainsNotAllowed[] = + "\"*\" is an effective top level domain for which wildcard subdomains such " + "as \"*\" are not allowed"; +const char kErrorWildcardHostsNotAllowed[] = + "Wildcard domain patterns such as \"*\" are not allowed"; +} // namespace externally_connectable_errors + +namespace keys = extensions::manifest_keys; +namespace errors = externally_connectable_errors; +using core_api::extensions_manifest_types::ExternallyConnectable; + +namespace { + +const char kAllIds[] = "*"; + +template <typename T> +std::vector<T> Sorted(const std::vector<T>& in) { + std::vector<T> out = in; + std::sort(out.begin(), out.end()); + return out; +} + +} // namespace + +ExternallyConnectableHandler::ExternallyConnectableHandler() { +} + +ExternallyConnectableHandler::~ExternallyConnectableHandler() { +} + +bool ExternallyConnectableHandler::Parse(Extension* extension, + base::string16* error) { + const base::Value* externally_connectable = NULL; + CHECK(extension->manifest()->Get(keys::kExternallyConnectable, + &externally_connectable)); + std::vector<InstallWarning> install_warnings; + scoped_ptr<ExternallyConnectableInfo> info = + ExternallyConnectableInfo::FromValue( + *externally_connectable, &install_warnings, error); + if (!info) + return false; + if (!info->matches.is_empty()) { + PermissionsData::GetInitialAPIPermissions(extension) + ->insert(APIPermission::kWebConnectable); + } + extension->AddInstallWarnings(install_warnings); + extension->SetManifestData(keys::kExternallyConnectable, info.release()); + return true; +} + +const std::vector<std::string> ExternallyConnectableHandler::Keys() const { + return SingleKey(keys::kExternallyConnectable); +} + +// static +ExternallyConnectableInfo* ExternallyConnectableInfo::Get( + const Extension* extension) { + return static_cast<ExternallyConnectableInfo*>( + extension->GetManifestData(keys::kExternallyConnectable)); +} + +// static +scoped_ptr<ExternallyConnectableInfo> ExternallyConnectableInfo::FromValue( + const base::Value& value, + std::vector<InstallWarning>* install_warnings, + base::string16* error) { + scoped_ptr<ExternallyConnectable> externally_connectable = + ExternallyConnectable::FromValue(value, error); + if (!externally_connectable) + return scoped_ptr<ExternallyConnectableInfo>(); + + URLPatternSet matches; + + if (externally_connectable->matches) { + for (std::vector<std::string>::iterator it = + externally_connectable->matches->begin(); + it != externally_connectable->matches->end(); + ++it) { + // Safe to use SCHEME_ALL here; externally_connectable gives a page -> + // extension communication path, not the other way. + URLPattern pattern(URLPattern::SCHEME_ALL); + if (pattern.Parse(*it) != URLPattern::PARSE_SUCCESS) { + *error = ErrorUtils::FormatErrorMessageUTF16( + errors::kErrorInvalidMatchPattern, *it); + return scoped_ptr<ExternallyConnectableInfo>(); + } + + // Wildcard hosts are not allowed. + if (pattern.host().empty()) { + // Warning not error for forwards compatibility. + install_warnings->push_back( + InstallWarning(ErrorUtils::FormatErrorMessage( + errors::kErrorWildcardHostsNotAllowed, *it), + keys::kExternallyConnectable, + *it)); + continue; + } + + // Wildcards on subdomains of a TLD are not allowed. + size_t registry_length = rcd::GetRegistryLength( + pattern.host(), + // This means that things that look like TLDs - the foobar in + // http://google.foobar - count as TLDs. + rcd::INCLUDE_UNKNOWN_REGISTRIES, + // This means that effective TLDs like appspot.com count as TLDs; + // codereview.appspot.com and evil.appspot.com are different. + rcd::INCLUDE_PRIVATE_REGISTRIES); + + if (registry_length == std::string::npos) { + // The URL parsing combined with host().empty() should have caught this. + NOTREACHED() << *it; + *error = ErrorUtils::FormatErrorMessageUTF16( + errors::kErrorInvalidMatchPattern, *it); + return scoped_ptr<ExternallyConnectableInfo>(); + } + + // Broad match patterns like "*.com", "*.co.uk", and even "*.appspot.com" + // are not allowed. However just "appspot.com" is ok. + if (registry_length == 0 && pattern.match_subdomains()) { + // Warning not error for forwards compatibility. + install_warnings->push_back( + InstallWarning(ErrorUtils::FormatErrorMessage( + errors::kErrorTopLevelDomainsNotAllowed, + pattern.host().c_str(), + *it), + keys::kExternallyConnectable, + *it)); + continue; + } + + matches.AddPattern(pattern); + } + } + + std::vector<std::string> ids; + bool all_ids = false; + + if (externally_connectable->ids) { + for (std::vector<std::string>::iterator it = + externally_connectable->ids->begin(); + it != externally_connectable->ids->end(); + ++it) { + if (*it == kAllIds) { + all_ids = true; + } else if (Extension::IdIsValid(*it)) { + ids.push_back(*it); + } else { + *error = + ErrorUtils::FormatErrorMessageUTF16(errors::kErrorInvalidId, *it); + return scoped_ptr<ExternallyConnectableInfo>(); + } + } + } + + if (!externally_connectable->matches && !externally_connectable->ids) { + install_warnings->push_back(InstallWarning(errors::kErrorNothingSpecified, + keys::kExternallyConnectable)); + } + + bool accepts_tls_channel_id = + externally_connectable->accepts_tls_channel_id.get() && + *externally_connectable->accepts_tls_channel_id; + return make_scoped_ptr(new ExternallyConnectableInfo( + matches, ids, all_ids, accepts_tls_channel_id)); +} + +ExternallyConnectableInfo::~ExternallyConnectableInfo() { +} + +ExternallyConnectableInfo::ExternallyConnectableInfo( + const URLPatternSet& matches, + const std::vector<std::string>& ids, + bool all_ids, + bool accepts_tls_channel_id) + : matches(matches), + ids(Sorted(ids)), + all_ids(all_ids), + accepts_tls_channel_id(accepts_tls_channel_id) { +} + +bool ExternallyConnectableInfo::IdCanConnect(const std::string& id) { + if (all_ids) + return true; + DCHECK(base::STLIsSorted(ids)); + return std::binary_search(ids.begin(), ids.end(), id); +} + +} // namespace extensions diff --git a/extensions/common/manifest_handlers/externally_connectable.h b/extensions/common/manifest_handlers/externally_connectable.h new file mode 100644 index 0000000..960904e --- /dev/null +++ b/extensions/common/manifest_handlers/externally_connectable.h @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_EXTERNALLY_CONNECTABLE_H_ +#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_EXTERNALLY_CONNECTABLE_H_ + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "extensions/common/extension.h" +#include "extensions/common/install_warning.h" +#include "extensions/common/manifest_handler.h" +#include "extensions/common/url_pattern_set.h" + +class GURL; + +namespace base { +class Value; +} + +namespace extensions { + +// Error constants used when parsing the externally_connectable manifest entry. +namespace externally_connectable_errors { +extern const char kErrorInvalid[]; +extern const char kErrorInvalidMatchPattern[]; +extern const char kErrorInvalidId[]; +extern const char kErrorNothingSpecified[]; +extern const char kErrorTopLevelDomainsNotAllowed[]; +extern const char kErrorWildcardHostsNotAllowed[]; +} // namespace externally_connectable_errors + +// Parses the externally_connectable manifest entry. +class ExternallyConnectableHandler : public ManifestHandler { + public: + ExternallyConnectableHandler(); + virtual ~ExternallyConnectableHandler(); + + virtual bool Parse(Extension* extension, base::string16* error) OVERRIDE; + + private: + virtual const std::vector<std::string> Keys() const OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(ExternallyConnectableHandler); +}; + +// The parsed form of the externally_connectable manifest entry. +struct ExternallyConnectableInfo : public Extension::ManifestData { + public: + // Gets the ExternallyConnectableInfo for |extension|, or NULL if none was + // specified. + static ExternallyConnectableInfo* Get(const Extension* extension); + + // Tries to construct the info based on |value|, as it would have appeared in + // the manifest. Sets |error| and returns an empty scoped_ptr on failure. + static scoped_ptr<ExternallyConnectableInfo> FromValue( + const base::Value& value, + std::vector<InstallWarning>* install_warnings, + base::string16* error); + + virtual ~ExternallyConnectableInfo(); + + // The URL patterns that are allowed to connect/sendMessage. + const URLPatternSet matches; + + // The extension IDs that are allowed to connect/sendMessage. Sorted. + const std::vector<std::string> ids; + + // True if any extension is allowed to connect. This would have corresponded + // to an ID of "*" in |ids|. + const bool all_ids; + + // True if extension accepts the TLS channel ID, when requested by the + // connecting origin. + const bool accepts_tls_channel_id; + + // Returns true if |ids| contains |id| or if |all_ids| is true. + // + // More convenient for callers than checking each individually, and it makes + // use of the sortedness of |ids|. + bool IdCanConnect(const std::string& id); + + // Public only for testing. Use FromValue in production. + ExternallyConnectableInfo(const URLPatternSet& matches, + const std::vector<std::string>& ids, + bool all_ids, + bool accepts_tls_channel_id); + + private: + DISALLOW_COPY_AND_ASSIGN(ExternallyConnectableInfo); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_EXTERNALLY_CONNECTABLE_H_ diff --git a/extensions/common/manifest_handlers/externally_connectable_unittest.cc b/extensions/common/manifest_handlers/externally_connectable_unittest.cc new file mode 100644 index 0000000..41b2d32 --- /dev/null +++ b/extensions/common/manifest_handlers/externally_connectable_unittest.cc @@ -0,0 +1,315 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> + +#include "chrome/common/extensions/features/feature_channel.h" +#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/manifest_handlers/externally_connectable.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::ElementsAre; + +namespace extensions { + +namespace errors = externally_connectable_errors; + +class ExternallyConnectableTest : public ExtensionManifestTest { + public: + ExternallyConnectableTest() : channel_(chrome::VersionInfo::CHANNEL_DEV) {} + + protected: + ExternallyConnectableInfo* GetExternallyConnectableInfo( + scoped_refptr<Extension> extension) { + return static_cast<ExternallyConnectableInfo*>( + extension->GetManifestData(manifest_keys::kExternallyConnectable)); + } + + private: + ScopedCurrentChannel channel_; +}; + +TEST_F(ExternallyConnectableTest, IDsAndMatches) { + scoped_refptr<Extension> extension = + LoadAndExpectSuccess("externally_connectable_ids_and_matches.json"); + ASSERT_TRUE(extension.get()); + + EXPECT_TRUE(extension->HasAPIPermission(APIPermission::kWebConnectable)); + + ExternallyConnectableInfo* info = + ExternallyConnectableInfo::Get(extension.get()); + ASSERT_TRUE(info); + + EXPECT_THAT(info->ids, + ElementsAre("abcdefghijklmnopabcdefghijklmnop", + "ponmlkjihgfedcbaponmlkjihgfedcba")); + + EXPECT_FALSE(info->all_ids); + + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com/"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://example.com/index.html"))); + + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/index.html"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com/"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com/"))); + + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org/"))); + EXPECT_TRUE( + info->matches.MatchesURL(GURL("http://build.chromium.org/index.html"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org/"))); + EXPECT_FALSE( + info->matches.MatchesURL(GURL("http://foo.chromium.org/index.html"))); + + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com/"))); + + // TLD-style patterns should match just the TLD. + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://appspot.com/foo.html"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://go/here"))); + + // TLD-style patterns should *not* match any subdomains of the TLD. + EXPECT_FALSE( + info->matches.MatchesURL(GURL("http://codereview.appspot.com/foo.html"))); + EXPECT_FALSE( + info->matches.MatchesURL(GURL("http://chromium.com/index.html"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://here.go/somewhere"))); + + // Paths that don't have any wildcards should match the exact domain, but + // ignore the trailing slash. This is kind of a corner case, so let's test it. + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://no.wildcard.path"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://no.wildcard.path/"))); + EXPECT_FALSE( + info->matches.MatchesURL(GURL("http://no.wildcard.path/index.html"))); +} + +TEST_F(ExternallyConnectableTest, IDs) { + scoped_refptr<Extension> extension = + LoadAndExpectSuccess("externally_connectable_ids.json"); + ASSERT_TRUE(extension.get()); + + EXPECT_FALSE(extension->HasAPIPermission(APIPermission::kWebConnectable)); + + ExternallyConnectableInfo* info = + ExternallyConnectableInfo::Get(extension.get()); + ASSERT_TRUE(info); + + EXPECT_THAT(info->ids, + ElementsAre("abcdefghijklmnopabcdefghijklmnop", + "ponmlkjihgfedcbaponmlkjihgfedcba")); + + EXPECT_FALSE(info->all_ids); + + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://google.com/index.html"))); +} + +TEST_F(ExternallyConnectableTest, Matches) { + scoped_refptr<Extension> extension = + LoadAndExpectSuccess("externally_connectable_matches.json"); + ASSERT_TRUE(extension.get()); + + EXPECT_TRUE(extension->HasAPIPermission(APIPermission::kWebConnectable)); + + ExternallyConnectableInfo* info = + ExternallyConnectableInfo::Get(extension.get()); + ASSERT_TRUE(info); + + EXPECT_THAT(info->ids, ElementsAre()); + + EXPECT_FALSE(info->all_ids); + + EXPECT_FALSE(info->accepts_tls_channel_id); + + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com/"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://example.com/index.html"))); + + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/index.html"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com/"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com/"))); + + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org/"))); + EXPECT_TRUE( + info->matches.MatchesURL(GURL("http://build.chromium.org/index.html"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org/"))); + EXPECT_FALSE( + info->matches.MatchesURL(GURL("http://foo.chromium.org/index.html"))); + + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com/"))); +} + +TEST_F(ExternallyConnectableTest, MatchesWithTlsChannelId) { + scoped_refptr<Extension> extension = LoadAndExpectSuccess( + "externally_connectable_matches_tls_channel_id.json"); + ASSERT_TRUE(extension.get()); + + EXPECT_TRUE(extension->HasAPIPermission(APIPermission::kWebConnectable)); + + ExternallyConnectableInfo* info = + ExternallyConnectableInfo::Get(extension.get()); + ASSERT_TRUE(info); + + EXPECT_THAT(info->ids, ElementsAre()); + + EXPECT_FALSE(info->all_ids); + + EXPECT_TRUE(info->accepts_tls_channel_id); + + // The matches portion of the manifest is identical to those in + // externally_connectable_matches, so only a subset of the Matches tests is + // repeated here. + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com"))); + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://example.com/index.html"))); +} + +TEST_F(ExternallyConnectableTest, AllIDs) { + scoped_refptr<Extension> extension = + LoadAndExpectSuccess("externally_connectable_all_ids.json"); + ASSERT_TRUE(extension.get()); + + EXPECT_FALSE(extension->HasAPIPermission(APIPermission::kWebConnectable)); + + ExternallyConnectableInfo* info = + ExternallyConnectableInfo::Get(extension.get()); + ASSERT_TRUE(info); + + EXPECT_THAT(info->ids, + ElementsAre("abcdefghijklmnopabcdefghijklmnop", + "ponmlkjihgfedcbaponmlkjihgfedcba")); + + EXPECT_TRUE(info->all_ids); + + EXPECT_FALSE(info->matches.MatchesURL(GURL("http://google.com/index.html"))); +} + +TEST_F(ExternallyConnectableTest, IdCanConnect) { + // Not in order to test that ExternallyConnectableInfo sorts it. + std::string matches_ids_array[] = {"g", "h", "c", "i", "a", "z", "b"}; + std::vector<std::string> matches_ids( + matches_ids_array, matches_ids_array + arraysize(matches_ids_array)); + + std::string nomatches_ids_array[] = {"2", "3", "1"}; + + // all_ids = false. + { + ExternallyConnectableInfo info(URLPatternSet(), matches_ids, false, false); + for (size_t i = 0; i < matches_ids.size(); ++i) + EXPECT_TRUE(info.IdCanConnect(matches_ids[i])); + for (size_t i = 0; i < arraysize(nomatches_ids_array); ++i) + EXPECT_FALSE(info.IdCanConnect(nomatches_ids_array[i])); + } + + // all_ids = true. + { + ExternallyConnectableInfo info(URLPatternSet(), matches_ids, true, false); + for (size_t i = 0; i < matches_ids.size(); ++i) + EXPECT_TRUE(info.IdCanConnect(matches_ids[i])); + for (size_t i = 0; i < arraysize(nomatches_ids_array); ++i) + EXPECT_TRUE(info.IdCanConnect(nomatches_ids_array[i])); + } +} + +TEST_F(ExternallyConnectableTest, ErrorWrongFormat) { + LoadAndExpectError("externally_connectable_error_wrong_format.json", + "expected dictionary, got string"); +} + +TEST_F(ExternallyConnectableTest, ErrorBadID) { + LoadAndExpectError( + "externally_connectable_bad_id.json", + ErrorUtils::FormatErrorMessage(errors::kErrorInvalidId, "badid")); +} + +TEST_F(ExternallyConnectableTest, ErrorBadMatches) { + LoadAndExpectError("externally_connectable_error_bad_matches.json", + ErrorUtils::FormatErrorMessage( + errors::kErrorInvalidMatchPattern, "www.yahoo.com")); +} + +TEST_F(ExternallyConnectableTest, WarningNoAllURLs) { + scoped_refptr<Extension> extension = LoadAndExpectWarning( + "externally_connectable_error_all_urls.json", + ErrorUtils::FormatErrorMessage(errors::kErrorWildcardHostsNotAllowed, + "<all_urls>")); + ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension); + EXPECT_FALSE(info->matches.ContainsPattern( + URLPattern(URLPattern::SCHEME_ALL, "<all_urls>"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org"))); +} + +TEST_F(ExternallyConnectableTest, WarningWildcardHost) { + scoped_refptr<Extension> extension = LoadAndExpectWarning( + "externally_connectable_error_wildcard_host.json", + ErrorUtils::FormatErrorMessage(errors::kErrorWildcardHostsNotAllowed, + "http://*/*")); + ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension); + EXPECT_FALSE(info->matches.ContainsPattern( + URLPattern(URLPattern::SCHEME_ALL, "http://*/*"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org"))); +} + +TEST_F(ExternallyConnectableTest, WarningNoTLD) { + scoped_refptr<Extension> extension = LoadAndExpectWarning( + "externally_connectable_error_tld.json", + ErrorUtils::FormatErrorMessage(errors::kErrorTopLevelDomainsNotAllowed, + "co.uk", + "http://*.co.uk/*")); + ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension); + EXPECT_FALSE(info->matches.ContainsPattern( + URLPattern(URLPattern::SCHEME_ALL, "http://*.co.uk/*"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org"))); +} + +TEST_F(ExternallyConnectableTest, WarningNoEffectiveTLD) { + scoped_refptr<Extension> extension = LoadAndExpectWarning( + "externally_connectable_error_effective_tld.json", + ErrorUtils::FormatErrorMessage(errors::kErrorTopLevelDomainsNotAllowed, + "appspot.com", + "http://*.appspot.com/*")); + ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension); + EXPECT_FALSE(info->matches.ContainsPattern( + URLPattern(URLPattern::SCHEME_ALL, "http://*.appspot.com/*"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org"))); +} + +TEST_F(ExternallyConnectableTest, WarningUnknownTLD) { + scoped_refptr<Extension> extension = LoadAndExpectWarning( + "externally_connectable_error_unknown_tld.json", + ErrorUtils::FormatErrorMessage(errors::kErrorTopLevelDomainsNotAllowed, + "notatld", + "http://*.notatld/*")); + ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension); + EXPECT_FALSE(info->matches.ContainsPattern( + URLPattern(URLPattern::SCHEME_ALL, "http://*.notatld/*"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com"))); + EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org"))); +} + +TEST_F(ExternallyConnectableTest, WarningNothingSpecified) { + LoadAndExpectWarning("externally_connectable_nothing_specified.json", + errors::kErrorNothingSpecified); +} + +} // namespace extensions diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index 1664e87..faa7790 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -124,6 +124,8 @@ 'common/manifest_handlers/background_info.h', 'common/manifest_handlers/csp_info.cc', 'common/manifest_handlers/csp_info.h', + 'common/manifest_handlers/externally_connectable.cc', + 'common/manifest_handlers/externally_connectable.h', 'common/manifest_handlers/icons_handler.cc', 'common/manifest_handlers/icons_handler.h', 'common/manifest_handlers/incognito_info.cc', @@ -417,8 +419,12 @@ 'sources': [ 'renderer/activity_log_converter_strategy.cc', 'renderer/activity_log_converter_strategy.h', + 'renderer/api_activity_logger.cc', + 'renderer/api_activity_logger.h', 'renderer/api_definitions_natives.cc', 'renderer/api_definitions_natives.h', + 'renderer/app_runtime_custom_bindings.cc', + 'renderer/app_runtime_custom_bindings.h', 'renderer/binding_generating_native_handler.cc', 'renderer/binding_generating_native_handler.h', 'renderer/blob_native_handler.cc', @@ -431,6 +437,11 @@ 'renderer/context_menus_custom_bindings.h', 'renderer/css_native_handler.cc', 'renderer/css_native_handler.h', + 'renderer/default_dispatcher_delegate.cc', + 'renderer/default_dispatcher_delegate.h', + 'renderer/dispatcher.cc', + 'renderer/dispatcher.h', + 'renderer/dispatcher_delegate.h', 'renderer/document_custom_bindings.cc', 'renderer/document_custom_bindings.h', 'renderer/dom_activity_logger.cc', @@ -446,18 +457,30 @@ 'renderer/i18n_custom_bindings.h', 'renderer/id_generator_custom_bindings.cc', 'renderer/id_generator_custom_bindings.h', + 'renderer/lazy_background_page_native_handler.cc', + 'renderer/lazy_background_page_native_handler.h', 'renderer/logging_native_handler.cc', 'renderer/logging_native_handler.h', + 'renderer/messaging_bindings.cc', + 'renderer/messaging_bindings.h', 'renderer/module_system.cc', 'renderer/module_system.h', 'renderer/native_handler.cc', 'renderer/native_handler.h', 'renderer/object_backed_native_handler.cc', 'renderer/object_backed_native_handler.h', + 'renderer/print_native_handler.cc', + 'renderer/print_native_handler.h', + 'renderer/process_info_native_handler.cc', + 'renderer/process_info_native_handler.h', 'renderer/render_view_observer_natives.cc', 'renderer/render_view_observer_natives.h', 'renderer/request_sender.cc', 'renderer/request_sender.h', + 'renderer/resource_bundle_source_map.cc', + 'renderer/resource_bundle_source_map.h', + 'renderer/runtime_custom_bindings.cc', + 'renderer/runtime_custom_bindings.h', 'renderer/safe_builtins.cc', 'renderer/safe_builtins.h', 'renderer/send_request_natives.cc', @@ -469,12 +492,24 @@ 'renderer/script_context.h', 'renderer/script_context_set.cc', 'renderer/script_context_set.h', + 'renderer/static_v8_external_ascii_string_resource.cc', + 'renderer/static_v8_external_ascii_string_resource.h', + 'renderer/test_features_native_handler.cc', + 'renderer/test_features_native_handler.h', + 'renderer/user_gestures_native_handler.cc', + 'renderer/user_gestures_native_handler.h', + 'renderer/user_script_slave.cc', + 'renderer/user_script_slave.h', 'renderer/utils_native_handler.cc', 'renderer/utils_native_handler.h', + 'renderer/v8_context_native_handler.cc', + 'renderer/v8_context_native_handler.h', 'renderer/v8_schema_registry.cc', 'renderer/v8_schema_registry.h', ], 'dependencies': [ + 'extensions_resources.gyp:extensions_resources', + '../chrome/chrome_resources.gyp:chrome_resources', '../third_party/WebKit/public/blink.gyp:blink', ], # Disable c4267 warnings until we fix size_t to int truncations. diff --git a/extensions/renderer/api_activity_logger.cc b/extensions/renderer/api_activity_logger.cc new file mode 100644 index 0000000..c1c5aef --- /dev/null +++ b/extensions/renderer/api_activity_logger.cc @@ -0,0 +1,80 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/bind.h" +#include "content/public/renderer/render_thread.h" +#include "content/public/renderer/v8_value_converter.h" +#include "extensions/common/extension_messages.h" +#include "extensions/renderer/activity_log_converter_strategy.h" +#include "extensions/renderer/api_activity_logger.h" +#include "extensions/renderer/script_context.h" + +using content::V8ValueConverter; + +namespace extensions { + +APIActivityLogger::APIActivityLogger(ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction("LogEvent", base::Bind(&APIActivityLogger::LogEvent)); + RouteFunction("LogAPICall", base::Bind(&APIActivityLogger::LogAPICall)); +} + +// static +void APIActivityLogger::LogAPICall( + const v8::FunctionCallbackInfo<v8::Value>& args) { + LogInternal(APICALL, args); +} + +// static +void APIActivityLogger::LogEvent( + const v8::FunctionCallbackInfo<v8::Value>& args) { + LogInternal(EVENT, args); +} + +// static +void APIActivityLogger::LogInternal( + const CallType call_type, + const v8::FunctionCallbackInfo<v8::Value>& args) { + DCHECK_GT(args.Length(), 2); + DCHECK(args[0]->IsString()); + DCHECK(args[1]->IsString()); + DCHECK(args[2]->IsArray()); + + std::string ext_id = *v8::String::Utf8Value(args[0]); + ExtensionHostMsg_APIActionOrEvent_Params params; + params.api_call = *v8::String::Utf8Value(args[1]); + if (args.Length() == 4) // Extras are optional. + params.extra = *v8::String::Utf8Value(args[3]); + else + params.extra = ""; + + // Get the array of api call arguments. + v8::Local<v8::Array> arg_array = v8::Local<v8::Array>::Cast(args[2]); + if (arg_array->Length() > 0) { + scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); + ActivityLogConverterStrategy strategy; + converter->SetFunctionAllowed(true); + converter->SetStrategy(&strategy); + scoped_ptr<base::ListValue> arg_list(new base::ListValue()); + for (size_t i = 0; i < arg_array->Length(); ++i) { + arg_list->Set( + i, + converter->FromV8Value(arg_array->Get(i), + args.GetIsolate()->GetCurrentContext())); + } + params.arguments.Swap(arg_list.get()); + } + + if (call_type == APICALL) { + content::RenderThread::Get()->Send( + new ExtensionHostMsg_AddAPIActionToActivityLog(ext_id, params)); + } else if (call_type == EVENT) { + content::RenderThread::Get()->Send( + new ExtensionHostMsg_AddEventToActivityLog(ext_id, params)); + } +} + +} // namespace extensions diff --git a/extensions/renderer/api_activity_logger.h b/extensions/renderer/api_activity_logger.h new file mode 100644 index 0000000..13f23f9 --- /dev/null +++ b/extensions/renderer/api_activity_logger.h @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_ +#define EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_ + +#include <string> + +#include "extensions/common/features/feature.h" +#include "extensions/renderer/object_backed_native_handler.h" +#include "v8/include/v8.h" + +namespace extensions { + +// Used to log extension API calls and events that are implemented with custom +// bindings.The actions are sent via IPC to extensions::ActivityLog for +// recording and display. +class APIActivityLogger : public ObjectBackedNativeHandler { + public: + explicit APIActivityLogger(ScriptContext* context); + + private: + // Used to distinguish API calls & events from each other in LogInternal. + enum CallType { APICALL, EVENT }; + + // This is ultimately invoked in bindings.js with JavaScript arguments. + // arg0 - extension ID as a string + // arg1 - API call name as a string + // arg2 - arguments to the API call + // arg3 - any extra logging info as a string (optional) + static void LogAPICall(const v8::FunctionCallbackInfo<v8::Value>& args); + + // This is ultimately invoked in bindings.js with JavaScript arguments. + // arg0 - extension ID as a string + // arg1 - Event name as a string + // arg2 - Event arguments + // arg3 - any extra logging info as a string (optional) + static void LogEvent(const v8::FunctionCallbackInfo<v8::Value>& args); + + // LogAPICall and LogEvent are really the same underneath except for + // how they are ultimately dispatched to the log. + static void LogInternal(const CallType call_type, + const v8::FunctionCallbackInfo<v8::Value>& args); + + DISALLOW_COPY_AND_ASSIGN(APIActivityLogger); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_ diff --git a/extensions/renderer/api_definitions_natives.cc b/extensions/renderer/api_definitions_natives.cc index a7970b2..c0dc699 100644 --- a/extensions/renderer/api_definitions_natives.cc +++ b/extensions/renderer/api_definitions_natives.cc @@ -4,9 +4,9 @@ #include "extensions/renderer/api_definitions_natives.h" -#include "chrome/renderer/extensions/dispatcher.h" #include "extensions/common/features/feature.h" #include "extensions/common/features/feature_provider.h" +#include "extensions/renderer/dispatcher.h" #include "extensions/renderer/script_context.h" namespace extensions { diff --git a/extensions/renderer/app_runtime_custom_bindings.cc b/extensions/renderer/app_runtime_custom_bindings.cc new file mode 100644 index 0000000..3a1f169 --- /dev/null +++ b/extensions/renderer/app_runtime_custom_bindings.cc @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/app_runtime_custom_bindings.h" + +#include "base/bind.h" +#include "base/strings/string_number_conversions.h" +#include "third_party/WebKit/public/platform/WebCString.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/web/WebBlob.h" +#include "third_party/WebKit/public/web/WebSerializedScriptValue.h" + +using blink::WebBlob; +using blink::WebSerializedScriptValue; +using blink::WebString; + +namespace { + +void DeserializeString(const v8::FunctionCallbackInfo<v8::Value>& args) { + DCHECK(args.Length() == 1); + DCHECK(args[0]->IsString()); + + std::string data_v8(*v8::String::Utf8Value(args[0])); + WebString data_webstring = WebString::fromUTF8(data_v8); + WebSerializedScriptValue serialized = + WebSerializedScriptValue::fromString(data_webstring); + args.GetReturnValue().Set(serialized.deserialize()); +} + +void SerializeToString(const v8::FunctionCallbackInfo<v8::Value>& args) { + DCHECK(args.Length() == 1); + WebSerializedScriptValue data = WebSerializedScriptValue::serialize(args[0]); + WebString data_webstring = data.toString(); + + std::string v = std::string(data_webstring.utf8()); + args.GetReturnValue().Set( + v8::String::NewFromUtf8(args.GetIsolate(), v.c_str())); +} + +void CreateBlob(const v8::FunctionCallbackInfo<v8::Value>& args) { + DCHECK(args.Length() == 2); + DCHECK(args[0]->IsString()); + DCHECK(args[1]->IsNumber()); + + std::string blob_file_path(*v8::String::Utf8Value(args[0])); + std::string blob_length_string(*v8::String::Utf8Value(args[1])); + int64 blob_length = 0; + DCHECK(base::StringToInt64(blob_length_string, &blob_length)); + blink::WebBlob web_blob = + WebBlob::createFromFile(WebString::fromUTF8(blob_file_path), blob_length); + args.GetReturnValue().Set(web_blob.toV8Value()); +} + +} // namespace + +namespace extensions { + +AppRuntimeCustomBindings::AppRuntimeCustomBindings(ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction("DeserializeString", base::Bind(&DeserializeString)); + RouteFunction("SerializeToString", base::Bind(&SerializeToString)); + RouteFunction("CreateBlob", base::Bind(&CreateBlob)); +} + +} // namespace extensions diff --git a/extensions/renderer/app_runtime_custom_bindings.h b/extensions/renderer/app_runtime_custom_bindings.h new file mode 100644 index 0000000..f4ff906 --- /dev/null +++ b/extensions/renderer/app_runtime_custom_bindings.h @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_APP_RUNTIME_CUSTOM_BINDINGS_H_ +#define EXTENSIONS_RENDERER_APP_RUNTIME_CUSTOM_BINDINGS_H_ + +#include "extensions/renderer/object_backed_native_handler.h" + +namespace extensions { + +// The native component of custom bindings for the chrome.app.runtime API. +class AppRuntimeCustomBindings : public ObjectBackedNativeHandler { + public: + explicit AppRuntimeCustomBindings(ScriptContext* context); + + private: + DISALLOW_COPY_AND_ASSIGN(AppRuntimeCustomBindings); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_APP_RUNTIME_CUSTOM_BINDINGS_H_ diff --git a/extensions/renderer/console.cc b/extensions/renderer/console.cc index 303de44..f7ae995 100644 --- a/extensions/renderer/console.cc +++ b/extensions/renderer/console.cc @@ -10,10 +10,10 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -#include "chrome/renderer/extensions/dispatcher.h" #include "chrome/renderer/extensions/extension_helper.h" #include "content/public/renderer/render_view.h" #include "content/public/renderer/render_view_visitor.h" +#include "extensions/renderer/dispatcher.h" #include "third_party/WebKit/public/web/WebConsoleMessage.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebView.h" diff --git a/extensions/renderer/default_dispatcher_delegate.cc b/extensions/renderer/default_dispatcher_delegate.cc new file mode 100644 index 0000000..326ee70 --- /dev/null +++ b/extensions/renderer/default_dispatcher_delegate.cc @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/default_dispatcher_delegate.h" + +#include "extensions/renderer/script_context.h" + +namespace extensions { + +DefaultDispatcherDelegate::DefaultDispatcherDelegate() { +} + +DefaultDispatcherDelegate::~DefaultDispatcherDelegate() { +} + +// DispatcherDelegate implementation. +scoped_ptr<ScriptContext> DefaultDispatcherDelegate::CreateScriptContext( + const v8::Handle<v8::Context>& v8_context, + blink::WebFrame* frame, + const Extension* extension, + Feature::Context context_type) { + return make_scoped_ptr( + new ScriptContext(v8_context, frame, extension, context_type)); +} + +} // namespace extensions diff --git a/extensions/renderer/default_dispatcher_delegate.h b/extensions/renderer/default_dispatcher_delegate.h new file mode 100644 index 0000000..1ee92ab --- /dev/null +++ b/extensions/renderer/default_dispatcher_delegate.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_DEFAULT_DISPATCHER_DELEGATE_H +#define EXTENSIONS_RENDERER_DEFAULT_DISPATCHER_DELEGATE_H + +#include "extensions/renderer/dispatcher_delegate.h" + +namespace extensions { + +class DefaultDispatcherDelegate : public DispatcherDelegate { + public: + DefaultDispatcherDelegate(); + virtual ~DefaultDispatcherDelegate(); + + // DispatcherDelegate implementation. + virtual scoped_ptr<ScriptContext> CreateScriptContext( + const v8::Handle<v8::Context>& v8_context, + blink::WebFrame* frame, + const Extension* extension, + Feature::Context context_type) OVERRIDE; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_DEFAULT_DISPATCHER_DELEGATE_H diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc new file mode 100644 index 0000000..93fa434 --- /dev/null +++ b/extensions/renderer/dispatcher.cc @@ -0,0 +1,1216 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/dispatcher.h" + +#include "base/callback.h" +#include "base/command_line.h" +#include "base/debug/alias.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/user_metrics_action.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "chrome/renderer/extensions/extension_helper.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/url_constants.h" +#include "content/public/renderer/render_thread.h" +#include "content/public/renderer/render_view.h" +#include "content/public/renderer/v8_value_converter.h" +#include "extensions/common/api/messaging/message.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_api.h" +#include "extensions/common/extension_messages.h" +#include "extensions/common/extension_urls.h" +#include "extensions/common/features/feature.h" +#include "extensions/common/features/feature_provider.h" +#include "extensions/common/manifest.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/manifest_handlers/background_info.h" +#include "extensions/common/manifest_handlers/externally_connectable.h" +#include "extensions/common/manifest_handlers/sandboxed_page_info.h" +#include "extensions/common/message_bundle.h" +#include "extensions/common/permissions/permission_set.h" +#include "extensions/common/permissions/permissions_data.h" +#include "extensions/common/switches.h" +#include "extensions/common/view_type.h" +#include "extensions/renderer/api_activity_logger.h" +#include "extensions/renderer/api_definitions_natives.h" +#include "extensions/renderer/app_runtime_custom_bindings.h" +#include "extensions/renderer/binding_generating_native_handler.h" +#include "extensions/renderer/blob_native_handler.h" +#include "extensions/renderer/content_watcher.h" +#include "extensions/renderer/context_menus_custom_bindings.h" +#include "extensions/renderer/css_native_handler.h" +#include "extensions/renderer/dispatcher_delegate.h" +#include "extensions/renderer/document_custom_bindings.h" +#include "extensions/renderer/dom_activity_logger.h" +#include "extensions/renderer/event_bindings.h" +#include "extensions/renderer/extension_groups.h" +#include "extensions/renderer/extensions_renderer_client.h" +#include "extensions/renderer/file_system_natives.h" +#include "extensions/renderer/i18n_custom_bindings.h" +#include "extensions/renderer/id_generator_custom_bindings.h" +#include "extensions/renderer/lazy_background_page_native_handler.h" +#include "extensions/renderer/logging_native_handler.h" +#include "extensions/renderer/messaging_bindings.h" +#include "extensions/renderer/module_system.h" +#include "extensions/renderer/print_native_handler.h" +#include "extensions/renderer/process_info_native_handler.h" +#include "extensions/renderer/render_view_observer_natives.h" +#include "extensions/renderer/request_sender.h" +#include "extensions/renderer/runtime_custom_bindings.h" +#include "extensions/renderer/safe_builtins.h" +#include "extensions/renderer/script_context.h" +#include "extensions/renderer/script_context_set.h" +#include "extensions/renderer/send_request_natives.h" +#include "extensions/renderer/set_icon_natives.h" +#include "extensions/renderer/test_features_native_handler.h" +#include "extensions/renderer/user_gestures_native_handler.h" +#include "extensions/renderer/user_script_slave.h" +#include "extensions/renderer/utils_native_handler.h" +#include "extensions/renderer/v8_context_native_handler.h" +#include "grit/common_resources.h" +#include "grit/renderer_resources.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/web/WebCustomElement.h" +#include "third_party/WebKit/public/web/WebDataSource.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebRuntimeFeatures.h" +#include "third_party/WebKit/public/web/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "ui/base/layout.h" +#include "ui/base/resource/resource_bundle.h" +#include "v8/include/v8.h" + +using base::UserMetricsAction; +using blink::WebDataSource; +using blink::WebDocument; +using blink::WebFrame; +using blink::WebScopedUserGesture; +using blink::WebSecurityPolicy; +using blink::WebString; +using blink::WebVector; +using blink::WebView; +using content::RenderThread; +using content::RenderView; + +namespace extensions { + +namespace { + +static const int64 kInitialExtensionIdleHandlerDelayMs = 5 * 1000; +static const int64 kMaxExtensionIdleHandlerDelayMs = 5 * 60 * 1000; +static const char kEventDispatchFunction[] = "dispatchEvent"; +static const char kOnSuspendEvent[] = "runtime.onSuspend"; +static const char kOnSuspendCanceledEvent[] = "runtime.onSuspendCanceled"; + +// Returns the global value for "chrome" from |context|. If one doesn't exist +// creates a new object for it. +// +// Note that this isn't necessarily an object, since webpages can write, for +// example, "window.chrome = true". +v8::Handle<v8::Value> GetOrCreateChrome(ScriptContext* context) { + v8::Handle<v8::String> chrome_string( + v8::String::NewFromUtf8(context->isolate(), "chrome")); + v8::Handle<v8::Object> global(context->v8_context()->Global()); + v8::Handle<v8::Value> chrome(global->Get(chrome_string)); + if (chrome->IsUndefined()) { + chrome = v8::Object::New(context->isolate()); + global->Set(chrome_string, chrome); + } + return chrome; +} + +// Returns |value| cast to an object if possible, else an empty handle. +v8::Handle<v8::Object> AsObjectOrEmpty(v8::Handle<v8::Value> value) { + return value->IsObject() ? value.As<v8::Object>() : v8::Handle<v8::Object>(); +} + +// Calls a method |method_name| in a module |module_name| belonging to the +// module system from |context|. Intended as a callback target from +// ScriptContextSet::ForEach. +void CallModuleMethod(const std::string& module_name, + const std::string& method_name, + const base::ListValue* args, + ScriptContext* context) { + v8::HandleScope handle_scope(context->isolate()); + v8::Context::Scope context_scope(context->v8_context()); + + scoped_ptr<content::V8ValueConverter> converter( + content::V8ValueConverter::create()); + + std::vector<v8::Handle<v8::Value> > arguments; + for (base::ListValue::const_iterator it = args->begin(); it != args->end(); + ++it) { + arguments.push_back(converter->ToV8Value(*it, context->v8_context())); + } + + context->module_system()->CallModuleMethod( + module_name, method_name, &arguments); +} + +// This handles the "chrome." root API object in script contexts. +class ChromeNativeHandler : public ObjectBackedNativeHandler { + public: + explicit ChromeNativeHandler(ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction( + "GetChrome", + base::Bind(&ChromeNativeHandler::GetChrome, base::Unretained(this))); + } + + void GetChrome(const v8::FunctionCallbackInfo<v8::Value>& args) { + args.GetReturnValue().Set(GetOrCreateChrome(context())); + } +}; + +} // namespace + +Dispatcher::Dispatcher(DispatcherDelegate* delegate) + : delegate_(delegate), + content_watcher_(new ContentWatcher()), + source_map_(&ResourceBundle::GetSharedInstance()), + v8_schema_registry_(new V8SchemaRegistry), + is_webkit_initialized_(false) { + const CommandLine& command_line = *(CommandLine::ForCurrentProcess()); + is_extension_process_ = + command_line.HasSwitch(extensions::switches::kExtensionProcess) || + command_line.HasSwitch(::switches::kSingleProcess); + + if (is_extension_process_) { + RenderThread::Get()->SetIdleNotificationDelayInMs( + kInitialExtensionIdleHandlerDelayMs); + } + + RenderThread::Get()->RegisterExtension(SafeBuiltins::CreateV8Extension()); + + user_script_slave_.reset(new UserScriptSlave(&extensions_)); + request_sender_.reset(new RequestSender(this)); + PopulateSourceMap(); +} + +Dispatcher::~Dispatcher() { +} + +bool Dispatcher::IsExtensionActive(const std::string& extension_id) const { + bool is_active = + active_extension_ids_.find(extension_id) != active_extension_ids_.end(); + if (is_active) + CHECK(extensions_.Contains(extension_id)); + return is_active; +} + +std::string Dispatcher::GetExtensionID(const WebFrame* frame, int world_id) { + if (world_id != 0) { + // Isolated worlds (content script). + return user_script_slave_->GetExtensionIdForIsolatedWorld(world_id); + } + + // TODO(kalman): Delete this check. + if (frame->document().securityOrigin().isUnique()) + return std::string(); + + // Extension pages (chrome-extension:// URLs). + GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame); + return extensions_.GetExtensionOrAppIDByURL(frame_url); +} + +void Dispatcher::DidCreateScriptContext( + WebFrame* frame, + const v8::Handle<v8::Context>& v8_context, + int extension_group, + int world_id) { +#if !defined(ENABLE_EXTENSIONS) + return; +#endif + + std::string extension_id = GetExtensionID(frame, world_id); + + const Extension* extension = extensions_.GetByID(extension_id); + if (!extension && !extension_id.empty()) { + // There are conditions where despite a context being associated with an + // extension, no extension actually gets found. Ignore "invalid" because + // CSP blocks extension page loading by switching the extension ID to + // "invalid". This isn't interesting. + if (extension_id != "invalid") { + LOG(ERROR) << "Extension \"" << extension_id << "\" not found"; + RenderThread::Get()->RecordAction( + UserMetricsAction("ExtensionNotFound_ED")); + } + + extension_id = ""; + } + + Feature::Context context_type = + ClassifyJavaScriptContext(extension, + extension_group, + ScriptContext::GetDataSourceURLForFrame(frame), + frame->document().securityOrigin()); + + ScriptContext* context = + delegate_->CreateScriptContext(v8_context, frame, extension, context_type) + .release(); + script_context_set_.Add(context); + + if (extension) { + InitOriginPermissions(extension, context_type); + } + + { + scoped_ptr<ModuleSystem> module_system( + new ModuleSystem(context, &source_map_)); + context->set_module_system(module_system.Pass()); + } + ModuleSystem* module_system = context->module_system(); + + // Enable natives in startup. + ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system); + + RegisterNativeHandlers(module_system, context); + + // chrome.Event is part of the public API (although undocumented). Make it + // lazily evalulate to Event from event_bindings.js. For extensions only + // though, not all webpages! + if (context->extension()) { + v8::Handle<v8::Object> chrome = AsObjectOrEmpty(GetOrCreateChrome(context)); + if (!chrome.IsEmpty()) + module_system->SetLazyField(chrome, "Event", kEventBindings, "Event"); + } + + UpdateBindingsForContext(context); + + bool is_within_platform_app = IsWithinPlatformApp(); + // Inject custom JS into the platform app context. + if (is_within_platform_app) { + module_system->Require("platformApp"); + } + + delegate_->RequireAdditionalModules( + module_system, extension, context_type, is_within_platform_app); + + VLOG(1) << "Num tracked contexts: " << script_context_set_.size(); +} + +void Dispatcher::WillReleaseScriptContext( + WebFrame* frame, + const v8::Handle<v8::Context>& v8_context, + int world_id) { + ScriptContext* context = script_context_set_.GetByV8Context(v8_context); + if (!context) + return; + + context->DispatchOnUnloadEvent(); + // TODO(kalman): add an invalidation observer interface to ScriptContext. + request_sender_->InvalidateSource(context); + + script_context_set_.Remove(context); + VLOG(1) << "Num tracked contexts: " << script_context_set_.size(); +} + +void Dispatcher::DidCreateDocumentElement(blink::WebFrame* frame) { + if (IsWithinPlatformApp()) { + // WebKit doesn't let us define an additional user agent stylesheet, so we + // insert the default platform app stylesheet into all documents that are + // loaded in each app. + std::string stylesheet = ResourceBundle::GetSharedInstance() + .GetRawDataResource(IDR_PLATFORM_APP_CSS) + .as_string(); + ReplaceFirstSubstringAfterOffset( + &stylesheet, 0, "$FONTFAMILY", system_font_family_); + ReplaceFirstSubstringAfterOffset( + &stylesheet, 0, "$FONTSIZE", system_font_size_); + frame->document().insertStyleSheet(WebString::fromUTF8(stylesheet)); + } + + content_watcher_->DidCreateDocumentElement(frame); +} + +void Dispatcher::DidMatchCSS( + blink::WebFrame* frame, + const blink::WebVector<blink::WebString>& newly_matching_selectors, + const blink::WebVector<blink::WebString>& stopped_matching_selectors) { + content_watcher_->DidMatchCSS( + frame, newly_matching_selectors, stopped_matching_selectors); +} + +void Dispatcher::OnExtensionResponse(int request_id, + bool success, + const base::ListValue& response, + const std::string& error) { + request_sender_->HandleResponse(request_id, success, response, error); +} + +bool Dispatcher::CheckContextAccessToExtensionAPI( + const std::string& function_name, + ScriptContext* context) const { + if (!context) { + DLOG(ERROR) << "Not in a v8::Context"; + return false; + } + + if (!context->extension()) { + context->isolate()->ThrowException(v8::Exception::Error( + v8::String::NewFromUtf8(context->isolate(), "Not in an extension."))); + return false; + } + + // Theoretically we could end up with bindings being injected into sandboxed + // frames, for example content scripts. Don't let them execute API functions. + blink::WebFrame* frame = context->web_frame(); + if (IsSandboxedPage(ScriptContext::GetDataSourceURLForFrame(frame))) { + static const char kMessage[] = + "%s cannot be used within a sandboxed frame."; + std::string error_msg = base::StringPrintf(kMessage, function_name.c_str()); + context->isolate()->ThrowException(v8::Exception::Error( + v8::String::NewFromUtf8(context->isolate(), error_msg.c_str()))); + return false; + } + + Feature::Availability availability = context->GetAvailability(function_name); + if (!availability.is_available()) { + context->isolate()->ThrowException( + v8::Exception::Error(v8::String::NewFromUtf8( + context->isolate(), availability.message().c_str()))); + } + + return availability.is_available(); +} + +void Dispatcher::DispatchEvent(const std::string& extension_id, + const std::string& event_name) const { + base::ListValue args; + args.Set(0, new base::StringValue(event_name)); + args.Set(1, new base::ListValue()); + + // Needed for Windows compilation, since kEventBindings is declared extern. + const char* local_event_bindings = kEventBindings; + script_context_set_.ForEach(extension_id, + NULL, // all render views + base::Bind(&CallModuleMethod, + local_event_bindings, + kEventDispatchFunction, + &args)); +} + +void Dispatcher::InvokeModuleSystemMethod(content::RenderView* render_view, + const std::string& extension_id, + const std::string& module_name, + const std::string& function_name, + const base::ListValue& args, + bool user_gesture) { + scoped_ptr<WebScopedUserGesture> web_user_gesture; + if (user_gesture) + web_user_gesture.reset(new WebScopedUserGesture); + + script_context_set_.ForEach( + extension_id, + render_view, + base::Bind(&CallModuleMethod, module_name, function_name, &args)); + + // Reset the idle handler each time there's any activity like event or message + // dispatch, for which Invoke is the chokepoint. + if (is_extension_process_) { + RenderThread::Get()->ScheduleIdleHandler( + kInitialExtensionIdleHandlerDelayMs); + } + + // Tell the browser process when an event has been dispatched with a lazy + // background page active. + const Extension* extension = extensions_.GetByID(extension_id); + if (extension && BackgroundInfo::HasLazyBackgroundPage(extension) && + module_name == kEventBindings && + function_name == kEventDispatchFunction) { + RenderView* background_view = + ExtensionHelper::GetBackgroundPage(extension_id); + if (background_view) { + background_view->Send( + new ExtensionHostMsg_EventAck(background_view->GetRoutingID())); + } + } +} + +void Dispatcher::ClearPortData(int port_id) { + // Only the target port side has entries in |port_to_tab_id_map_|. If + // |port_id| is a source port, std::map::erase() will just silently fail + // here as a no-op. + port_to_tab_id_map_.erase(port_id); +} + +bool Dispatcher::OnControlMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(Dispatcher, message) + IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension) + IPC_MESSAGE_HANDLER(ExtensionMsg_CancelSuspend, OnCancelSuspend) + IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions, + OnClearTabSpecificPermissions) + IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage) + IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect, OnDispatchOnConnect) + IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect, OnDispatchOnDisconnect) + IPC_MESSAGE_HANDLER(ExtensionMsg_Loaded, OnLoaded) + IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnMessageInvoke) + IPC_MESSAGE_HANDLER(ExtensionMsg_SetChannel, OnSetChannel) + IPC_MESSAGE_HANDLER(ExtensionMsg_SetFunctionNames, OnSetFunctionNames) + IPC_MESSAGE_HANDLER(ExtensionMsg_SetScriptingWhitelist, + OnSetScriptingWhitelist) + IPC_MESSAGE_HANDLER(ExtensionMsg_SetSystemFont, OnSetSystemFont) + IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldSuspend, OnShouldSuspend) + IPC_MESSAGE_HANDLER(ExtensionMsg_Suspend, OnSuspend) + IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded) + IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions) + IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions, + OnUpdateTabSpecificPermissions) + IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts) + IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI) + IPC_MESSAGE_FORWARD(ExtensionMsg_WatchPages, + content_watcher_.get(), + ContentWatcher::OnWatchPages) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +void Dispatcher::WebKitInitialized() { + // For extensions, we want to ensure we call the IdleHandler every so often, + // even if the extension keeps up activity. + if (is_extension_process_) { + forced_idle_timer_.reset(new base::RepeatingTimer<content::RenderThread>); + forced_idle_timer_->Start( + FROM_HERE, + base::TimeDelta::FromMilliseconds(kMaxExtensionIdleHandlerDelayMs), + RenderThread::Get(), + &RenderThread::IdleHandler); + } + + // Initialize host permissions for any extensions that were activated before + // WebKit was initialized. + for (std::set<std::string>::iterator iter = active_extension_ids_.begin(); + iter != active_extension_ids_.end(); + ++iter) { + const Extension* extension = extensions_.GetByID(*iter); + CHECK(extension); + } + + EnableCustomElementWhiteList(); + + is_webkit_initialized_ = true; +} + +void Dispatcher::IdleNotification() { + if (is_extension_process_) { + // Dampen the forced delay as well if the extension stays idle for long + // periods of time. + int64 forced_delay_ms = + std::max(RenderThread::Get()->GetIdleNotificationDelayInMs(), + kMaxExtensionIdleHandlerDelayMs); + forced_idle_timer_->Stop(); + forced_idle_timer_->Start( + FROM_HERE, + base::TimeDelta::FromMilliseconds(forced_delay_ms), + RenderThread::Get(), + &RenderThread::IdleHandler); + } +} + +void Dispatcher::OnRenderProcessShutdown() { + v8_schema_registry_.reset(); + forced_idle_timer_.reset(); +} + +void Dispatcher::OnActivateExtension(const std::string& extension_id) { + const Extension* extension = extensions_.GetByID(extension_id); + if (!extension) { + // Extension was activated but was never loaded. This probably means that + // the renderer failed to load it (or the browser failed to tell us when it + // did). Failures shouldn't happen, but instead of crashing there (which + // executes on all renderers) be conservative and only crash in the renderer + // of the extension which failed to load; this one. + std::string& error = extension_load_errors_[extension_id]; + char minidump[256]; + base::debug::Alias(&minidump); + base::snprintf(minidump, + arraysize(minidump), + "e::dispatcher:%s:%s", + extension_id.c_str(), + error.c_str()); + CHECK(extension) << extension_id << " was never loaded: " << error; + } + + active_extension_ids_.insert(extension_id); + + // This is called when starting a new extension page, so start the idle + // handler ticking. + RenderThread::Get()->ScheduleIdleHandler(kInitialExtensionIdleHandlerDelayMs); + + if (is_webkit_initialized_) { + extensions::DOMActivityLogger::AttachToWorld( + extensions::DOMActivityLogger::kMainWorldId, extension_id); + } + + UpdateActiveExtensions(); +} + +void Dispatcher::OnCancelSuspend(const std::string& extension_id) { + DispatchEvent(extension_id, kOnSuspendCanceledEvent); +} + +void Dispatcher::OnClearTabSpecificPermissions( + int tab_id, + const std::vector<std::string>& extension_ids) { + delegate_->ClearTabSpecificPermissions(this, tab_id, extension_ids); +} + +void Dispatcher::OnDeliverMessage(int target_port_id, const Message& message) { + scoped_ptr<RequestSender::ScopedTabID> scoped_tab_id; + std::map<int, int>::const_iterator it = + port_to_tab_id_map_.find(target_port_id); + if (it != port_to_tab_id_map_.end()) { + scoped_tab_id.reset( + new RequestSender::ScopedTabID(request_sender(), it->second)); + } + + MessagingBindings::DeliverMessage(script_context_set_.GetAll(), + target_port_id, + message, + NULL); // All render views. +} + +void Dispatcher::OnDispatchOnConnect( + int target_port_id, + const std::string& channel_name, + const base::DictionaryValue& source_tab, + const ExtensionMsg_ExternalConnectionInfo& info, + const std::string& tls_channel_id) { + DCHECK(!ContainsKey(port_to_tab_id_map_, target_port_id)); + DCHECK_EQ(1, target_port_id % 2); // target renderer ports have odd IDs. + int sender_tab_id = -1; + source_tab.GetInteger("id", &sender_tab_id); + port_to_tab_id_map_[target_port_id] = sender_tab_id; + + MessagingBindings::DispatchOnConnect(script_context_set_.GetAll(), + target_port_id, + channel_name, + source_tab, + info.source_id, + info.target_id, + info.source_url, + tls_channel_id, + NULL); // All render views. +} + +void Dispatcher::OnDispatchOnDisconnect(int port_id, + const std::string& error_message) { + MessagingBindings::DispatchOnDisconnect(script_context_set_.GetAll(), + port_id, + error_message, + NULL); // All render views. +} + +void Dispatcher::OnLoaded( + const std::vector<ExtensionMsg_Loaded_Params>& loaded_extensions) { + std::vector<ExtensionMsg_Loaded_Params>::const_iterator i; + for (i = loaded_extensions.begin(); i != loaded_extensions.end(); ++i) { + std::string error; + scoped_refptr<const Extension> extension = i->ConvertToExtension(&error); + if (!extension.get()) { + extension_load_errors_[i->id] = error; + continue; + } + OnLoadedInternal(extension); + } + // Update the available bindings for all contexts. These may have changed if + // an externally_connectable extension was loaded that can connect to an + // open webpage. + UpdateBindings(""); +} + +void Dispatcher::OnLoadedInternal(scoped_refptr<const Extension> extension) { + extensions_.Insert(extension); +} + +void Dispatcher::OnMessageInvoke(const std::string& extension_id, + const std::string& module_name, + const std::string& function_name, + const base::ListValue& args, + bool user_gesture) { + InvokeModuleSystemMethod( + NULL, extension_id, module_name, function_name, args, user_gesture); +} + +void Dispatcher::OnSetChannel(int channel) { + delegate_->SetChannel(channel); +} + +void Dispatcher::OnSetFunctionNames(const std::vector<std::string>& names) { + function_names_.clear(); + for (size_t i = 0; i < names.size(); ++i) + function_names_.insert(names[i]); +} + +void Dispatcher::OnSetScriptingWhitelist( + const ExtensionsClient::ScriptingWhitelist& extension_ids) { + ExtensionsClient::Get()->SetScriptingWhitelist(extension_ids); +} + +void Dispatcher::OnSetSystemFont(const std::string& font_family, + const std::string& font_size) { + system_font_family_ = font_family; + system_font_size_ = font_size; +} + +void Dispatcher::OnShouldSuspend(const std::string& extension_id, + int sequence_id) { + RenderThread::Get()->Send( + new ExtensionHostMsg_ShouldSuspendAck(extension_id, sequence_id)); +} + +void Dispatcher::OnSuspend(const std::string& extension_id) { + // Dispatch the suspend event. This doesn't go through the standard event + // dispatch machinery because it requires special handling. We need to let + // the browser know when we are starting and stopping the event dispatch, so + // that it still considers the extension idle despite any activity the suspend + // event creates. + DispatchEvent(extension_id, kOnSuspendEvent); + RenderThread::Get()->Send(new ExtensionHostMsg_SuspendAck(extension_id)); +} + +void Dispatcher::OnUnloaded(const std::string& id) { + extensions_.Remove(id); + active_extension_ids_.erase(id); + + // If the extension is later reloaded with a different set of permissions, + // we'd like it to get a new isolated world ID, so that it can pick up the + // changed origin whitelist. + user_script_slave_->RemoveIsolatedWorld(id); + + // Invalidate all of the contexts that were removed. + // TODO(kalman): add an invalidation observer interface to ScriptContext. + ScriptContextSet::ContextSet removed_contexts = + script_context_set_.OnExtensionUnloaded(id); + for (ScriptContextSet::ContextSet::iterator it = removed_contexts.begin(); + it != removed_contexts.end(); + ++it) { + request_sender_->InvalidateSource(*it); + } + + // Update the available bindings for the remaining contexts. These may have + // changed if an externally_connectable extension is unloaded and a webpage + // is no longer accessible. + UpdateBindings(""); + + // Invalidates the messages map for the extension in case the extension is + // reloaded with a new messages map. + EraseL10nMessagesMap(id); + + // We don't do anything with existing platform-app stylesheets. They will + // stay resident, but the URL pattern corresponding to the unloaded + // extension's URL just won't match anything anymore. +} + +void Dispatcher::OnUpdatePermissions( + const ExtensionMsg_UpdatePermissions_Params& params) { + int reason_id = params.reason_id; + const std::string& extension_id = params.extension_id; + const APIPermissionSet& apis = params.apis; + const ManifestPermissionSet& manifest_permissions = + params.manifest_permissions; + const URLPatternSet& explicit_hosts = params.explicit_hosts; + const URLPatternSet& scriptable_hosts = params.scriptable_hosts; + + const Extension* extension = extensions_.GetByID(extension_id); + if (!extension) + return; + + scoped_refptr<const PermissionSet> delta = new PermissionSet( + apis, manifest_permissions, explicit_hosts, scriptable_hosts); + scoped_refptr<const PermissionSet> old_active = + extension->GetActivePermissions(); + UpdatedExtensionPermissionsInfo::Reason reason = + static_cast<UpdatedExtensionPermissionsInfo::Reason>(reason_id); + + const PermissionSet* new_active = NULL; + switch (reason) { + case UpdatedExtensionPermissionsInfo::ADDED: + new_active = PermissionSet::CreateUnion(old_active.get(), delta.get()); + break; + case UpdatedExtensionPermissionsInfo::REMOVED: + new_active = + PermissionSet::CreateDifference(old_active.get(), delta.get()); + break; + } + + PermissionsData::SetActivePermissions(extension, new_active); + UpdateOriginPermissions(reason, extension, explicit_hosts); + UpdateBindings(extension->id()); +} + +void Dispatcher::OnUpdateTabSpecificPermissions( + int page_id, + int tab_id, + const std::string& extension_id, + const URLPatternSet& origin_set) { + delegate_->UpdateTabSpecificPermissions( + this, page_id, tab_id, extension_id, origin_set); +} + +void Dispatcher::OnUpdateUserScripts(base::SharedMemoryHandle scripts) { + DCHECK(base::SharedMemory::IsHandleValid(scripts)) << "Bad scripts handle"; + user_script_slave_->UpdateScripts(scripts); + UpdateActiveExtensions(); +} + +void Dispatcher::OnUsingWebRequestAPI(bool adblock, + bool adblock_plus, + bool other_webrequest) { + delegate_->HandleWebRequestAPIUsage(adblock, adblock_plus, other_webrequest); +} + +void Dispatcher::UpdateActiveExtensions() { + std::set<std::string> active_extensions = active_extension_ids_; + user_script_slave_->GetActiveExtensions(&active_extensions); + delegate_->OnActiveExtensionsUpdated(active_extensions); +} + +void Dispatcher::InitOriginPermissions(const Extension* extension, + Feature::Context context_type) { + delegate_->InitOriginPermissions(extension, context_type); + UpdateOriginPermissions( + UpdatedExtensionPermissionsInfo::ADDED, + extension, + PermissionsData::GetEffectiveHostPermissions(extension)); +} + +void Dispatcher::UpdateOriginPermissions( + UpdatedExtensionPermissionsInfo::Reason reason, + const Extension* extension, + const URLPatternSet& origins) { + for (URLPatternSet::const_iterator i = origins.begin(); i != origins.end(); + ++i) { + const char* schemes[] = { + content::kHttpScheme, content::kHttpsScheme, content::kFileScheme, + content::kChromeUIScheme, content::kFtpScheme, + }; + for (size_t j = 0; j < arraysize(schemes); ++j) { + if (i->MatchesScheme(schemes[j])) { + ((reason == UpdatedExtensionPermissionsInfo::REMOVED) + ? WebSecurityPolicy::removeOriginAccessWhitelistEntry + : WebSecurityPolicy::addOriginAccessWhitelistEntry)( + extension->url(), + WebString::fromUTF8(schemes[j]), + WebString::fromUTF8(i->host()), + i->match_subdomains()); + } + } + } +} + +void Dispatcher::EnableCustomElementWhiteList() { + blink::WebCustomElement::addEmbedderCustomElementName("webview"); + // TODO(fsamuel): Add <adview> to the whitelist once it has been converted + // into a custom element. + blink::WebCustomElement::addEmbedderCustomElementName("browser-plugin"); +} + +void Dispatcher::UpdateBindings(const std::string& extension_id) { + script_context_set().ForEach(extension_id, + NULL, // all render views + base::Bind(&Dispatcher::UpdateBindingsForContext, + base::Unretained(this))); +} + +void Dispatcher::UpdateBindingsForContext(ScriptContext* context) { + v8::HandleScope handle_scope(context->isolate()); + v8::Context::Scope context_scope(context->v8_context()); + + // TODO(kalman): Make the bindings registration have zero overhead then run + // the same code regardless of context type. + switch (context->context_type()) { + case Feature::UNSPECIFIED_CONTEXT: + case Feature::WEB_PAGE_CONTEXT: + case Feature::BLESSED_WEB_PAGE_CONTEXT: { + // Web page context; it's too expensive to run the full bindings code. + // Hard-code that the app and webstore APIs are available... + RegisterBinding("app", context); + RegisterBinding("webstore", context); + + // ... and that the runtime API might be available if any extension can + // connect to it. + bool runtime_is_available = false; + for (ExtensionSet::const_iterator it = extensions_.begin(); + it != extensions_.end(); + ++it) { + ExternallyConnectableInfo* info = + static_cast<ExternallyConnectableInfo*>( + (*it)->GetManifestData(manifest_keys::kExternallyConnectable)); + if (info && info->matches.MatchesURL(context->GetURL())) { + runtime_is_available = true; + break; + } + } + if (runtime_is_available) + RegisterBinding("runtime", context); + break; + } + + case Feature::BLESSED_EXTENSION_CONTEXT: + case Feature::UNBLESSED_EXTENSION_CONTEXT: + case Feature::CONTENT_SCRIPT_CONTEXT: { + // Extension context; iterate through all the APIs and bind the available + // ones. + FeatureProvider* api_feature_provider = FeatureProvider::GetAPIFeatures(); + const std::vector<std::string>& apis = + api_feature_provider->GetAllFeatureNames(); + for (std::vector<std::string>::const_iterator it = apis.begin(); + it != apis.end(); + ++it) { + const std::string& api_name = *it; + Feature* feature = api_feature_provider->GetFeature(api_name); + DCHECK(feature); + + // Internal APIs are included via require(api_name) from internal code + // rather than chrome[api_name]. + if (feature->IsInternal()) + continue; + + // If this API has a parent feature (and isn't marked 'noparent'), + // then this must be a function or event, so we should not register. + if (api_feature_provider->GetParent(feature) != NULL) + continue; + + if (context->IsAnyFeatureAvailableToContext(*feature)) + RegisterBinding(api_name, context); + } + if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType)) { + RegisterBinding("test", context); + } + break; + } + } +} + +void Dispatcher::RegisterBinding(const std::string& api_name, + ScriptContext* context) { + std::string bind_name; + v8::Handle<v8::Object> bind_object = + GetOrCreateBindObjectIfAvailable(api_name, &bind_name, context); + + // Empty if the bind object failed to be created, probably because the + // extension overrode chrome with a non-object, e.g. window.chrome = true. + if (bind_object.IsEmpty()) + return; + + v8::Local<v8::String> v8_api_name = + v8::String::NewFromUtf8(context->isolate(), api_name.c_str()); + if (bind_object->HasRealNamedProperty(v8_api_name)) { + // The bind object may already have the property if the API has been + // registered before (or if the extension has put something there already, + // but, whatevs). + // + // In the former case, we need to re-register the bindings for the APIs + // which the extension now has permissions for (if any), but not touch any + // others so that we don't destroy state such as event listeners. + // + // TODO(kalman): Only register available APIs to make this all moot. + if (bind_object->HasRealNamedCallbackProperty(v8_api_name)) + return; // lazy binding still there, nothing to do + if (bind_object->Get(v8_api_name)->IsObject()) + return; // binding has already been fully installed + } + + ModuleSystem* module_system = context->module_system(); + if (!source_map_.Contains(api_name)) { + module_system->RegisterNativeHandler( + api_name, + scoped_ptr<NativeHandler>(new BindingGeneratingNativeHandler( + module_system, api_name, "binding"))); + module_system->SetNativeLazyField( + bind_object, bind_name, api_name, "binding"); + } else { + module_system->SetLazyField(bind_object, bind_name, api_name, "binding"); + } +} + +// NOTE: please use the naming convention "foo_natives" for these. +void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system, + ScriptContext* context) { + module_system->RegisterNativeHandler( + "chrome", scoped_ptr<NativeHandler>(new ChromeNativeHandler(context))); + module_system->RegisterNativeHandler( + "lazy_background_page", + scoped_ptr<NativeHandler>(new LazyBackgroundPageNativeHandler(context))); + module_system->RegisterNativeHandler( + "logging", scoped_ptr<NativeHandler>(new LoggingNativeHandler(context))); + module_system->RegisterNativeHandler("schema_registry", + v8_schema_registry_->AsNativeHandler()); + module_system->RegisterNativeHandler( + "print", scoped_ptr<NativeHandler>(new PrintNativeHandler(context))); + module_system->RegisterNativeHandler( + "test_features", + scoped_ptr<NativeHandler>(new TestFeaturesNativeHandler(context))); + module_system->RegisterNativeHandler( + "user_gestures", + scoped_ptr<NativeHandler>(new UserGesturesNativeHandler(context))); + module_system->RegisterNativeHandler( + "utils", scoped_ptr<NativeHandler>(new UtilsNativeHandler(context))); + module_system->RegisterNativeHandler( + "v8_context", + scoped_ptr<NativeHandler>(new V8ContextNativeHandler(context, this))); + + const Extension* extension = context->extension(); + int manifest_version = extension ? extension->manifest_version() : 1; + bool send_request_disabled = + (extension && Manifest::IsUnpackedLocation(extension->location()) && + BackgroundInfo::HasLazyBackgroundPage(extension)); + module_system->RegisterNativeHandler( + "process", + scoped_ptr<NativeHandler>(new ProcessInfoNativeHandler( + context, + context->GetExtensionID(), + context->GetContextTypeDescription(), + ExtensionsRendererClient::Get()->IsIncognitoProcess(), + manifest_version, + send_request_disabled))); + + module_system->RegisterNativeHandler( + "event_natives", + scoped_ptr<NativeHandler>(new EventBindings(this, context))); + module_system->RegisterNativeHandler( + "messaging_natives", + scoped_ptr<NativeHandler>(MessagingBindings::Get(this, context))); + module_system->RegisterNativeHandler( + "apiDefinitions", + scoped_ptr<NativeHandler>(new ApiDefinitionsNatives(this, context))); + module_system->RegisterNativeHandler( + "sendRequest", + scoped_ptr<NativeHandler>( + new SendRequestNatives(request_sender_.get(), context))); + module_system->RegisterNativeHandler( + "setIcon", + scoped_ptr<NativeHandler>( + new SetIconNatives(request_sender_.get(), context))); + module_system->RegisterNativeHandler( + "activityLogger", + scoped_ptr<NativeHandler>(new APIActivityLogger(context))); + module_system->RegisterNativeHandler( + "renderViewObserverNatives", + scoped_ptr<NativeHandler>(new RenderViewObserverNatives(context))); + + // Natives used by multiple APIs. + module_system->RegisterNativeHandler( + "file_system_natives", + scoped_ptr<NativeHandler>(new FileSystemNatives(context))); + + // Custom bindings. + module_system->RegisterNativeHandler( + "app_runtime", + scoped_ptr<NativeHandler>(new AppRuntimeCustomBindings(context))); + module_system->RegisterNativeHandler( + "blob_natives", + scoped_ptr<NativeHandler>(new BlobNativeHandler(context))); + module_system->RegisterNativeHandler( + "context_menus", + scoped_ptr<NativeHandler>(new ContextMenusCustomBindings(context))); + module_system->RegisterNativeHandler( + "css_natives", scoped_ptr<NativeHandler>(new CssNativeHandler(context))); + module_system->RegisterNativeHandler( + "document_natives", + scoped_ptr<NativeHandler>(new DocumentCustomBindings(context))); + module_system->RegisterNativeHandler( + "i18n", scoped_ptr<NativeHandler>(new I18NCustomBindings(context))); + module_system->RegisterNativeHandler( + "id_generator", + scoped_ptr<NativeHandler>(new IdGeneratorCustomBindings(context))); + module_system->RegisterNativeHandler( + "runtime", scoped_ptr<NativeHandler>(new RuntimeCustomBindings(context))); + + delegate_->RegisterNativeHandlers(this, module_system, context); +} + +void Dispatcher::PopulateSourceMap() { + // Libraries. + source_map_.RegisterSource("entryIdManager", IDR_ENTRY_ID_MANAGER); + source_map_.RegisterSource(kEventBindings, IDR_EVENT_BINDINGS_JS); + source_map_.RegisterSource("imageUtil", IDR_IMAGE_UTIL_JS); + source_map_.RegisterSource("json_schema", IDR_JSON_SCHEMA_JS); + source_map_.RegisterSource("lastError", IDR_LAST_ERROR_JS); + source_map_.RegisterSource("messaging", IDR_MESSAGING_JS); + source_map_.RegisterSource("messaging_utils", IDR_MESSAGING_UTILS_JS); + source_map_.RegisterSource(kSchemaUtils, IDR_SCHEMA_UTILS_JS); + source_map_.RegisterSource("sendRequest", IDR_SEND_REQUEST_JS); + source_map_.RegisterSource("setIcon", IDR_SET_ICON_JS); + source_map_.RegisterSource("test", IDR_TEST_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("unload_event", IDR_UNLOAD_EVENT_JS); + source_map_.RegisterSource("utils", IDR_UTILS_JS); + + // Custom bindings. + source_map_.RegisterSource("app.runtime", IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("contextMenus", + IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("i18n", IDR_I18N_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("permissions", IDR_PERMISSIONS_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("runtime", IDR_RUNTIME_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("binding", IDR_BINDING_JS); + + // Custom types sources. + source_map_.RegisterSource("StorageArea", IDR_STORAGE_AREA_JS); + + // Platform app sources that are not API-specific.. + source_map_.RegisterSource("platformApp", IDR_PLATFORM_APP_JS); + + delegate_->PopulateSourceMap(&source_map_); +} + +bool Dispatcher::IsWithinPlatformApp() { + for (std::set<std::string>::iterator iter = active_extension_ids_.begin(); + iter != active_extension_ids_.end(); + ++iter) { + const Extension* extension = extensions_.GetByID(*iter); + if (extension && extension->is_platform_app()) + return true; + } + return false; +} + +// TODO(kalman): This is checking for the wrong thing, it should be checking if +// the frame's security origin is unique. The extension sandbox directive is +// checked for in extensions/common/manifest_handlers/csp_info.cc. +bool Dispatcher::IsSandboxedPage(const GURL& url) const { + if (url.SchemeIs(kExtensionScheme)) { + const Extension* extension = extensions_.GetByID(url.host()); + if (extension) { + return SandboxedPageInfo::IsSandboxedPage(extension, url.path()); + } + } + return false; +} + +Feature::Context Dispatcher::ClassifyJavaScriptContext( + const Extension* extension, + int extension_group, + const GURL& url, + const blink::WebSecurityOrigin& origin) { + DCHECK_GE(extension_group, 0); + if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) { + return extension ? // TODO(kalman): when does this happen? + Feature::CONTENT_SCRIPT_CONTEXT + : Feature::UNSPECIFIED_CONTEXT; + } + + // We have an explicit check for sandboxed pages before checking whether the + // extension is active in this process because: + // 1. Sandboxed pages run in the same process as regular extension pages, so + // the extension is considered active. + // 2. ScriptContext creation (which triggers bindings injection) happens + // before the SecurityContext is updated with the sandbox flags (after + // reading the CSP header), so the caller can't check if the context's + // security origin is unique yet. + if (IsSandboxedPage(url)) + return Feature::WEB_PAGE_CONTEXT; + + if (extension && IsExtensionActive(extension->id())) { + // |extension| is active in this process, but it could be either a true + // extension process or within the extent of a hosted app. In the latter + // case this would usually be considered a (blessed) web page context, + // unless the extension in question is a component extension, in which case + // we cheat and call it blessed. + return (extension->is_hosted_app() && + extension->location() != Manifest::COMPONENT) + ? Feature::BLESSED_WEB_PAGE_CONTEXT + : Feature::BLESSED_EXTENSION_CONTEXT; + } + + // TODO(kalman): This isUnique() check is wrong, it should be performed as + // part of IsSandboxedPage(). + if (!origin.isUnique() && extensions_.ExtensionBindingsAllowed(url)) { + if (!extension) // TODO(kalman): when does this happen? + return Feature::UNSPECIFIED_CONTEXT; + return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT + : Feature::UNBLESSED_EXTENSION_CONTEXT; + } + + if (url.is_valid()) + return Feature::WEB_PAGE_CONTEXT; + + return Feature::UNSPECIFIED_CONTEXT; +} + +v8::Handle<v8::Object> Dispatcher::GetOrCreateObject( + const v8::Handle<v8::Object>& object, + const std::string& field, + v8::Isolate* isolate) { + v8::Handle<v8::String> key = v8::String::NewFromUtf8(isolate, field.c_str()); + // If the object has a callback property, it is assumed it is an unavailable + // API, so it is safe to delete. This is checked before GetOrCreateObject is + // called. + if (object->HasRealNamedCallbackProperty(key)) { + object->Delete(key); + } else if (object->HasRealNamedProperty(key)) { + v8::Handle<v8::Value> value = object->Get(key); + CHECK(value->IsObject()); + return v8::Handle<v8::Object>::Cast(value); + } + + v8::Handle<v8::Object> new_object = v8::Object::New(isolate); + object->Set(key, new_object); + return new_object; +} + +v8::Handle<v8::Object> Dispatcher::GetOrCreateBindObjectIfAvailable( + const std::string& api_name, + std::string* bind_name, + ScriptContext* context) { + std::vector<std::string> split; + base::SplitString(api_name, '.', &split); + + v8::Handle<v8::Object> bind_object; + + // Check if this API has an ancestor. If the API's ancestor is available and + // the API is not available, don't install the bindings for this API. If + // the API is available and its ancestor is not, delete the ancestor and + // install the bindings for the API. This is to prevent loading the ancestor + // API schema if it will not be needed. + // + // For example: + // If app is available and app.window is not, just install app. + // If app.window is available and app is not, delete app and install + // app.window on a new object so app does not have to be loaded. + FeatureProvider* api_feature_provider = FeatureProvider::GetAPIFeatures(); + std::string ancestor_name; + bool only_ancestor_available = false; + + for (size_t i = 0; i < split.size() - 1; ++i) { + ancestor_name += (i ? "." : "") + split[i]; + if (api_feature_provider->GetFeature(ancestor_name) && + context->GetAvailability(ancestor_name).is_available() && + !context->GetAvailability(api_name).is_available()) { + only_ancestor_available = true; + break; + } + + if (bind_object.IsEmpty()) { + bind_object = AsObjectOrEmpty(GetOrCreateChrome(context)); + if (bind_object.IsEmpty()) + return v8::Handle<v8::Object>(); + } + bind_object = GetOrCreateObject(bind_object, split[i], context->isolate()); + } + + if (only_ancestor_available) + return v8::Handle<v8::Object>(); + + if (bind_name) + *bind_name = split.back(); + + return bind_object.IsEmpty() ? AsObjectOrEmpty(GetOrCreateChrome(context)) + : bind_object; +} + +} // namespace extensions diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h new file mode 100644 index 0000000..e4d7aed --- /dev/null +++ b/extensions/renderer/dispatcher.h @@ -0,0 +1,293 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_DISPATCHER_H_ +#define EXTENSIONS_RENDERER_DISPATCHER_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/memory/shared_memory.h" +#include "base/timer/timer.h" +#include "content/public/renderer/render_process_observer.h" +#include "extensions/common/event_filter.h" +#include "extensions/common/extension_set.h" +#include "extensions/common/extensions_client.h" +#include "extensions/common/features/feature.h" +#include "extensions/renderer/resource_bundle_source_map.h" +#include "extensions/renderer/script_context.h" +#include "extensions/renderer/script_context_set.h" +#include "extensions/renderer/v8_schema_registry.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebVector.h" +#include "v8/include/v8.h" + +class ChromeRenderViewTest; +class GURL; +class ModuleSystem; +class URLPattern; +struct ExtensionMsg_ExternalConnectionInfo; +struct ExtensionMsg_Loaded_Params; +struct ExtensionMsg_UpdatePermissions_Params; + +namespace blink { +class WebFrame; +class WebSecurityOrigin; +} + +namespace base { +class DictionaryValue; +class ListValue; +} + +namespace content { +class RenderThread; +} + +namespace extensions { +class ContentWatcher; +class DispatcherDelegate; +class Extension; +class FilteredEventRouter; +class ManifestPermissionSet; +class RequestSender; +class ScriptContext; +class UserScriptSlave; +struct Message; + +// Dispatches extension control messages sent to the renderer and stores +// renderer extension related state. +class Dispatcher : public content::RenderProcessObserver { + public: + explicit Dispatcher(DispatcherDelegate* delegate); + virtual ~Dispatcher(); + + const std::set<std::string>& function_names() const { + return function_names_; + } + + bool is_extension_process() const { return is_extension_process_; } + + const ExtensionSet* extensions() const { return &extensions_; } + + const ScriptContextSet& script_context_set() const { + return script_context_set_; + } + + V8SchemaRegistry* v8_schema_registry() { return v8_schema_registry_.get(); } + + ContentWatcher* content_watcher() { return content_watcher_.get(); } + + UserScriptSlave* user_script_slave() { return user_script_slave_.get(); } + + RequestSender* request_sender() { return request_sender_.get(); } + + bool IsExtensionActive(const std::string& extension_id) const; + + // Finds the extension ID for the JavaScript context associated with the + // specified |frame| and isolated world. If |world_id| is zero, finds the + // extension ID associated with the main world's JavaScript context. If the + // JavaScript context isn't from an extension, returns empty string. + std::string GetExtensionID(const blink::WebFrame* frame, int world_id); + + void DidCreateScriptContext(blink::WebFrame* frame, + const v8::Handle<v8::Context>& context, + int extension_group, + int world_id); + + void WillReleaseScriptContext(blink::WebFrame* frame, + const v8::Handle<v8::Context>& context, + int world_id); + + void DidCreateDocumentElement(blink::WebFrame* frame); + + void DidMatchCSS( + blink::WebFrame* frame, + const blink::WebVector<blink::WebString>& newly_matching_selectors, + const blink::WebVector<blink::WebString>& stopped_matching_selectors); + + void OnExtensionResponse(int request_id, + bool success, + const base::ListValue& response, + const std::string& error); + + // Checks that the current context contains an extension that has permission + // to execute the specified function. If it does not, a v8 exception is thrown + // and the method returns false. Otherwise returns true. + bool CheckContextAccessToExtensionAPI(const std::string& function_name, + ScriptContext* context) const; + + // Dispatches the event named |event_name| to all render views. + void DispatchEvent(const std::string& extension_id, + const std::string& event_name) const; + + // Shared implementation of the various MessageInvoke IPCs. + void InvokeModuleSystemMethod(content::RenderView* render_view, + const std::string& extension_id, + const std::string& module_name, + const std::string& function_name, + const base::ListValue& args, + bool user_gesture); + + void ClearPortData(int port_id); + + private: + friend class ::ChromeRenderViewTest; + FRIEND_TEST_ALL_PREFIXES(RendererPermissionsPolicyDelegateTest, + CannotScriptWebstore); + + // RenderProcessObserver implementation: + virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void WebKitInitialized() OVERRIDE; + virtual void IdleNotification() OVERRIDE; + virtual void OnRenderProcessShutdown() OVERRIDE; + + void OnActivateExtension(const std::string& extension_id); + void OnCancelSuspend(const std::string& extension_id); + void OnClearTabSpecificPermissions( + int tab_id, + const std::vector<std::string>& extension_ids); + void OnDeliverMessage(int target_port_id, const Message& message); + void OnDispatchOnConnect(int target_port_id, + const std::string& channel_name, + const base::DictionaryValue& source_tab, + const ExtensionMsg_ExternalConnectionInfo& info, + const std::string& tls_channel_id); + void OnDispatchOnDisconnect(int port_id, const std::string& error_message); + void OnLoaded( + const std::vector<ExtensionMsg_Loaded_Params>& loaded_extensions); + void OnLoadedInternal(scoped_refptr<const Extension> extension); + void OnMessageInvoke(const std::string& extension_id, + const std::string& module_name, + const std::string& function_name, + const base::ListValue& args, + bool user_gesture); + void OnSetChannel(int channel); + void OnSetFunctionNames(const std::vector<std::string>& names); + void OnSetScriptingWhitelist( + const ExtensionsClient::ScriptingWhitelist& extension_ids); + void OnSetSystemFont(const std::string& font_family, + const std::string& font_size); + void OnShouldSuspend(const std::string& extension_id, int sequence_id); + void OnSuspend(const std::string& extension_id); + void OnUnloaded(const std::string& id); + void OnUpdatePermissions(const ExtensionMsg_UpdatePermissions_Params& params); + void OnUpdateTabSpecificPermissions(int page_id, + int tab_id, + const std::string& extension_id, + const URLPatternSet& origin_set); + void OnUpdateUserScripts(base::SharedMemoryHandle scripts); + void OnUsingWebRequestAPI(bool adblock, + bool adblock_plus, + bool other_webrequest); + + void UpdateActiveExtensions(); + + // Sets up the host permissions for |extension|. + void InitOriginPermissions(const Extension* extension, + Feature::Context context_type); + void UpdateOriginPermissions(UpdatedExtensionPermissionsInfo::Reason reason, + const Extension* extension, + const URLPatternSet& origins); + + // Enable custom element whitelist in Apps. + void EnableCustomElementWhiteList(); + + // Adds or removes bindings for every context belonging to |extension_id|, or + // or all contexts if |extension_id| is empty. + void UpdateBindings(const std::string& extension_id); + + void UpdateBindingsForContext(ScriptContext* context); + + void RegisterBinding(const std::string& api_name, ScriptContext* context); + + void RegisterNativeHandlers(ModuleSystem* module_system, + ScriptContext* context); + + // Inserts static source code into |source_map_|. + void PopulateSourceMap(); + + // Returns whether the current renderer hosts a platform app. + bool IsWithinPlatformApp(); + + bool IsSandboxedPage(const GURL& url) const; + + // Returns the Feature::Context type of context for a JavaScript context. + Feature::Context ClassifyJavaScriptContext( + const Extension* extension, + int extension_group, + const GURL& url, + const blink::WebSecurityOrigin& origin); + + // Gets |field| from |object| or creates it as an empty object if it doesn't + // exist. + v8::Handle<v8::Object> GetOrCreateObject(const v8::Handle<v8::Object>& object, + const std::string& field, + v8::Isolate* isolate); + + v8::Handle<v8::Object> GetOrCreateBindObjectIfAvailable( + const std::string& api_name, + std::string* bind_name, + ScriptContext* context); + + // The delegate for this dispatcher. Not owned, but must extend beyond the + // Dispatcher's own lifetime. + DispatcherDelegate* delegate_; + + // True if this renderer is running extensions. + bool is_extension_process_; + + // Contains all loaded extensions. This is essentially the renderer + // counterpart to ExtensionService in the browser. It contains information + // about all extensions currently loaded by the browser. + ExtensionSet extensions_; + + // The IDs of extensions that failed to load, mapped to the error message + // generated on failure. + std::map<std::string, std::string> extension_load_errors_; + + // All the bindings contexts that are currently loaded for this renderer. + // There is zero or one for each v8 context. + ScriptContextSet script_context_set_; + + scoped_ptr<ContentWatcher> content_watcher_; + + scoped_ptr<UserScriptSlave> user_script_slave_; + + // Same as above, but on a longer timer and will run even if the process is + // not idle, to ensure that IdleHandle gets called eventually. + scoped_ptr<base::RepeatingTimer<content::RenderThread> > forced_idle_timer_; + + // All declared function names. + std::set<std::string> function_names_; + + // The extensions and apps that are active in this process. + std::set<std::string> active_extension_ids_; + + ResourceBundleSourceMap source_map_; + + // Cache for the v8 representation of extension API schemas. + scoped_ptr<V8SchemaRegistry> v8_schema_registry_; + + // Sends API requests to the extension host. + scoped_ptr<RequestSender> request_sender_; + + // The platforms system font family and size; + std::string system_font_family_; + std::string system_font_size_; + + // Mapping of port IDs to tabs. If there is no tab, the value would be -1. + std::map<int, int> port_to_tab_id_map_; + + // True once WebKit has been initialized (and it is therefore safe to poke). + bool is_webkit_initialized_; + + DISALLOW_COPY_AND_ASSIGN(Dispatcher); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_DISPATCHER_H_ diff --git a/extensions/renderer/dispatcher_delegate.h b/extensions/renderer/dispatcher_delegate.h new file mode 100644 index 0000000..13d557f --- /dev/null +++ b/extensions/renderer/dispatcher_delegate.h @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H +#define EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H + +#include <set> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "extensions/common/features/feature.h" +#include "v8/include/v8.h" + +namespace blink { +class WebFrame; +} + +namespace extensions { +class Dispatcher; +class Extension; +class ModuleSystem; +class ResourceBundleSourceMap; +class ScriptContext; +class URLPatternSet; + +// Base class and default implementation for an extensions::Dispacher delegate. +// DispatcherDelegate can be used to override and extend the behavior of the +// extensions system's renderer side. +class DispatcherDelegate { + public: + virtual ~DispatcherDelegate() {} + + // Creates a new ScriptContext for a given v8 context. + virtual scoped_ptr<ScriptContext> CreateScriptContext( + const v8::Handle<v8::Context>& v8_context, + blink::WebFrame* frame, + const Extension* extension, + Feature::Context context_type) = 0; + + // Initializes origin permissions for a newly created extension context. + virtual void InitOriginPermissions(const Extension* extension, + Feature::Context context_type) {} + + // Includes additional native handlers in a given ModuleSystem. + virtual void RegisterNativeHandlers(Dispatcher* dispatcher, + ModuleSystem* module_system, + ScriptContext* context) {} + + // Includes additional source resources into the resource map. + virtual void PopulateSourceMap(ResourceBundleSourceMap* source_map) {} + + // Requires additional modules within an extension context's module system. + virtual void RequireAdditionalModules(ModuleSystem* module_system, + const Extension* extension, + Feature::Context context_type, + bool is_within_platform_app) {} + + // Allows the delegate to respond to an updated set of active extensions in + // the Dispatcher. + virtual void OnActiveExtensionsUpdated( + const std::set<std::string>& extension_ids) {} + + // Sets the current Chrome channel. + // TODO(rockot): This doesn't belong in a generic extensions system interface. + // See http://crbug.com/368431. + virtual void SetChannel(int channel) {} + + // Clears extension permissions specific to a given tab. + // TODO(rockot): This doesn't belong in a generic extensions system interface. + // See http://crbug.com/368431. + virtual void ClearTabSpecificPermissions( + const extensions::Dispatcher* dispatcher, + int tab_id, + const std::vector<std::string>& extension_ids) {} + + // Updates extension permissions specific to a given tab. + // TODO(rockot): This doesn't belong in a generic extensions system interface. + // See http://crbug.com/368431. + virtual void UpdateTabSpecificPermissions( + const extensions::Dispatcher* dispatcher, + int page_id, + int tab_id, + const std::string& extension_id, + const extensions::URLPatternSet& origin_set) {} + + // Allows the delegate to respond to reports from the browser about WebRequest + // API usage from within this process. + virtual void HandleWebRequestAPIUsage(bool adblock, + bool adblock_plus, + bool other_webrequest) {} +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H diff --git a/extensions/renderer/event_bindings.cc b/extensions/renderer/event_bindings.cc index 9446595..0ebd08e 100644 --- a/extensions/renderer/event_bindings.cc +++ b/extensions/renderer/event_bindings.cc @@ -13,7 +13,6 @@ #include "base/bind.h" #include "base/lazy_instance.h" #include "base/memory/scoped_ptr.h" -#include "chrome/renderer/extensions/dispatcher.h" #include "chrome/renderer/extensions/extension_helper.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/render_view.h" @@ -23,6 +22,7 @@ #include "extensions/common/extension_messages.h" #include "extensions/common/manifest_handlers/background_info.h" #include "extensions/common/value_counter.h" +#include "extensions/renderer/dispatcher.h" #include "extensions/renderer/object_backed_native_handler.h" #include "url/gurl.h" #include "v8/include/v8.h" diff --git a/extensions/renderer/extensions_renderer_client.h b/extensions/renderer/extensions_renderer_client.h index 6b06b6c..ae4a31b 100644 --- a/extensions/renderer/extensions_renderer_client.h +++ b/extensions/renderer/extensions_renderer_client.h @@ -9,9 +9,6 @@ class ResourceBundleSourceMap; namespace extensions { -class ModuleSystem; -class ScriptContext; - // Interface to allow the extensions module to make render-process-specific // queries of the embedder. Should be Set() once in the render process. // @@ -30,13 +27,6 @@ class ExtensionsRendererClient { // (third_party/WebKit/public/web/WebFrame.h) for additional context. virtual int GetLowestIsolatedWorldId() const = 0; - // Registers additional native C++ code handlers for JS API functions. - virtual void RegisterNativeHandlers(ModuleSystem* module_system, - ScriptContext* context) = 0; - - // Registers additional JS source code resources for API implementations. - virtual void PopulateSourceMap(ResourceBundleSourceMap* source_map) = 0; - // Returns the single instance of |this|. static ExtensionsRendererClient* Get(); diff --git a/extensions/renderer/lazy_background_page_native_handler.cc b/extensions/renderer/lazy_background_page_native_handler.cc new file mode 100644 index 0000000..b7c72cd --- /dev/null +++ b/extensions/renderer/lazy_background_page_native_handler.cc @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/lazy_background_page_native_handler.h" + +#include "base/bind.h" +#include "chrome/renderer/extensions/extension_helper.h" +#include "content/public/renderer/render_view.h" +#include "extensions/common/extension_messages.h" +#include "extensions/common/manifest_handlers/background_info.h" +#include "extensions/renderer/script_context.h" + +namespace extensions { + +LazyBackgroundPageNativeHandler::LazyBackgroundPageNativeHandler( + ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction( + "IncrementKeepaliveCount", + base::Bind(&LazyBackgroundPageNativeHandler::IncrementKeepaliveCount, + base::Unretained(this))); + RouteFunction( + "DecrementKeepaliveCount", + base::Bind(&LazyBackgroundPageNativeHandler::DecrementKeepaliveCount, + base::Unretained(this))); +} + +void LazyBackgroundPageNativeHandler::IncrementKeepaliveCount( + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!context()) + return; + content::RenderView* render_view = context()->GetRenderView(); + if (IsContextLazyBackgroundPage(render_view, context()->extension())) { + render_view->Send(new ExtensionHostMsg_IncrementLazyKeepaliveCount( + render_view->GetRoutingID())); + } +} + +void LazyBackgroundPageNativeHandler::DecrementKeepaliveCount( + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!context()) + return; + content::RenderView* render_view = context()->GetRenderView(); + if (IsContextLazyBackgroundPage(render_view, context()->extension())) { + render_view->Send(new ExtensionHostMsg_DecrementLazyKeepaliveCount( + render_view->GetRoutingID())); + } +} + +bool LazyBackgroundPageNativeHandler::IsContextLazyBackgroundPage( + content::RenderView* render_view, + const Extension* extension) { + if (!render_view) + return false; + + ExtensionHelper* helper = ExtensionHelper::Get(render_view); + return (extension && BackgroundInfo::HasLazyBackgroundPage(extension) && + helper->view_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); +} + +} // namespace extensions diff --git a/extensions/renderer/lazy_background_page_native_handler.h b/extensions/renderer/lazy_background_page_native_handler.h new file mode 100644 index 0000000..5ee43b2 --- /dev/null +++ b/extensions/renderer/lazy_background_page_native_handler.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_ +#define EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_ + +#include "extensions/renderer/object_backed_native_handler.h" + +namespace content { +class RenderView; +} + +namespace extensions { + +class Extension; + +class LazyBackgroundPageNativeHandler : public ObjectBackedNativeHandler { + public: + explicit LazyBackgroundPageNativeHandler(ScriptContext* context); + void IncrementKeepaliveCount(const v8::FunctionCallbackInfo<v8::Value>& args); + void DecrementKeepaliveCount(const v8::FunctionCallbackInfo<v8::Value>& args); + + private: + bool IsContextLazyBackgroundPage(content::RenderView* render_view, + const Extension* extension); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_ diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc new file mode 100644 index 0000000..281f276 --- /dev/null +++ b/extensions/renderer/messaging_bindings.cc @@ -0,0 +1,441 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/messaging_bindings.h" + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/lazy_instance.h" +#include "base/message_loop/message_loop.h" +#include "base/values.h" +#include "content/public/renderer/render_thread.h" +#include "content/public/renderer/render_view.h" +#include "content/public/renderer/v8_value_converter.h" +#include "extensions/common/api/messaging/message.h" +#include "extensions/common/extension_messages.h" +#include "extensions/common/manifest_handlers/externally_connectable.h" +#include "extensions/renderer/dispatcher.h" +#include "extensions/renderer/event_bindings.h" +#include "extensions/renderer/object_backed_native_handler.h" +#include "extensions/renderer/scoped_persistent.h" +#include "extensions/renderer/script_context.h" +#include "extensions/renderer/script_context_set.h" +#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" +#include "third_party/WebKit/public/web/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h" +#include "third_party/WebKit/public/web/WebUserGestureIndicator.h" +#include "third_party/WebKit/public/web/WebUserGestureToken.h" +#include "v8/include/v8.h" + +// Message passing API example (in a content script): +// var extension = +// new chrome.Extension('00123456789abcdef0123456789abcdef0123456'); +// var port = runtime.connect(); +// port.postMessage('Can you hear me now?'); +// port.onmessage.addListener(function(msg, port) { +// alert('response=' + msg); +// port.postMessage('I got your reponse'); +// }); + +using content::RenderThread; +using content::V8ValueConverter; + +namespace extensions { + +namespace { + +struct ExtensionData { + struct PortData { + int ref_count; // how many contexts have a handle to this port + PortData() : ref_count(0) {} + }; + std::map<int, PortData> ports; // port ID -> data +}; + +base::LazyInstance<ExtensionData> g_extension_data = LAZY_INSTANCE_INITIALIZER; + +bool HasPortData(int port_id) { + return g_extension_data.Get().ports.find(port_id) != + g_extension_data.Get().ports.end(); +} + +ExtensionData::PortData& GetPortData(int port_id) { + return g_extension_data.Get().ports[port_id]; +} + +void ClearPortData(int port_id) { + g_extension_data.Get().ports.erase(port_id); +} + +const char kPortClosedError[] = "Attempting to use a disconnected port object"; +const char kReceivingEndDoesntExistError[] = + "Could not establish connection. Receiving end does not exist."; + +class ExtensionImpl : public ObjectBackedNativeHandler { + public: + ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context) + : ObjectBackedNativeHandler(context), dispatcher_(dispatcher) { + RouteFunction( + "CloseChannel", + base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this))); + RouteFunction( + "PortAddRef", + base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this))); + RouteFunction( + "PortRelease", + base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this))); + RouteFunction( + "PostMessage", + base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this))); + // TODO(fsamuel, kalman): Move BindToGC out of messaging natives. + RouteFunction("BindToGC", + base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this))); + } + + virtual ~ExtensionImpl() {} + + private: + void ClearPortDataAndNotifyDispatcher(int port_id) { + ClearPortData(port_id); + dispatcher_->ClearPortData(port_id); + } + + bool ShouldForwardUserGesture() { + return blink::WebUserGestureIndicator::isProcessingUserGesture() && + !blink::WebUserGestureIndicator::currentUserGestureToken() + .wasForwarded(); + } + + // Sends a message along the given channel. + void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) { + content::RenderView* renderview = context()->GetRenderView(); + if (!renderview) + return; + + // Arguments are (int32 port_id, string message). + CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString()); + + int port_id = args[0]->Int32Value(); + if (!HasPortData(port_id)) { + args.GetIsolate()->ThrowException(v8::Exception::Error( + v8::String::NewFromUtf8(args.GetIsolate(), kPortClosedError))); + return; + } + + renderview->Send(new ExtensionHostMsg_PostMessage( + renderview->GetRoutingID(), + port_id, + Message(*v8::String::Utf8Value(args[1]), ShouldForwardUserGesture()))); + } + + // Forcefully disconnects a port. + void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) { + // Arguments are (int32 port_id, boolean notify_browser). + CHECK_EQ(2, args.Length()); + CHECK(args[0]->IsInt32()); + CHECK(args[1]->IsBoolean()); + + int port_id = args[0]->Int32Value(); + if (!HasPortData(port_id)) + return; + + // Send via the RenderThread because the RenderView might be closing. + bool notify_browser = args[1]->BooleanValue(); + if (notify_browser) { + content::RenderThread::Get()->Send( + new ExtensionHostMsg_CloseChannel(port_id, std::string())); + } + + ClearPortDataAndNotifyDispatcher(port_id); + } + + // A new port has been created for a context. This occurs both when script + // opens a connection, and when a connection is opened to this script. + void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) { + // Arguments are (int32 port_id). + CHECK_EQ(1, args.Length()); + CHECK(args[0]->IsInt32()); + + int port_id = args[0]->Int32Value(); + ++GetPortData(port_id).ref_count; + } + + // The frame a port lived in has been destroyed. When there are no more + // frames with a reference to a given port, we will disconnect it and notify + // the other end of the channel. + void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) { + // Arguments are (int32 port_id). + CHECK_EQ(1, args.Length()); + CHECK(args[0]->IsInt32()); + + int port_id = args[0]->Int32Value(); + if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) { + // Send via the RenderThread because the RenderView might be closing. + content::RenderThread::Get()->Send( + new ExtensionHostMsg_CloseChannel(port_id, std::string())); + ClearPortDataAndNotifyDispatcher(port_id); + } + } + + // Holds a |callback| to run sometime after |object| is GC'ed. |callback| will + // not be executed re-entrantly to avoid running JS in an unexpected state. + class GCCallback { + public: + static void Bind(v8::Handle<v8::Object> object, + v8::Handle<v8::Function> callback, + v8::Isolate* isolate) { + GCCallback* cb = new GCCallback(object, callback, isolate); + cb->object_.SetWeak(cb, NearDeathCallback); + } + + private: + static void NearDeathCallback( + const v8::WeakCallbackData<v8::Object, GCCallback>& data) { + // v8 says we need to explicitly reset weak handles from their callbacks. + // It's not implicit as one might expect. + data.GetParameter()->object_.reset(); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&GCCallback::RunCallback, + base::Owned(data.GetParameter()))); + } + + GCCallback(v8::Handle<v8::Object> object, + v8::Handle<v8::Function> callback, + v8::Isolate* isolate) + : object_(object), callback_(callback), isolate_(isolate) {} + + void RunCallback() { + v8::HandleScope handle_scope(isolate_); + v8::Handle<v8::Function> callback = callback_.NewHandle(isolate_); + v8::Handle<v8::Context> context = callback->CreationContext(); + if (context.IsEmpty()) + return; + v8::Context::Scope context_scope(context); + blink::WebScopedMicrotaskSuppression suppression; + callback->Call(context->Global(), 0, NULL); + } + + ScopedPersistent<v8::Object> object_; + ScopedPersistent<v8::Function> callback_; + v8::Isolate* isolate_; + + DISALLOW_COPY_AND_ASSIGN(GCCallback); + }; + + // void BindToGC(object, callback) + // + // Binds |callback| to be invoked *sometime after* |object| is garbage + // collected. We don't call the method re-entrantly so as to avoid executing + // JS in some bizarro undefined mid-GC state. + void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) { + CHECK(args.Length() == 2 && args[0]->IsObject() && args[1]->IsFunction()); + GCCallback::Bind(args[0].As<v8::Object>(), + args[1].As<v8::Function>(), + args.GetIsolate()); + } + + // Dispatcher handle. Not owned. + Dispatcher* dispatcher_; +}; + +} // namespace + +ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher, + ScriptContext* context) { + return new ExtensionImpl(dispatcher, context); +} + +// static +void MessagingBindings::DispatchOnConnect( + const ScriptContextSet::ContextSet& contexts, + int target_port_id, + const std::string& channel_name, + const base::DictionaryValue& source_tab, + const std::string& source_extension_id, + const std::string& target_extension_id, + const GURL& source_url, + const std::string& tls_channel_id, + content::RenderView* restrict_to_render_view) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + + scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); + + bool port_created = false; + std::string source_url_spec = source_url.spec(); + + // TODO(kalman): pass in the full ScriptContextSet; call ForEach. + for (ScriptContextSet::ContextSet::const_iterator it = contexts.begin(); + it != contexts.end(); + ++it) { + if (restrict_to_render_view && + restrict_to_render_view != (*it)->GetRenderView()) { + continue; + } + + // TODO(kalman): remove when ContextSet::ForEach is available. + if ((*it)->v8_context().IsEmpty()) + continue; + + v8::Handle<v8::Value> tab = v8::Null(isolate); + v8::Handle<v8::Value> tls_channel_id_value = v8::Undefined(isolate); + const Extension* extension = (*it)->extension(); + if (extension) { + if (!source_tab.empty() && !extension->is_platform_app()) + tab = converter->ToV8Value(&source_tab, (*it)->v8_context()); + + ExternallyConnectableInfo* externally_connectable = + ExternallyConnectableInfo::Get(extension); + if (externally_connectable && + externally_connectable->accepts_tls_channel_id) { + tls_channel_id_value = + v8::String::NewFromUtf8(isolate, + tls_channel_id.c_str(), + v8::String::kNormalString, + tls_channel_id.size()); + } + } + + v8::Handle<v8::Value> arguments[] = { + // portId + v8::Integer::New(isolate, target_port_id), + // channelName + v8::String::NewFromUtf8(isolate, + channel_name.c_str(), + v8::String::kNormalString, + channel_name.size()), + // sourceTab + tab, + // sourceExtensionId + v8::String::NewFromUtf8(isolate, + source_extension_id.c_str(), + v8::String::kNormalString, + source_extension_id.size()), + // targetExtensionId + v8::String::NewFromUtf8(isolate, + target_extension_id.c_str(), + v8::String::kNormalString, + target_extension_id.size()), + // sourceUrl + v8::String::NewFromUtf8(isolate, + source_url_spec.c_str(), + v8::String::kNormalString, + source_url_spec.size()), + // tlsChannelId + tls_channel_id_value, + }; + + v8::Handle<v8::Value> retval = (*it)->module_system()->CallModuleMethod( + "messaging", "dispatchOnConnect", arraysize(arguments), arguments); + + if (retval.IsEmpty()) { + LOG(ERROR) << "Empty return value from dispatchOnConnect."; + continue; + } + + CHECK(retval->IsBoolean()); + port_created |= retval->BooleanValue(); + } + + // If we didn't create a port, notify the other end of the channel (treat it + // as a disconnect). + if (!port_created) { + content::RenderThread::Get()->Send(new ExtensionHostMsg_CloseChannel( + target_port_id, kReceivingEndDoesntExistError)); + } +} + +// static +void MessagingBindings::DeliverMessage( + const ScriptContextSet::ContextSet& contexts, + int target_port_id, + const Message& message, + content::RenderView* restrict_to_render_view) { + scoped_ptr<blink::WebScopedUserGesture> web_user_gesture; + scoped_ptr<blink::WebScopedWindowFocusAllowedIndicator> allow_window_focus; + if (message.user_gesture) { + web_user_gesture.reset(new blink::WebScopedUserGesture); + blink::WebUserGestureIndicator::currentUserGestureToken().setForwarded(); + allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator); + } + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + + // TODO(kalman): pass in the full ScriptContextSet; call ForEach. + for (ScriptContextSet::ContextSet::const_iterator it = contexts.begin(); + it != contexts.end(); + ++it) { + if (restrict_to_render_view && + restrict_to_render_view != (*it)->GetRenderView()) { + continue; + } + + // TODO(kalman): remove when ContextSet::ForEach is available. + if ((*it)->v8_context().IsEmpty()) + continue; + + // Check to see whether the context has this port before bothering to create + // the message. + v8::Handle<v8::Value> port_id_handle = + v8::Integer::New(isolate, target_port_id); + v8::Handle<v8::Value> has_port = (*it)->module_system()->CallModuleMethod( + "messaging", "hasPort", 1, &port_id_handle); + + CHECK(!has_port.IsEmpty()); + if (!has_port->BooleanValue()) + continue; + + std::vector<v8::Handle<v8::Value> > arguments; + arguments.push_back(v8::String::NewFromUtf8(isolate, + message.data.c_str(), + v8::String::kNormalString, + message.data.size())); + arguments.push_back(port_id_handle); + (*it)->module_system()->CallModuleMethod( + "messaging", "dispatchOnMessage", &arguments); + } +} + +// static +void MessagingBindings::DispatchOnDisconnect( + const ScriptContextSet::ContextSet& contexts, + int port_id, + const std::string& error_message, + content::RenderView* restrict_to_render_view) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + + // TODO(kalman): pass in the full ScriptContextSet; call ForEach. + for (ScriptContextSet::ContextSet::const_iterator it = contexts.begin(); + it != contexts.end(); + ++it) { + if (restrict_to_render_view && + restrict_to_render_view != (*it)->GetRenderView()) { + continue; + } + + // TODO(kalman): remove when ContextSet::ForEach is available. + if ((*it)->v8_context().IsEmpty()) + continue; + + std::vector<v8::Handle<v8::Value> > arguments; + arguments.push_back(v8::Integer::New(isolate, port_id)); + if (!error_message.empty()) { + arguments.push_back( + v8::String::NewFromUtf8(isolate, error_message.c_str())); + } else { + arguments.push_back(v8::Null(isolate)); + } + (*it)->module_system()->CallModuleMethod( + "messaging", "dispatchOnDisconnect", &arguments); + } +} + +} // namespace extensions diff --git a/extensions/renderer/messaging_bindings.h b/extensions/renderer/messaging_bindings.h new file mode 100644 index 0000000..721b434 --- /dev/null +++ b/extensions/renderer/messaging_bindings.h @@ -0,0 +1,71 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_ +#define EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_ + +#include <string> + +#include "extensions/renderer/script_context_set.h" + +namespace base { +class DictionaryValue; +} + +namespace content { +class RenderView; +} + +namespace v8 { +class Extension; +} + +namespace extensions { +class Dispatcher; +struct Message; +class ObjectBackedNativeHandler; + +// Manually implements JavaScript bindings for extension messaging. +// +// TODO(aa): This should all get re-implemented using SchemaGeneratedBindings. +// If anything needs to be manual for some reason, it should be implemented in +// its own class. +class MessagingBindings { + public: + // Creates an instance of the extension. + static ObjectBackedNativeHandler* Get(Dispatcher* dispatcher, + ScriptContext* context); + + // Dispatches the onConnect content script messaging event to some contexts + // in |contexts|. If |restrict_to_render_view| is specified, only contexts in + // that render view will receive the message. + static void DispatchOnConnect(const ScriptContextSet::ContextSet& contexts, + int target_port_id, + const std::string& channel_name, + const base::DictionaryValue& source_tab, + const std::string& source_extension_id, + const std::string& target_extension_id, + const GURL& source_url, + const std::string& tls_channel_id, + content::RenderView* restrict_to_render_view); + + // Delivers a message sent using content script messaging to some of the + // contexts in |bindings_context_set|. If |restrict_to_render_view| is + // specified, only contexts in that render view will receive the message. + static void DeliverMessage(const ScriptContextSet::ContextSet& context_set, + int target_port_id, + const Message& message, + content::RenderView* restrict_to_render_view); + + // Dispatches the onDisconnect event in response to the channel being closed. + static void DispatchOnDisconnect( + const ScriptContextSet::ContextSet& context_set, + int port_id, + const std::string& error_message, + content::RenderView* restrict_to_render_view); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_ diff --git a/extensions/renderer/print_native_handler.cc b/extensions/renderer/print_native_handler.cc new file mode 100644 index 0000000..1e83b0e --- /dev/null +++ b/extensions/renderer/print_native_handler.cc @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/print_native_handler.h" + +#include <string> + +#include "base/bind.h" +#include "base/strings/string_util.h" + +namespace extensions { + +PrintNativeHandler::PrintNativeHandler(ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction("Print", + base::Bind(&PrintNativeHandler::Print, base::Unretained(this))); +} + +void PrintNativeHandler::Print( + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (args.Length() < 1) + return; + + std::vector<std::string> components; + for (int i = 0; i < args.Length(); ++i) + components.push_back(*v8::String::Utf8Value(args[i]->ToString())); + + LOG(ERROR) << JoinString(components, ','); +} + +} // namespace extensions diff --git a/extensions/renderer/print_native_handler.h b/extensions/renderer/print_native_handler.h new file mode 100644 index 0000000..b2e5d37 --- /dev/null +++ b/extensions/renderer/print_native_handler.h @@ -0,0 +1,21 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_ +#define EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_ + +#include "extensions/renderer/object_backed_native_handler.h" + +namespace extensions { + +class PrintNativeHandler : public ObjectBackedNativeHandler { + public: + explicit PrintNativeHandler(ScriptContext* context); + + void Print(const v8::FunctionCallbackInfo<v8::Value>& args); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_ diff --git a/extensions/renderer/process_info_native_handler.cc b/extensions/renderer/process_info_native_handler.cc new file mode 100644 index 0000000..3c7ba78 --- /dev/null +++ b/extensions/renderer/process_info_native_handler.cc @@ -0,0 +1,86 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/process_info_native_handler.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "extensions/renderer/script_context.h" + +namespace extensions { + +ProcessInfoNativeHandler::ProcessInfoNativeHandler( + ScriptContext* context, + const std::string& extension_id, + const std::string& context_type, + bool is_incognito_context, + int manifest_version, + bool send_request_disabled) + : ObjectBackedNativeHandler(context), + extension_id_(extension_id), + context_type_(context_type), + is_incognito_context_(is_incognito_context), + manifest_version_(manifest_version), + send_request_disabled_(send_request_disabled) { + RouteFunction("GetExtensionId", + base::Bind(&ProcessInfoNativeHandler::GetExtensionId, + base::Unretained(this))); + RouteFunction("GetContextType", + base::Bind(&ProcessInfoNativeHandler::GetContextType, + base::Unretained(this))); + RouteFunction("InIncognitoContext", + base::Bind(&ProcessInfoNativeHandler::InIncognitoContext, + base::Unretained(this))); + RouteFunction("GetManifestVersion", + base::Bind(&ProcessInfoNativeHandler::GetManifestVersion, + base::Unretained(this))); + RouteFunction("IsSendRequestDisabled", + base::Bind(&ProcessInfoNativeHandler::IsSendRequestDisabled, + base::Unretained(this))); + RouteFunction( + "HasSwitch", + base::Bind(&ProcessInfoNativeHandler::HasSwitch, base::Unretained(this))); +} + +void ProcessInfoNativeHandler::GetExtensionId( + const v8::FunctionCallbackInfo<v8::Value>& args) { + args.GetReturnValue().Set( + v8::String::NewFromUtf8(args.GetIsolate(), extension_id_.c_str())); +} + +void ProcessInfoNativeHandler::GetContextType( + const v8::FunctionCallbackInfo<v8::Value>& args) { + args.GetReturnValue().Set( + v8::String::NewFromUtf8(args.GetIsolate(), context_type_.c_str())); +} + +void ProcessInfoNativeHandler::InIncognitoContext( + const v8::FunctionCallbackInfo<v8::Value>& args) { + args.GetReturnValue().Set(is_incognito_context_); +} + +void ProcessInfoNativeHandler::GetManifestVersion( + const v8::FunctionCallbackInfo<v8::Value>& args) { + args.GetReturnValue().Set(static_cast<int32_t>(manifest_version_)); +} + +void ProcessInfoNativeHandler::IsSendRequestDisabled( + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (send_request_disabled_) { + args.GetReturnValue().Set(v8::String::NewFromUtf8( + args.GetIsolate(), + "sendRequest and onRequest are obsolete." + " Please use sendMessage and onMessage instead.")); + } +} + +void ProcessInfoNativeHandler::HasSwitch( + const v8::FunctionCallbackInfo<v8::Value>& args) { + CHECK(args.Length() == 1 && args[0]->IsString()); + bool has_switch = CommandLine::ForCurrentProcess()->HasSwitch( + *v8::String::Utf8Value(args[0])); + args.GetReturnValue().Set(v8::Boolean::New(args.GetIsolate(), has_switch)); +} + +} // namespace extensions diff --git a/extensions/renderer/process_info_native_handler.h b/extensions/renderer/process_info_native_handler.h new file mode 100644 index 0000000..1f25269 --- /dev/null +++ b/extensions/renderer/process_info_native_handler.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_ +#define EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_ + +#include <string> + +#include "extensions/renderer/object_backed_native_handler.h" + +namespace extensions { + +class ProcessInfoNativeHandler : public ObjectBackedNativeHandler { + public: + ProcessInfoNativeHandler(ScriptContext* context, + const std::string& extension_id, + const std::string& context_type, + bool is_incognito_context, + int manifest_version, + bool send_request_disabled); + + private: + void GetExtensionId(const v8::FunctionCallbackInfo<v8::Value>& args); + void GetContextType(const v8::FunctionCallbackInfo<v8::Value>& args); + void InIncognitoContext(const v8::FunctionCallbackInfo<v8::Value>& args); + void GetManifestVersion(const v8::FunctionCallbackInfo<v8::Value>& args); + void IsSendRequestDisabled(const v8::FunctionCallbackInfo<v8::Value>& args); + void HasSwitch(const v8::FunctionCallbackInfo<v8::Value>& args); + + std::string extension_id_; + std::string context_type_; + bool is_incognito_context_; + int manifest_version_; + bool send_request_disabled_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_ diff --git a/extensions/renderer/request_sender.cc b/extensions/renderer/request_sender.cc index 09ffa8f..60c77c6 100644 --- a/extensions/renderer/request_sender.cc +++ b/extensions/renderer/request_sender.cc @@ -5,9 +5,9 @@ #include "extensions/renderer/request_sender.h" #include "base/values.h" -#include "chrome/renderer/extensions/dispatcher.h" #include "content/public/renderer/render_view.h" #include "extensions/common/extension_messages.h" +#include "extensions/renderer/dispatcher.h" #include "extensions/renderer/script_context.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebFrame.h" diff --git a/extensions/renderer/resource_bundle_source_map.cc b/extensions/renderer/resource_bundle_source_map.cc new file mode 100644 index 0000000..954a6d7 --- /dev/null +++ b/extensions/renderer/resource_bundle_source_map.cc @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/resource_bundle_source_map.h" + +#include "ui/base/resource/resource_bundle.h" + +namespace extensions { + +ResourceBundleSourceMap::ResourceBundleSourceMap( + const ui::ResourceBundle* resource_bundle) + : resource_bundle_(resource_bundle) { +} + +ResourceBundleSourceMap::~ResourceBundleSourceMap() { +} + +void ResourceBundleSourceMap::RegisterSource(const std::string& name, + int resource_id) { + resource_id_map_[name] = resource_id; +} + +v8::Handle<v8::Value> ResourceBundleSourceMap::GetSource( + v8::Isolate* isolate, + const std::string& name) { + if (!Contains(name)) + return v8::Undefined(isolate); + int resource_id = resource_id_map_[name]; + return ConvertString(isolate, + resource_bundle_->GetRawDataResource(resource_id)); +} + +bool ResourceBundleSourceMap::Contains(const std::string& name) { + return !!resource_id_map_.count(name); +} + +v8::Handle<v8::String> ResourceBundleSourceMap::ConvertString( + v8::Isolate* isolate, + const base::StringPiece& string) { + // v8 takes ownership of the StaticV8ExternalAsciiStringResource (see + // v8::String::NewExternal()). + return v8::String::NewExternal( + isolate, new StaticV8ExternalAsciiStringResource(string)); +} + +} // namespace extensions diff --git a/extensions/renderer/resource_bundle_source_map.h b/extensions/renderer/resource_bundle_source_map.h new file mode 100644 index 0000000..89e8b1d --- /dev/null +++ b/extensions/renderer/resource_bundle_source_map.h @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_ +#define EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_ + +#include <map> +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/linked_ptr.h" +#include "base/strings/string_piece.h" +#include "extensions/renderer/module_system.h" +#include "extensions/renderer/static_v8_external_ascii_string_resource.h" +#include "v8/include/v8.h" + +namespace ui { +class ResourceBundle; +} + +namespace extensions { + +class ResourceBundleSourceMap : public extensions::ModuleSystem::SourceMap { + public: + explicit ResourceBundleSourceMap(const ui::ResourceBundle* resource_bundle); + virtual ~ResourceBundleSourceMap(); + + virtual v8::Handle<v8::Value> GetSource(v8::Isolate* isolate, + const std::string& name) OVERRIDE; + virtual bool Contains(const std::string& name) OVERRIDE; + + void RegisterSource(const std::string& name, int resource_id); + + private: + v8::Handle<v8::String> ConvertString(v8::Isolate* isolate, + const base::StringPiece& string); + + const ui::ResourceBundle* resource_bundle_; + std::map<std::string, int> resource_id_map_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_ diff --git a/extensions/renderer/runtime_custom_bindings.cc b/extensions/renderer/runtime_custom_bindings.cc new file mode 100644 index 0000000..7ed2ad2 --- /dev/null +++ b/extensions/renderer/runtime_custom_bindings.cc @@ -0,0 +1,177 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/runtime_custom_bindings.h" + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/renderer/extensions/extension_helper.h" +#include "content/public/renderer/render_view.h" +#include "content/public/renderer/v8_value_converter.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_messages.h" +#include "extensions/common/features/feature.h" +#include "extensions/common/features/feature_provider.h" +#include "extensions/common/manifest.h" +#include "extensions/renderer/api_activity_logger.h" +#include "extensions/renderer/script_context.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebView.h" + +using content::V8ValueConverter; + +namespace extensions { + +RuntimeCustomBindings::RuntimeCustomBindings(ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction( + "GetManifest", + base::Bind(&RuntimeCustomBindings::GetManifest, base::Unretained(this))); + RouteFunction("OpenChannelToExtension", + base::Bind(&RuntimeCustomBindings::OpenChannelToExtension, + base::Unretained(this))); + RouteFunction("OpenChannelToNativeApp", + base::Bind(&RuntimeCustomBindings::OpenChannelToNativeApp, + base::Unretained(this))); + RouteFunction("GetExtensionViews", + base::Bind(&RuntimeCustomBindings::GetExtensionViews, + base::Unretained(this))); +} + +RuntimeCustomBindings::~RuntimeCustomBindings() { +} + +void RuntimeCustomBindings::OpenChannelToExtension( + const v8::FunctionCallbackInfo<v8::Value>& args) { + // Get the current RenderView so that we can send a routed IPC message from + // the correct source. + content::RenderView* renderview = context()->GetRenderView(); + if (!renderview) + return; + + // The Javascript code should validate/fill the arguments. + CHECK_EQ(args.Length(), 3); + CHECK(args[0]->IsString() && args[1]->IsString() && args[2]->IsBoolean()); + + ExtensionMsg_ExternalConnectionInfo info; + + // For messaging APIs, hosted apps should be considered a web page so hide + // its extension ID. + const Extension* extension = context()->extension(); + if (extension && !extension->is_hosted_app()) + info.source_id = extension->id(); + + info.target_id = *v8::String::Utf8Value(args[0]->ToString()); + info.source_url = context()->GetURL(); + std::string channel_name = *v8::String::Utf8Value(args[1]->ToString()); + bool include_tls_channel_id = + args.Length() > 2 ? args[2]->BooleanValue() : false; + int port_id = -1; + renderview->Send( + new ExtensionHostMsg_OpenChannelToExtension(renderview->GetRoutingID(), + info, + channel_name, + include_tls_channel_id, + &port_id)); + args.GetReturnValue().Set(static_cast<int32_t>(port_id)); +} + +void RuntimeCustomBindings::OpenChannelToNativeApp( + const v8::FunctionCallbackInfo<v8::Value>& args) { + // Verify that the extension has permission to use native messaging. + Feature::Availability availability = + FeatureProvider::GetPermissionFeatures() + ->GetFeature("nativeMessaging") + ->IsAvailableToContext(context()->extension(), + context()->context_type(), + context()->GetURL()); + if (!availability.is_available()) + return; + + // Get the current RenderView so that we can send a routed IPC message from + // the correct source. + content::RenderView* renderview = context()->GetRenderView(); + if (!renderview) + return; + + // The Javascript code should validate/fill the arguments. + CHECK(args.Length() >= 2 && args[0]->IsString() && args[1]->IsString()); + + std::string extension_id = *v8::String::Utf8Value(args[0]->ToString()); + std::string native_app_name = *v8::String::Utf8Value(args[1]->ToString()); + + int port_id = -1; + renderview->Send(new ExtensionHostMsg_OpenChannelToNativeApp( + renderview->GetRoutingID(), extension_id, native_app_name, &port_id)); + args.GetReturnValue().Set(static_cast<int32_t>(port_id)); +} + +void RuntimeCustomBindings::GetManifest( + const v8::FunctionCallbackInfo<v8::Value>& args) { + CHECK(context()->extension()); + + scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); + args.GetReturnValue().Set(converter->ToV8Value( + context()->extension()->manifest()->value(), context()->v8_context())); +} + +void RuntimeCustomBindings::GetExtensionViews( + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (args.Length() != 2) + return; + + if (!args[0]->IsInt32() || !args[1]->IsString()) + return; + + // |browser_window_id| == extension_misc::kUnknownWindowId means getting + // all views for the current extension. + int browser_window_id = args[0]->Int32Value(); + + std::string view_type_string = *v8::String::Utf8Value(args[1]->ToString()); + StringToUpperASCII(&view_type_string); + // |view_type| == VIEW_TYPE_INVALID means getting any type of + // views. + ViewType view_type = VIEW_TYPE_INVALID; + if (view_type_string == kViewTypeBackgroundPage) { + view_type = VIEW_TYPE_EXTENSION_BACKGROUND_PAGE; + } else if (view_type_string == kViewTypeInfobar) { + view_type = VIEW_TYPE_EXTENSION_INFOBAR; + } else if (view_type_string == kViewTypeTabContents) { + view_type = VIEW_TYPE_TAB_CONTENTS; + } else if (view_type_string == kViewTypePopup) { + view_type = VIEW_TYPE_EXTENSION_POPUP; + } else if (view_type_string == kViewTypeExtensionDialog) { + view_type = VIEW_TYPE_EXTENSION_DIALOG; + } else if (view_type_string == kViewTypeAppWindow) { + view_type = VIEW_TYPE_APP_WINDOW; + } else if (view_type_string == kViewTypePanel) { + view_type = VIEW_TYPE_PANEL; + } else if (view_type_string != kViewTypeAll) { + return; + } + + std::string extension_id = context()->GetExtensionID(); + if (extension_id.empty()) + return; + + std::vector<content::RenderView*> views = ExtensionHelper::GetExtensionViews( + extension_id, browser_window_id, view_type); + v8::Local<v8::Array> v8_views = v8::Array::New(args.GetIsolate()); + int v8_index = 0; + for (size_t i = 0; i < views.size(); ++i) { + v8::Local<v8::Context> context = + views[i]->GetWebView()->mainFrame()->mainWorldScriptContext(); + if (!context.IsEmpty()) { + v8::Local<v8::Value> window = context->Global(); + DCHECK(!window.IsEmpty()); + v8_views->Set(v8::Integer::New(args.GetIsolate(), v8_index++), window); + } + } + + args.GetReturnValue().Set(v8_views); +} + +} // namespace extensions diff --git a/extensions/renderer/runtime_custom_bindings.h b/extensions/renderer/runtime_custom_bindings.h new file mode 100644 index 0000000..70f4d03 --- /dev/null +++ b/extensions/renderer/runtime_custom_bindings.h @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_ +#define EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_ + +#include "base/compiler_specific.h" +#include "extensions/renderer/object_backed_native_handler.h" +#include "v8/include/v8.h" + +namespace extensions { + +// The native component of custom bindings for the chrome.runtime API. +class RuntimeCustomBindings : public ObjectBackedNativeHandler { + public: + explicit RuntimeCustomBindings(ScriptContext* context); + + virtual ~RuntimeCustomBindings(); + + // Creates a new messaging channel to the given extension. + void OpenChannelToExtension(const v8::FunctionCallbackInfo<v8::Value>& args); + + // Creates a new messaging channels for the specified native application. + void OpenChannelToNativeApp(const v8::FunctionCallbackInfo<v8::Value>& args); + + private: + void GetManifest(const v8::FunctionCallbackInfo<v8::Value>& args); + void GetExtensionViews(const v8::FunctionCallbackInfo<v8::Value>& args); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_ diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc index 77f72aa..33a9413 100644 --- a/extensions/renderer/script_context.cc +++ b/extensions/renderer/script_context.cc @@ -24,7 +24,7 @@ using content::V8ValueConverter; namespace extensions { -ScriptContext::ScriptContext(v8::Handle<v8::Context> v8_context, +ScriptContext::ScriptContext(const v8::Handle<v8::Context>& v8_context, blink::WebFrame* web_frame, const Extension* extension, Feature::Context context_type) diff --git a/extensions/renderer/script_context.h b/extensions/renderer/script_context.h index c2cbb04..5cc137e 100644 --- a/extensions/renderer/script_context.h +++ b/extensions/renderer/script_context.h @@ -30,7 +30,7 @@ class Extension; // Extensions wrapper for a v8 context. class ScriptContext : public RequestSender::Source { public: - ScriptContext(v8::Handle<v8::Context> context, + ScriptContext(const v8::Handle<v8::Context>& context, blink::WebFrame* frame, const Extension* extension, Feature::Context context_type); diff --git a/extensions/renderer/static_v8_external_ascii_string_resource.cc b/extensions/renderer/static_v8_external_ascii_string_resource.cc new file mode 100644 index 0000000..5d64d2e --- /dev/null +++ b/extensions/renderer/static_v8_external_ascii_string_resource.cc @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/static_v8_external_ascii_string_resource.h" + +namespace extensions { + +StaticV8ExternalAsciiStringResource::StaticV8ExternalAsciiStringResource( + const base::StringPiece& buffer) + : buffer_(buffer) { +} + +StaticV8ExternalAsciiStringResource::~StaticV8ExternalAsciiStringResource() { +} + +const char* StaticV8ExternalAsciiStringResource::data() const { + return buffer_.data(); +} + +size_t StaticV8ExternalAsciiStringResource::length() const { + return buffer_.length(); +} + +} // namespace extensions diff --git a/extensions/renderer/static_v8_external_ascii_string_resource.h b/extensions/renderer/static_v8_external_ascii_string_resource.h new file mode 100644 index 0000000..da02650 --- /dev/null +++ b/extensions/renderer/static_v8_external_ascii_string_resource.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ASCII_STRING_RESOURCE_H_ +#define EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ASCII_STRING_RESOURCE_H_ + +#include "base/compiler_specific.h" +#include "base/strings/string_piece.h" +#include "v8/include/v8.h" + +namespace extensions { + +// A very simple implementation of v8::ExternalAsciiStringResource that just +// wraps a buffer. The buffer must outlive the v8 runtime instance this resource +// is used in. +class StaticV8ExternalAsciiStringResource + : public v8::String::ExternalAsciiStringResource { + public: + explicit StaticV8ExternalAsciiStringResource(const base::StringPiece& buffer); + virtual ~StaticV8ExternalAsciiStringResource(); + + virtual const char* data() const OVERRIDE; + virtual size_t length() const OVERRIDE; + + private: + base::StringPiece buffer_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ASCII_STRING_RESOURCE_H_ diff --git a/extensions/renderer/test_extensions_renderer_client.cc b/extensions/renderer/test_extensions_renderer_client.cc index 4d1c021..5299878 100644 --- a/extensions/renderer/test_extensions_renderer_client.cc +++ b/extensions/renderer/test_extensions_renderer_client.cc @@ -19,13 +19,4 @@ int TestExtensionsRendererClient::GetLowestIsolatedWorldId() const { return 1; } -void TestExtensionsRendererClient::RegisterNativeHandlers( - extensions::ModuleSystem* module_system, - extensions::ScriptContext* context) { -} - -void TestExtensionsRendererClient::PopulateSourceMap( - ResourceBundleSourceMap* source_map) { -} - } // namespace extensions diff --git a/extensions/renderer/test_extensions_renderer_client.h b/extensions/renderer/test_extensions_renderer_client.h index a71d1bb..72550a9 100644 --- a/extensions/renderer/test_extensions_renderer_client.h +++ b/extensions/renderer/test_extensions_renderer_client.h @@ -18,9 +18,6 @@ class TestExtensionsRendererClient : public ExtensionsRendererClient { // ExtensionsRendererClient implementation. virtual bool IsIncognitoProcess() const OVERRIDE; virtual int GetLowestIsolatedWorldId() const OVERRIDE; - virtual void RegisterNativeHandlers(ModuleSystem* module_system, - ScriptContext* context) OVERRIDE; - virtual void PopulateSourceMap(ResourceBundleSourceMap* source_map) OVERRIDE; private: DISALLOW_COPY_AND_ASSIGN(TestExtensionsRendererClient); diff --git a/extensions/renderer/test_features_native_handler.cc b/extensions/renderer/test_features_native_handler.cc new file mode 100644 index 0000000..6e0423d --- /dev/null +++ b/extensions/renderer/test_features_native_handler.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/test_features_native_handler.h" + +#include "base/bind.h" +#include "content/public/renderer/v8_value_converter.h" +#include "extensions/common/features/json_feature_provider_source.h" +#include "extensions/renderer/script_context.h" +#include "grit/common_resources.h" +#include "grit/extensions_resources.h" + +namespace extensions { + +TestFeaturesNativeHandler::TestFeaturesNativeHandler(ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction("GetAPIFeatures", + base::Bind(&TestFeaturesNativeHandler::GetAPIFeatures, + base::Unretained(this))); +} + +void TestFeaturesNativeHandler::GetAPIFeatures( + const v8::FunctionCallbackInfo<v8::Value>& args) { + JSONFeatureProviderSource source("api"); + // TODO(rockot): Only inlcude extensions features here. Chrome should add + // its own native handler for Chrome features. + source.LoadJSON(IDR_CHROME_EXTENSION_API_FEATURES); + source.LoadJSON(IDR_EXTENSION_API_FEATURES); + scoped_ptr<content::V8ValueConverter> converter( + content::V8ValueConverter::create()); + args.GetReturnValue().Set( + converter->ToV8Value(&source.dictionary(), context()->v8_context())); +} + +} // namespace extensions diff --git a/extensions/renderer/test_features_native_handler.h b/extensions/renderer/test_features_native_handler.h new file mode 100644 index 0000000..2422178 --- /dev/null +++ b/extensions/renderer/test_features_native_handler.h @@ -0,0 +1,22 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_ +#define EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_ + +#include "extensions/renderer/object_backed_native_handler.h" + +namespace extensions { + +class TestFeaturesNativeHandler : public ObjectBackedNativeHandler { + public: + explicit TestFeaturesNativeHandler(ScriptContext* context); + + private: + void GetAPIFeatures(const v8::FunctionCallbackInfo<v8::Value>& args); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_ diff --git a/extensions/renderer/user_gestures_native_handler.cc b/extensions/renderer/user_gestures_native_handler.cc new file mode 100644 index 0000000..9875ac0 --- /dev/null +++ b/extensions/renderer/user_gestures_native_handler.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/user_gestures_native_handler.h" + +#include "base/bind.h" +#include "extensions/renderer/script_context.h" +#include "third_party/WebKit/public/web/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebUserGestureIndicator.h" + +namespace extensions { + +UserGesturesNativeHandler::UserGesturesNativeHandler(ScriptContext* context) + : ObjectBackedNativeHandler(context) { + RouteFunction("IsProcessingUserGesture", + base::Bind(&UserGesturesNativeHandler::IsProcessingUserGesture, + base::Unretained(this))); + RouteFunction("RunWithUserGesture", + base::Bind(&UserGesturesNativeHandler::RunWithUserGesture, + base::Unretained(this))); + RouteFunction("RunWithoutUserGesture", + base::Bind(&UserGesturesNativeHandler::RunWithoutUserGesture, + base::Unretained(this))); +} + +void UserGesturesNativeHandler::IsProcessingUserGesture( + const v8::FunctionCallbackInfo<v8::Value>& args) { + args.GetReturnValue().Set(v8::Boolean::New( + args.GetIsolate(), + blink::WebUserGestureIndicator::isProcessingUserGesture())); +} + +void UserGesturesNativeHandler::RunWithUserGesture( + const v8::FunctionCallbackInfo<v8::Value>& args) { + blink::WebScopedUserGesture user_gesture; + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsFunction()); + v8::Handle<v8::Value> no_args; + context()->CallFunction(v8::Handle<v8::Function>::Cast(args[0]), 0, &no_args); +} + +void UserGesturesNativeHandler::RunWithoutUserGesture( + const v8::FunctionCallbackInfo<v8::Value>& args) { + blink::WebUserGestureIndicator::consumeUserGesture(); + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsFunction()); + v8::Handle<v8::Value> no_args; + context()->CallFunction(v8::Handle<v8::Function>::Cast(args[0]), 0, &no_args); +} + +} // namespace extensions diff --git a/extensions/renderer/user_gestures_native_handler.h b/extensions/renderer/user_gestures_native_handler.h new file mode 100644 index 0000000..fbe15fb --- /dev/null +++ b/extensions/renderer/user_gestures_native_handler.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_ +#define EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_ + +#include "extensions/renderer/object_backed_native_handler.h" + +namespace extensions { + +class UserGesturesNativeHandler : public ObjectBackedNativeHandler { + public: + explicit UserGesturesNativeHandler(ScriptContext* context); + + private: + void IsProcessingUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args); + void RunWithUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args); + void RunWithoutUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_ diff --git a/extensions/renderer/user_script_slave.cc b/extensions/renderer/user_script_slave.cc new file mode 100644 index 0000000..64cd3f6 --- /dev/null +++ b/extensions/renderer/user_script_slave.cc @@ -0,0 +1,314 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/user_script_slave.h" + +#include <map> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/shared_memory.h" +#include "base/metrics/histogram.h" +#include "base/pickle.h" +#include "base/strings/stringprintf.h" +#include "base/timer/elapsed_timer.h" +#include "content/public/common/url_constants.h" +#include "content/public/renderer/render_thread.h" +#include "content/public/renderer/render_view.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_messages.h" +#include "extensions/common/extension_set.h" +#include "extensions/common/manifest_handlers/csp_info.h" +#include "extensions/common/permissions/permissions_data.h" +#include "extensions/renderer/dom_activity_logger.h" +#include "extensions/renderer/extension_groups.h" +#include "extensions/renderer/extensions_renderer_client.h" +#include "extensions/renderer/script_context.h" +#include "grit/renderer_resources.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/platform/WebVector.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebSecurityOrigin.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "ui/base/resource/resource_bundle.h" +#include "url/gurl.h" + +using blink::WebFrame; +using blink::WebSecurityOrigin; +using blink::WebSecurityPolicy; +using blink::WebString; +using blink::WebVector; +using blink::WebView; +using content::RenderThread; + +namespace extensions { + +// These two strings are injected before and after the Greasemonkey API and +// user script to wrap it in an anonymous scope. +static const char kUserScriptHead[] = "(function (unsafeWindow) {\n"; +static const char kUserScriptTail[] = "\n})(window);"; + +int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension, + WebFrame* frame) { + static int g_next_isolated_world_id = + ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId(); + + IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id()); + if (iter != isolated_world_ids_.end()) { + // We need to set the isolated world origin and CSP even if it's not a new + // world since these are stored per frame, and we might not have used this + // isolated world in this frame before. + frame->setIsolatedWorldSecurityOrigin( + iter->second, WebSecurityOrigin::create(extension->url())); + frame->setIsolatedWorldContentSecurityPolicy( + iter->second, + WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension))); + return iter->second; + } + + int new_id = g_next_isolated_world_id; + ++g_next_isolated_world_id; + + // This map will tend to pile up over time, but realistically, you're never + // going to have enough extensions for it to matter. + isolated_world_ids_[extension->id()] = new_id; + frame->setIsolatedWorldSecurityOrigin( + new_id, WebSecurityOrigin::create(extension->url())); + frame->setIsolatedWorldContentSecurityPolicy( + new_id, + WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension))); + return new_id; +} + +std::string UserScriptSlave::GetExtensionIdForIsolatedWorld( + int isolated_world_id) { + for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin(); + iter != isolated_world_ids_.end(); + ++iter) { + if (iter->second == isolated_world_id) + return iter->first; + } + return std::string(); +} + +void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) { + isolated_world_ids_.erase(extension_id); +} + +UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions) + : script_deleter_(&scripts_), extensions_(extensions) { + api_js_ = ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_GREASEMONKEY_API_JS); +} + +UserScriptSlave::~UserScriptSlave() { +} + +void UserScriptSlave::GetActiveExtensions( + std::set<std::string>* extension_ids) { + for (size_t i = 0; i < scripts_.size(); ++i) { + DCHECK(!scripts_[i]->extension_id().empty()); + extension_ids->insert(scripts_[i]->extension_id()); + } +} + +bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { + scripts_.clear(); + + bool only_inject_incognito = + ExtensionsRendererClient::Get()->IsIncognitoProcess(); + + // Create the shared memory object (read only). + shared_memory_.reset(new base::SharedMemory(shared_memory, true)); + if (!shared_memory_.get()) + return false; + + // First get the size of the memory block. + if (!shared_memory_->Map(sizeof(Pickle::Header))) + return false; + Pickle::Header* pickle_header = + reinterpret_cast<Pickle::Header*>(shared_memory_->memory()); + + // Now map in the rest of the block. + int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size; + shared_memory_->Unmap(); + if (!shared_memory_->Map(pickle_size)) + return false; + + // Unpickle scripts. + uint64 num_scripts = 0; + Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size); + PickleIterator iter(pickle); + CHECK(pickle.ReadUInt64(&iter, &num_scripts)); + + scripts_.reserve(num_scripts); + for (uint64 i = 0; i < num_scripts; ++i) { + scripts_.push_back(new UserScript()); + UserScript* script = scripts_.back(); + script->Unpickle(pickle, &iter); + + // Note that this is a pointer into shared memory. We don't own it. It gets + // cleared up when the last renderer or browser process drops their + // reference to the shared memory. + for (size_t j = 0; j < script->js_scripts().size(); ++j) { + const char* body = NULL; + int body_length = 0; + CHECK(pickle.ReadData(&iter, &body, &body_length)); + script->js_scripts()[j].set_external_content( + base::StringPiece(body, body_length)); + } + for (size_t j = 0; j < script->css_scripts().size(); ++j) { + const char* body = NULL; + int body_length = 0; + CHECK(pickle.ReadData(&iter, &body, &body_length)); + script->css_scripts()[j].set_external_content( + base::StringPiece(body, body_length)); + } + + if (only_inject_incognito && !script->is_incognito_enabled()) { + // This script shouldn't run in an incognito tab. + delete script; + scripts_.pop_back(); + } + } + + return true; +} + +void UserScriptSlave::InjectScripts(WebFrame* frame, + UserScript::RunLocation location) { + GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame); + if (data_source_url.is_empty()) + return; + + if (frame->isViewSourceModeEnabled()) + data_source_url = GURL(content::kViewSourceScheme + std::string(":") + + data_source_url.spec()); + + base::ElapsedTimer timer; + int num_css = 0; + int num_scripts = 0; + + ExecutingScriptsMap extensions_executing_scripts; + + for (size_t i = 0; i < scripts_.size(); ++i) { + std::vector<WebScriptSource> sources; + UserScript* script = scripts_[i]; + + if (frame->parent() && !script->match_all_frames()) + continue; // Only match subframes if the script declared it wanted to. + + const Extension* extension = extensions_->GetByID(script->extension_id()); + + // Since extension info is sent separately from user script info, they can + // be out of sync. We just ignore this situation. + if (!extension) + continue; + + // Content scripts are not tab-specific. + const int kNoTabId = -1; + // We don't have a process id in this context. + const int kNoProcessId = -1; + if (!PermissionsData::CanExecuteScriptOnPage(extension, + data_source_url, + frame->top()->document().url(), + kNoTabId, + script, + kNoProcessId, + NULL)) { + continue; + } + + if (location == UserScript::DOCUMENT_START) { + num_css += script->css_scripts().size(); + for (UserScript::FileList::const_iterator iter = + script->css_scripts().begin(); + iter != script->css_scripts().end(); + ++iter) { + frame->document().insertStyleSheet( + WebString::fromUTF8(iter->GetContent().as_string())); + } + } + + if (script->run_location() == location) { + num_scripts += script->js_scripts().size(); + for (size_t j = 0; j < script->js_scripts().size(); ++j) { + UserScript::File& file = script->js_scripts()[j]; + std::string content = file.GetContent().as_string(); + + // We add this dumb function wrapper for standalone user script to + // emulate what Greasemonkey does. + // TODO(aa): I think that maybe "is_standalone" scripts don't exist + // anymore. Investigate. + if (script->is_standalone() || script->emulate_greasemonkey()) { + content.insert(0, kUserScriptHead); + content += kUserScriptTail; + } + sources.push_back( + WebScriptSource(WebString::fromUTF8(content), file.url())); + } + } + + if (!sources.empty()) { + // Emulate Greasemonkey API for scripts that were converted to extensions + // and "standalone" user scripts. + if (script->is_standalone() || script->emulate_greasemonkey()) { + sources.insert( + sources.begin(), + WebScriptSource(WebString::fromUTF8(api_js_.as_string()))); + } + + int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame); + + base::ElapsedTimer exec_timer; + DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id()); + frame->executeScriptInIsolatedWorld(isolated_world_id, + &sources.front(), + sources.size(), + EXTENSION_GROUP_CONTENT_SCRIPTS); + UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed()); + + for (std::vector<WebScriptSource>::const_iterator iter = sources.begin(); + iter != sources.end(); + ++iter) { + extensions_executing_scripts[extension->id()].insert( + GURL(iter->url).path()); + } + } + } + + // Notify the browser if any extensions are now executing scripts. + if (!extensions_executing_scripts.empty()) { + blink::WebFrame* top_frame = frame->top(); + content::RenderView* render_view = + content::RenderView::FromWebView(top_frame->view()); + render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( + render_view->GetRoutingID(), + extensions_executing_scripts, + render_view->GetPageId(), + ScriptContext::GetDataSourceURLForFrame(top_frame))); + } + + // Log debug info. + if (location == UserScript::DOCUMENT_START) { + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css); + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_scripts); + if (num_css || num_scripts) + UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed()); + } else if (location == UserScript::DOCUMENT_END) { + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_scripts); + if (num_scripts) + UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed()); + } else if (location == UserScript::DOCUMENT_IDLE) { + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_scripts); + if (num_scripts) + UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed()); + } else { + NOTREACHED(); + } +} + +} // namespace extensions diff --git a/extensions/renderer/user_script_slave.h b/extensions/renderer/user_script_slave.h new file mode 100644 index 0000000..fc3da79 --- /dev/null +++ b/extensions/renderer/user_script_slave.h @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_USER_SCRIPT_SLAVE_H_ +#define EXTENSIONS_RENDERER_USER_SCRIPT_SLAVE_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory.h" +#include "base/stl_util.h" +#include "base/strings/string_piece.h" +#include "extensions/common/user_script.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" + +class GURL; + +namespace blink { +class WebFrame; +} + +using blink::WebScriptSource; + +namespace extensions { +class Extension; +class ExtensionSet; + +// Manages installed UserScripts for a render process. +class UserScriptSlave { + public: + explicit UserScriptSlave(const ExtensionSet* extensions); + ~UserScriptSlave(); + + // Returns the unique set of extension IDs this UserScriptSlave knows about. + void GetActiveExtensions(std::set<std::string>* extension_ids); + + // Update the parsed scripts from shared memory. + bool UpdateScripts(base::SharedMemoryHandle shared_memory); + + // Inject the appropriate scripts into a frame based on its URL. + // TODO(aa): Extract a UserScriptFrame interface out of this to improve + // testability. + void InjectScripts(blink::WebFrame* frame, UserScript::RunLocation location); + + // Gets the isolated world ID to use for the given |extension| in the given + // |frame|. If no isolated world has been created for that extension, + // one will be created and initialized. + int GetIsolatedWorldIdForExtension(const Extension* extension, + blink::WebFrame* frame); + + // Gets the id of the extension running in a given isolated world. If no such + // isolated world exists, or no extension is running in it, returns empty + // string. + std::string GetExtensionIdForIsolatedWorld(int isolated_world_id); + + void RemoveIsolatedWorld(const std::string& extension_id); + + private: + // Shared memory containing raw script data. + scoped_ptr<base::SharedMemory> shared_memory_; + + // Parsed script data. + std::vector<UserScript*> scripts_; + STLElementDeleter<std::vector<UserScript*> > script_deleter_; + + // Greasemonkey API source that is injected with the scripts. + base::StringPiece api_js_; + + // Extension metadata. + const ExtensionSet* extensions_; + + typedef std::map<std::string, int> IsolatedWorldMap; + IsolatedWorldMap isolated_world_ids_; + + DISALLOW_COPY_AND_ASSIGN(UserScriptSlave); +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_USER_SCRIPT_SLAVE_H_ diff --git a/extensions/renderer/v8_context_native_handler.cc b/extensions/renderer/v8_context_native_handler.cc new file mode 100644 index 0000000..56cd973 --- /dev/null +++ b/extensions/renderer/v8_context_native_handler.cc @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/v8_context_native_handler.h" + +#include "base/bind.h" +#include "extensions/common/features/feature.h" +#include "extensions/renderer/dispatcher.h" +#include "extensions/renderer/script_context.h" + +namespace extensions { + +V8ContextNativeHandler::V8ContextNativeHandler(ScriptContext* context, + Dispatcher* dispatcher) + : ObjectBackedNativeHandler(context), + context_(context), + dispatcher_(dispatcher) { + RouteFunction("GetAvailability", + base::Bind(&V8ContextNativeHandler::GetAvailability, + base::Unretained(this))); + RouteFunction("GetModuleSystem", + base::Bind(&V8ContextNativeHandler::GetModuleSystem, + base::Unretained(this))); +} + +void V8ContextNativeHandler::GetAvailability( + const v8::FunctionCallbackInfo<v8::Value>& args) { + CHECK_EQ(args.Length(), 1); + v8::Isolate* isolate = args.GetIsolate(); + std::string api_name = *v8::String::Utf8Value(args[0]->ToString()); + Feature::Availability availability = context_->GetAvailability(api_name); + + v8::Handle<v8::Object> ret = v8::Object::New(isolate); + ret->Set(v8::String::NewFromUtf8(isolate, "is_available"), + v8::Boolean::New(isolate, availability.is_available())); + ret->Set(v8::String::NewFromUtf8(isolate, "message"), + v8::String::NewFromUtf8(isolate, availability.message().c_str())); + ret->Set(v8::String::NewFromUtf8(isolate, "result"), + v8::Integer::New(isolate, availability.result())); + args.GetReturnValue().Set(ret); +} + +void V8ContextNativeHandler::GetModuleSystem( + const v8::FunctionCallbackInfo<v8::Value>& args) { + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsObject()); + v8::Handle<v8::Context> v8_context = + v8::Handle<v8::Object>::Cast(args[0])->CreationContext(); + ScriptContext* context = + dispatcher_->script_context_set().GetByV8Context(v8_context); + args.GetReturnValue().Set(context->module_system()->NewInstance()); +} + +} // namespace extensions diff --git a/extensions/renderer/v8_context_native_handler.h b/extensions/renderer/v8_context_native_handler.h new file mode 100644 index 0000000..a3a6163 --- /dev/null +++ b/extensions/renderer/v8_context_native_handler.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_ +#define EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_ + +#include "extensions/renderer/object_backed_native_handler.h" + +namespace extensions { + +class Dispatcher; + +class V8ContextNativeHandler : public ObjectBackedNativeHandler { + public: + V8ContextNativeHandler(ScriptContext* context, Dispatcher* dispatcher); + + private: + void GetAvailability(const v8::FunctionCallbackInfo<v8::Value>& args); + void GetModuleSystem(const v8::FunctionCallbackInfo<v8::Value>& args); + + ScriptContext* context_; + Dispatcher* dispatcher_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_ |