hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 1 | // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 5 | #include "extensions/browser/extension_user_script_loader.h" |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 6 | |
avi | c9cec10 | 2015-12-23 00:39:26 | [diff] [blame] | 7 | #include <stddef.h> |
| 8 | |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 9 | #include <set> |
| 10 | #include <string> |
| 11 | |
| 12 | #include "base/bind.h" |
| 13 | #include "base/bind_helpers.h" |
| 14 | #include "base/files/file_path.h" |
| 15 | #include "base/files/file_util.h" |
lazyboy | 28acad8 | 2016-06-22 23:14:20 | [diff] [blame] | 16 | #include "base/strings/string_util.h" |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 17 | #include "base/version.h" |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 18 | #include "content/public/browser/browser_context.h" |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 19 | #include "content/public/browser/browser_thread.h" |
| 20 | #include "content/public/browser/notification_service.h" |
| 21 | #include "content/public/browser/render_process_host.h" |
| 22 | #include "extensions/browser/component_extension_resource_manager.h" |
| 23 | #include "extensions/browser/content_verifier.h" |
| 24 | #include "extensions/browser/extension_registry.h" |
| 25 | #include "extensions/browser/extension_system.h" |
| 26 | #include "extensions/browser/extensions_browser_client.h" |
| 27 | #include "extensions/common/file_util.h" |
| 28 | #include "extensions/common/manifest_handlers/default_locale_handler.h" |
| 29 | #include "extensions/common/message_bundle.h" |
| 30 | #include "extensions/common/one_shot_event.h" |
| 31 | #include "ui/base/resource/resource_bundle.h" |
| 32 | |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 33 | using content::BrowserContext; |
| 34 | |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 35 | namespace extensions { |
| 36 | |
| 37 | namespace { |
| 38 | |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 39 | using SubstitutionMap = std::map<std::string, std::string>; |
| 40 | |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 41 | // Verifies file contents as they are read. |
| 42 | void VerifyContent(const scoped_refptr<ContentVerifier>& verifier, |
| 43 | const std::string& extension_id, |
| 44 | const base::FilePath& extension_root, |
| 45 | const base::FilePath& relative_path, |
| 46 | const std::string& content) { |
| 47 | DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| 48 | scoped_refptr<ContentVerifyJob> job( |
| 49 | verifier->CreateJobFor(extension_id, extension_root, relative_path)); |
| 50 | if (job.get()) { |
| 51 | job->Start(); |
| 52 | job->BytesRead(content.size(), content.data()); |
| 53 | job->DoneReading(); |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | // Loads user scripts from the extension who owns these scripts. |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 58 | bool LoadScriptContent(const HostID& host_id, |
| 59 | UserScript::File* script_file, |
| 60 | const SubstitutionMap* localization_messages, |
| 61 | const scoped_refptr<ContentVerifier>& verifier) { |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 62 | DCHECK(script_file); |
| 63 | std::string content; |
| 64 | const base::FilePath& path = ExtensionResource::GetFilePath( |
| 65 | script_file->extension_root(), script_file->relative_path(), |
| 66 | ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT); |
| 67 | if (path.empty()) { |
| 68 | int resource_id = 0; |
| 69 | if (ExtensionsBrowserClient::Get() |
| 70 | ->GetComponentExtensionResourceManager() |
| 71 | ->IsComponentExtensionResource(script_file->extension_root(), |
| 72 | script_file->relative_path(), |
| 73 | &resource_id)) { |
| 74 | const ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 75 | content = rb.GetRawDataResource(resource_id).as_string(); |
| 76 | } else { |
| 77 | LOG(WARNING) << "Failed to get file path to " |
| 78 | << script_file->relative_path().value() << " from " |
| 79 | << script_file->extension_root().value(); |
| 80 | return false; |
| 81 | } |
| 82 | } else { |
| 83 | if (!base::ReadFileToString(path, &content)) { |
| 84 | LOG(WARNING) << "Failed to load user script file: " << path.value(); |
| 85 | return false; |
| 86 | } |
| 87 | if (verifier.get()) { |
| 88 | content::BrowserThread::PostTask( |
| 89 | content::BrowserThread::IO, FROM_HERE, |
| 90 | base::Bind(&VerifyContent, verifier, host_id.id(), |
| 91 | script_file->extension_root(), |
| 92 | script_file->relative_path(), content)); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | // Localize the content. |
| 97 | if (localization_messages) { |
| 98 | std::string error; |
| 99 | MessageBundle::ReplaceMessagesWithExternalDictionary(*localization_messages, |
| 100 | &content, &error); |
| 101 | if (!error.empty()) |
| 102 | LOG(WARNING) << "Failed to replace messages in script: " << error; |
| 103 | } |
| 104 | |
| 105 | // Remove BOM from the content. |
lazyboy | 28acad8 | 2016-06-22 23:14:20 | [diff] [blame] | 106 | if (base::StartsWith(content, base::kUtf8ByteOrderMark, |
| 107 | base::CompareCase::SENSITIVE)) { |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 108 | script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark))); |
lazyboy | 28acad8 | 2016-06-22 23:14:20 | [diff] [blame] | 109 | } else { |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 110 | script_file->set_content(content); |
lazyboy | 28acad8 | 2016-06-22 23:14:20 | [diff] [blame] | 111 | } |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 112 | |
| 113 | return true; |
| 114 | } |
| 115 | |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 116 | SubstitutionMap* GetLocalizationMessages( |
| 117 | const ExtensionUserScriptLoader::HostsInfo& hosts_info, |
| 118 | const HostID& host_id) { |
| 119 | ExtensionUserScriptLoader::HostsInfo::const_iterator iter = |
| 120 | hosts_info.find(host_id); |
| 121 | if (iter == hosts_info.end()) |
| 122 | return nullptr; |
| 123 | return file_util::LoadMessageBundleSubstitutionMap( |
| 124 | iter->second.first, host_id.id(), iter->second.second); |
| 125 | } |
| 126 | |
| 127 | void LoadUserScripts(UserScriptList* user_scripts, |
| 128 | const ExtensionUserScriptLoader::HostsInfo& hosts_info, |
| 129 | const std::set<int>& added_script_ids, |
| 130 | const scoped_refptr<ContentVerifier>& verifier) { |
lazyboy | 12c77d7 | 2016-08-19 20:06:09 | [diff] [blame] | 131 | for (const std::unique_ptr<UserScript>& script : *user_scripts) { |
| 132 | if (added_script_ids.count(script->id()) == 0) |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 133 | continue; |
lazyboy | 12c77d7 | 2016-08-19 20:06:09 | [diff] [blame] | 134 | for (const std::unique_ptr<UserScript::File>& script_file : |
| 135 | script->js_scripts()) { |
| 136 | if (script_file->GetContent().empty()) |
| 137 | LoadScriptContent(script->host_id(), script_file.get(), nullptr, |
| 138 | verifier); |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 139 | } |
lazyboy | 12c77d7 | 2016-08-19 20:06:09 | [diff] [blame] | 140 | if (script->css_scripts().size() > 0) { |
lazyboy | 4ff54b3 | 2016-08-09 20:49:08 | [diff] [blame] | 141 | std::unique_ptr<SubstitutionMap> localization_messages( |
lazyboy | 12c77d7 | 2016-08-19 20:06:09 | [diff] [blame] | 142 | GetLocalizationMessages(hosts_info, script->host_id())); |
| 143 | for (const std::unique_ptr<UserScript::File>& script_file : |
| 144 | script->css_scripts()) { |
| 145 | if (script_file->GetContent().empty()) { |
| 146 | LoadScriptContent(script->host_id(), script_file.get(), |
lazyboy | 4ff54b3 | 2016-08-09 20:49:08 | [diff] [blame] | 147 | localization_messages.get(), verifier); |
| 148 | } |
| 149 | } |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 150 | } |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | void LoadScriptsOnFileThread( |
dcheng | f5d24108 | 2016-04-21 03:43:11 | [diff] [blame] | 155 | std::unique_ptr<UserScriptList> user_scripts, |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 156 | const ExtensionUserScriptLoader::HostsInfo& hosts_info, |
| 157 | const std::set<int>& added_script_ids, |
| 158 | const scoped_refptr<ContentVerifier>& verifier, |
| 159 | UserScriptLoader::LoadScriptsCallback callback) { |
| 160 | DCHECK(user_scripts.get()); |
| 161 | LoadUserScripts(user_scripts.get(), hosts_info, added_script_ids, verifier); |
dcheng | f5d24108 | 2016-04-21 03:43:11 | [diff] [blame] | 162 | std::unique_ptr<base::SharedMemory> memory = |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 163 | UserScriptLoader::Serialize(*user_scripts); |
| 164 | content::BrowserThread::PostTask( |
| 165 | content::BrowserThread::UI, FROM_HERE, |
| 166 | base::Bind(callback, base::Passed(&user_scripts), base::Passed(&memory))); |
| 167 | } |
| 168 | |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 169 | } // namespace |
| 170 | |
| 171 | ExtensionUserScriptLoader::ExtensionUserScriptLoader( |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 172 | BrowserContext* browser_context, |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 173 | const HostID& host_id, |
| 174 | bool listen_for_extension_system_loaded) |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 175 | : UserScriptLoader(browser_context, host_id), |
| 176 | content_verifier_( |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 177 | ExtensionSystem::Get(browser_context)->content_verifier()), |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 178 | extension_registry_observer_(this), |
| 179 | weak_factory_(this) { |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 180 | extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context)); |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 181 | if (listen_for_extension_system_loaded) { |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 182 | ExtensionSystem::Get(browser_context) |
| 183 | ->ready() |
| 184 | .Post(FROM_HERE, |
| 185 | base::Bind(&ExtensionUserScriptLoader::OnExtensionSystemReady, |
| 186 | weak_factory_.GetWeakPtr())); |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 187 | } else { |
| 188 | SetReady(true); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | ExtensionUserScriptLoader::~ExtensionUserScriptLoader() { |
| 193 | } |
| 194 | |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 195 | void ExtensionUserScriptLoader::LoadScriptsForTest( |
| 196 | UserScriptList* user_scripts) { |
| 197 | HostsInfo info; |
| 198 | std::set<int> added_script_ids; |
lazyboy | 12c77d7 | 2016-08-19 20:06:09 | [diff] [blame] | 199 | for (const std::unique_ptr<UserScript>& script : *user_scripts) |
| 200 | added_script_ids.insert(script->id()); |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 201 | |
| 202 | LoadUserScripts(user_scripts, info, added_script_ids, |
| 203 | nullptr /* no verifier for testing */); |
| 204 | } |
| 205 | |
| 206 | void ExtensionUserScriptLoader::LoadScripts( |
dcheng | f5d24108 | 2016-04-21 03:43:11 | [diff] [blame] | 207 | std::unique_ptr<UserScriptList> user_scripts, |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 208 | const std::set<HostID>& changed_hosts, |
| 209 | const std::set<int>& added_script_ids, |
| 210 | LoadScriptsCallback callback) { |
| 211 | UpdateHostsInfo(changed_hosts); |
| 212 | |
| 213 | content::BrowserThread::PostTask( |
| 214 | content::BrowserThread::FILE, FROM_HERE, |
| 215 | base::Bind(&LoadScriptsOnFileThread, base::Passed(&user_scripts), |
| 216 | hosts_info_, added_script_ids, content_verifier_, callback)); |
| 217 | } |
| 218 | |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 219 | void ExtensionUserScriptLoader::UpdateHostsInfo( |
| 220 | const std::set<HostID>& changed_hosts) { |
hanxi | 3b2b3df | 2015-02-24 15:28:07 | [diff] [blame] | 221 | ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context()); |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 222 | for (const HostID& host_id : changed_hosts) { |
| 223 | const Extension* extension = |
| 224 | registry->GetExtensionById(host_id.id(), ExtensionRegistry::ENABLED); |
| 225 | // |changed_hosts_| may include hosts that have been removed, |
| 226 | // which leads to the above lookup failing. In this case, just continue. |
| 227 | if (!extension) |
| 228 | continue; |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 229 | if (hosts_info_.find(host_id) != hosts_info_.end()) |
| 230 | continue; |
| 231 | hosts_info_[host_id] = ExtensionSet::ExtensionPathAndDefaultLocale( |
| 232 | extension->path(), LocaleInfo::GetDefaultLocale(extension)); |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 233 | } |
| 234 | } |
| 235 | |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 236 | void ExtensionUserScriptLoader::OnExtensionUnloaded( |
| 237 | content::BrowserContext* browser_context, |
| 238 | const Extension* extension, |
| 239 | UnloadedExtensionInfo::Reason reason) { |
hanxi | feb6a64f | 2015-04-24 19:21:40 | [diff] [blame] | 240 | hosts_info_.erase(HostID(HostID::EXTENSIONS, extension->id())); |
hanxi | c0503d7 | 2015-02-05 14:27:32 | [diff] [blame] | 241 | } |
| 242 | |
| 243 | void ExtensionUserScriptLoader::OnExtensionSystemReady() { |
| 244 | SetReady(true); |
| 245 | } |
| 246 | |
| 247 | } // namespace extensions |