| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/intents/web_intents_registry.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/bind_helpers.h" | |
| 11 #include "base/callback.h" | |
| 12 #include "base/string_util.h" | |
| 13 #include "base/string16.h" | |
| 14 #include "base/utf_string_conversions.h" | |
| 15 #include "chrome/browser/intents/default_web_intent_service.h" | |
| 16 #include "chrome/browser/intents/native_services.h" | |
| 17 #include "chrome/browser/intents/web_intents_util.h" | |
| 18 #include "chrome/browser/webdata/web_data_service.h" | |
| 19 #include "chrome/common/extensions/extension.h" | |
| 20 #include "chrome/common/extensions/extension_set.h" | |
| 21 #include "chrome/common/extensions/web_intents_handler.h" | |
| 22 #include "googleurl/src/gurl.h" | |
| 23 #include "net/base/mime_util.h" | |
| 24 | |
| 25 using extensions::Extension; | |
| 26 using net::IsMimeType; | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 // TODO(hshi): Temporary workaround for http://crbug.com/134197. | |
| 31 // If no user-set default service is found, use built-in QuickOffice Viewer as | |
| 32 // default for MS office files. Remove this once full defaults is in place. | |
| 33 const char* kQuickOfficeViewerMimeType[] = { | |
| 34 "application/msword", | |
| 35 "application/vnd.ms-powerpoint", | |
| 36 "application/vnd.ms-excel", | |
| 37 "application/vnd.openxmlformats-officedocument.wordprocessingml.document", | |
| 38 "application/vnd.openxmlformats-officedocument.presentationml.presentation", | |
| 39 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | |
| 40 }; | |
| 41 | |
| 42 typedef base::Callback<void(const WDTypedResult* result)> ResultsHandler; | |
| 43 typedef WebIntentsRegistry::IntentServiceList IntentServiceList; | |
| 44 | |
| 45 // Compares two web intents type specifiers to see if there is a match. | |
| 46 // First checks if both are MIME types. If so, uses MatchesMimeType. | |
| 47 // If not, uses exact string equality. | |
| 48 bool WebIntentsTypesMatch(const string16& type1, const string16& type2) { | |
| 49 return (IsMimeType(UTF16ToUTF8(type1)) && IsMimeType(UTF16ToUTF8(type2))) | |
| 50 ? web_intents::MimeTypesMatch(type1, type2) | |
| 51 : type1 == type2; | |
| 52 } | |
| 53 | |
| 54 // Adds any intent services of |extension| that match |action| to | |
| 55 // |matching_services|. | |
| 56 void AddMatchingServicesForExtension(const Extension& extension, | |
| 57 const string16& action, | |
| 58 IntentServiceList* matching_services) { | |
| 59 const IntentServiceList& services = | |
| 60 extensions::WebIntentsInfo::GetIntentsServices(&extension); | |
| 61 for (IntentServiceList::const_iterator i = services.begin(); | |
| 62 i != services.end(); ++i) { | |
| 63 if (action.empty() || action == i->action) | |
| 64 matching_services->push_back(*i); | |
| 65 } | |
| 66 } | |
| 67 | |
| 68 // Removes all services from |matching_services| that do not match |type|. | |
| 69 // Wildcards are supported, of the form '<type>/*' or '*'. | |
| 70 void FilterServicesByType(const string16& type, | |
| 71 IntentServiceList* matching_services) { | |
| 72 // Filter out all services not matching the query type. | |
| 73 IntentServiceList::iterator iter(matching_services->begin()); | |
| 74 while (iter != matching_services->end()) { | |
| 75 if (WebIntentsTypesMatch(iter->type, type)) | |
| 76 ++iter; | |
| 77 else | |
| 78 iter = matching_services->erase(iter); | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 // Callback for existence checks. Converts a callback for a list of services | |
| 83 // into a callback that returns true if the list contains a specific service. | |
| 84 void ExistenceCallback(const webkit_glue::WebIntentServiceData& service, | |
| 85 const base::Callback<void(bool)>& callback, | |
| 86 const WDTypedResult* result) { | |
| 87 | |
| 88 WebIntentsRegistry::IntentServiceList list = static_cast< | |
| 89 const WDResult<IntentServiceList>*>(result)->GetValue(); | |
| 90 | |
| 91 for (WebIntentsRegistry::IntentServiceList::const_iterator i = list.begin(); | |
| 92 i != list.end(); ++i) { | |
| 93 if (*i == service) { | |
| 94 callback.Run(true); | |
| 95 return; | |
| 96 } | |
| 97 } | |
| 98 | |
| 99 callback.Run(false); | |
| 100 } | |
| 101 | |
| 102 // Functor object for intent ordering. | |
| 103 struct IntentOrdering { | |
| 104 // Implements StrictWeakOrdering for intents, based on intent-equivalence. | |
| 105 // Order by |service_url|, |action|, |title|, and |disposition| in order. | |
| 106 bool operator()(const webkit_glue::WebIntentServiceData& lhs, | |
| 107 const webkit_glue::WebIntentServiceData& rhs) { | |
| 108 if (lhs.service_url != rhs.service_url) | |
| 109 return lhs.service_url < rhs.service_url; | |
| 110 | |
| 111 if (lhs.action != rhs.action) | |
| 112 return lhs.action < rhs.action; | |
| 113 | |
| 114 if (lhs.title != rhs.title) | |
| 115 return lhs.title < rhs.title; | |
| 116 | |
| 117 if (lhs.disposition != rhs.disposition) | |
| 118 return lhs.disposition < rhs.disposition; | |
| 119 | |
| 120 // At this point, we consider intents to be equal, even if |type| differs. | |
| 121 return false; | |
| 122 } | |
| 123 }; | |
| 124 | |
| 125 // Two intents are equivalent iff all fields except |type| are equal. | |
| 126 bool IntentsAreEquivalent(const webkit_glue::WebIntentServiceData& lhs, | |
| 127 const webkit_glue::WebIntentServiceData& rhs) { | |
| 128 return !((lhs.service_url != rhs.service_url) || | |
| 129 (lhs.action != rhs.action) || | |
| 130 (lhs.title != rhs.title) || | |
| 131 (lhs.disposition != rhs.disposition)); | |
| 132 } | |
| 133 | |
| 134 } // namespace | |
| 135 | |
| 136 using webkit_glue::WebIntentServiceData; | |
| 137 | |
| 138 // Internal object containing arguments to be used in post processing | |
| 139 // WDS results. | |
| 140 struct WebIntentsRegistry::QueryParams { | |
| 141 | |
| 142 // The particular action to filter for while searching through extensions. | |
| 143 // If |action_| is empty, return all extension-provided services. | |
| 144 string16 action_; | |
| 145 | |
| 146 // The MIME type that was requested for this service query. | |
| 147 // Suppports wild cards. | |
| 148 string16 type_; | |
| 149 | |
| 150 // The url of the invoking page. | |
| 151 GURL url_; | |
| 152 | |
| 153 // Create a new QueryParams for all intent services or for existence checks. | |
| 154 QueryParams() : type_(ASCIIToUTF16("*")) {} | |
| 155 | |
| 156 QueryParams(const string16& action, const string16& type) | |
| 157 : action_(action), type_(type) {} | |
| 158 | |
| 159 // Create a new QueryParams for default services. | |
| 160 QueryParams(const string16& action, const string16& type, const GURL& url) | |
| 161 : action_(action), type_(type), url_(url) {} | |
| 162 }; | |
| 163 | |
| 164 // Internal object adapting the WDS consumer interface to base::Bind | |
| 165 // callback way of doing business. | |
| 166 class WebIntentsRegistry::QueryAdapter : public WebDataServiceConsumer { | |
| 167 | |
| 168 public: | |
| 169 // Underlying data query. | |
| 170 WebDataService::Handle query_handle_; | |
| 171 | |
| 172 // Create a new QueryAdapter that delegates results to |handler|. | |
| 173 QueryAdapter(WebIntentsRegistry* registry, const ResultsHandler& handler) | |
| 174 : registry_(registry), handler_(handler) { | |
| 175 registry_->TrackQuery(this); | |
| 176 } | |
| 177 | |
| 178 void OnWebDataServiceRequestDone( | |
| 179 WebDataService::Handle h, | |
| 180 const WDTypedResult* result) OVERRIDE { | |
| 181 | |
| 182 handler_.Run(result); | |
| 183 registry_->ReleaseQuery(this); | |
| 184 } | |
| 185 | |
| 186 private: | |
| 187 // Handle so we can call back into the WebIntentsRegistry when | |
| 188 // processing query results. The registry is guaranteed to be | |
| 189 // valid for the life of this object. We do not own this object. | |
| 190 WebIntentsRegistry* registry_; | |
| 191 | |
| 192 // The callback for this particular query. | |
| 193 ResultsHandler handler_; | |
| 194 }; | |
| 195 | |
| 196 WebIntentsRegistry::WebIntentsRegistry() { | |
| 197 native_services_.reset(new web_intents::NativeServiceRegistry()); | |
| 198 } | |
| 199 | |
| 200 WebIntentsRegistry::~WebIntentsRegistry() { | |
| 201 | |
| 202 // Cancel all pending queries, since we can't handle them any more. | |
| 203 for (QueryVector::iterator it = pending_queries_.begin(); | |
| 204 it != pending_queries_.end(); ++it) { | |
| 205 QueryAdapter* query = *it; | |
| 206 wds_->CancelRequest(query->query_handle_); | |
| 207 delete query; | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 void WebIntentsRegistry::Initialize( | |
| 212 scoped_refptr<WebDataService> wds, | |
| 213 ExtensionServiceInterface* extension_service) { | |
| 214 wds_ = wds; | |
| 215 extension_service_ = extension_service; | |
| 216 } | |
| 217 | |
| 218 void WebIntentsRegistry::OnWebIntentsResultReceived( | |
| 219 const QueryParams& params, | |
| 220 const QueryCallback& callback, | |
| 221 const WDTypedResult* result) { | |
| 222 DCHECK(result); | |
| 223 DCHECK(result->GetType() == WEB_INTENTS_RESULT); | |
| 224 | |
| 225 IntentServiceList matching_services = static_cast< | |
| 226 const WDResult<IntentServiceList>*>(result)->GetValue(); | |
| 227 | |
| 228 // Loop over all services in all extensions, collect ones | |
| 229 // matching the query. | |
| 230 if (extension_service_) { | |
| 231 const ExtensionSet* extensions = extension_service_->extensions(); | |
| 232 if (extensions) { | |
| 233 for (ExtensionSet::const_iterator i(extensions->begin()); | |
| 234 i != extensions->end(); ++i) { | |
| 235 AddMatchingServicesForExtension(**i, params.action_, | |
| 236 &matching_services); | |
| 237 } | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 // add native services. | |
| 242 native_services_->GetSupportedServices(params.action_, &matching_services); | |
| 243 | |
| 244 // Filter out all services not matching the query type. | |
| 245 FilterServicesByType(params.type_, &matching_services); | |
| 246 | |
| 247 // Collapse intents that are equivalent for all but |type|. | |
| 248 CollapseIntents(&matching_services); | |
| 249 | |
| 250 callback.Run(matching_services); | |
| 251 } | |
| 252 | |
| 253 void WebIntentsRegistry::OnAllDefaultIntentServicesReceived( | |
| 254 const DefaultIntentServicesCallback& callback, | |
| 255 const WDTypedResult* result) { | |
| 256 DCHECK(result); | |
| 257 DCHECK(result->GetType() == WEB_INTENTS_DEFAULTS_RESULT); | |
| 258 | |
| 259 const std::vector<DefaultWebIntentService> services = static_cast< | |
| 260 const WDResult<std::vector<DefaultWebIntentService> >*>(result)-> | |
| 261 GetValue(); | |
| 262 | |
| 263 callback.Run(services); | |
| 264 } | |
| 265 | |
| 266 void WebIntentsRegistry::OnWebIntentsDefaultsResultReceived( | |
| 267 const QueryParams& params, | |
| 268 const DefaultQueryCallback& callback, | |
| 269 const WDTypedResult* result) { | |
| 270 DCHECK(result); | |
| 271 DCHECK(result->GetType() == WEB_INTENTS_DEFAULTS_RESULT); | |
| 272 | |
| 273 std::vector<DefaultWebIntentService> services = static_cast< | |
| 274 const WDResult<std::vector<DefaultWebIntentService> >*>(result)-> | |
| 275 GetValue(); | |
| 276 | |
| 277 DefaultWebIntentService default_service; | |
| 278 std::vector<DefaultWebIntentService>::iterator iter(services.begin()); | |
| 279 for (; iter != services.end(); ++iter) { | |
| 280 if (!WebIntentsTypesMatch(iter->type, params.type_)) { | |
| 281 continue; | |
| 282 } | |
| 283 if (!iter->url_pattern.MatchesURL(params.url_)) { | |
| 284 continue; | |
| 285 } | |
| 286 const Extension* extension = ExtensionForURL(iter->service_url); | |
| 287 if (extension != NULL && | |
| 288 !extension_service_->IsExtensionEnabled(extension->id())) { | |
| 289 continue; | |
| 290 } | |
| 291 | |
| 292 // Found a match. If it is better than default_service, use it. | |
| 293 // Currently the metric is that if the new value is user-set, | |
| 294 // prefer it. If the new value has a more specific pattern, prefer it. | |
| 295 // If the new value has a more recent date, prefer it. | |
| 296 if (default_service.user_date <= 0 && iter->user_date >= 0) { | |
| 297 default_service = *iter; | |
| 298 } else if (default_service.url_pattern.match_all_urls() && | |
| 299 !iter->url_pattern.match_all_urls()) { | |
| 300 default_service = *iter; | |
| 301 } else if (iter->url_pattern < default_service.url_pattern) { | |
| 302 default_service = *iter; | |
| 303 } else if (default_service.user_date < iter->user_date) { | |
| 304 default_service = *iter; | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 // If QuickOffice is the default for this, recompute defaults. | |
| 309 if (default_service.service_url | |
| 310 == web_intents::kQuickOfficeViewerServiceURL || | |
| 311 default_service.service_url | |
| 312 == web_intents::kQuickOfficeViewerDevServiceURL) { | |
| 313 default_service.user_date = -1; | |
| 314 } | |
| 315 | |
| 316 // TODO(hshi): Temporary workaround for http://crbug.com/134197. | |
| 317 // If no user-set default service is found, use built-in QuickOffice Viewer as | |
| 318 // default for MS office files. Remove this once full defaults is in place. | |
| 319 | |
| 320 if (default_service.user_date <= 0) { | |
| 321 const char kQuickOfficeDevExtensionId[] = | |
| 322 "ionpfmkccalenbmnddpbmocokhaknphg"; | |
| 323 std::string service_url = web_intents::kQuickOfficeViewerServiceURL; | |
| 324 if (extension_service_->GetInstalledExtension(kQuickOfficeDevExtensionId) && | |
| 325 extension_service_->IsExtensionEnabled(kQuickOfficeDevExtensionId)) { | |
| 326 service_url = web_intents::kQuickOfficeViewerDevServiceURL; | |
| 327 } | |
| 328 | |
| 329 for (size_t i = 0; i < sizeof(kQuickOfficeViewerMimeType) / sizeof(char*); | |
| 330 ++i) { | |
| 331 DefaultWebIntentService qoviewer_service; | |
| 332 qoviewer_service.action = ASCIIToUTF16(web_intents::kActionView); | |
| 333 qoviewer_service.type = ASCIIToUTF16(kQuickOfficeViewerMimeType[i]); | |
| 334 qoviewer_service.service_url = service_url; | |
| 335 if (WebIntentsTypesMatch(qoviewer_service.type, params.type_)) { | |
| 336 default_service = qoviewer_service; | |
| 337 break; | |
| 338 } | |
| 339 } | |
| 340 } | |
| 341 | |
| 342 callback.Run(default_service); | |
| 343 } | |
| 344 | |
| 345 void WebIntentsRegistry::GetIntentServices( | |
| 346 const string16& action, const string16& type, | |
| 347 const QueryCallback& callback) { | |
| 348 DCHECK(wds_.get()); | |
| 349 DCHECK(!callback.is_null()); | |
| 350 | |
| 351 const QueryParams params(action, type); | |
| 352 const ResultsHandler handler = base::Bind( | |
| 353 &WebIntentsRegistry::OnWebIntentsResultReceived, | |
| 354 base::Unretained(this), | |
| 355 params, | |
| 356 callback); | |
| 357 | |
| 358 QueryAdapter* query = new QueryAdapter(this, handler); | |
| 359 query->query_handle_ = wds_->GetWebIntentServicesForAction(action, query); | |
| 360 } | |
| 361 | |
| 362 void WebIntentsRegistry::GetAllIntentServices( | |
| 363 const QueryCallback& callback) { | |
| 364 DCHECK(wds_.get()); | |
| 365 DCHECK(!callback.is_null()); | |
| 366 | |
| 367 const QueryParams params; | |
| 368 const ResultsHandler handler = base::Bind( | |
| 369 &WebIntentsRegistry::OnWebIntentsResultReceived, | |
| 370 base::Unretained(this), | |
| 371 params, | |
| 372 callback); | |
| 373 | |
| 374 QueryAdapter* query = new QueryAdapter(this, handler); | |
| 375 query->query_handle_ = wds_->GetAllWebIntentServices(query); | |
| 376 } | |
| 377 | |
| 378 void WebIntentsRegistry::GetAllDefaultIntentServices( | |
| 379 const DefaultIntentServicesCallback& callback) { | |
| 380 DCHECK(!callback.is_null()); | |
| 381 | |
| 382 ResultsHandler handler = base::Bind( | |
| 383 &WebIntentsRegistry::OnAllDefaultIntentServicesReceived, | |
| 384 base::Unretained(this), | |
| 385 callback); | |
| 386 | |
| 387 QueryAdapter* query = new QueryAdapter(this, handler); | |
| 388 query->query_handle_ = | |
| 389 wds_->GetAllDefaultWebIntentServices(query); | |
| 390 } | |
| 391 | |
| 392 void WebIntentsRegistry::IntentServiceExists( | |
| 393 const WebIntentServiceData& service, | |
| 394 const base::Callback<void(bool)>& callback) { | |
| 395 DCHECK(!callback.is_null()); | |
| 396 | |
| 397 ResultsHandler handler = base::Bind( | |
| 398 &ExistenceCallback, | |
| 399 service, | |
| 400 callback); | |
| 401 | |
| 402 QueryAdapter* query = new QueryAdapter(this, handler); | |
| 403 query->query_handle_ = wds_->GetWebIntentServicesForURL( | |
| 404 UTF8ToUTF16(service.service_url.spec()), query); | |
| 405 } | |
| 406 | |
| 407 void WebIntentsRegistry::GetIntentServicesForExtensionFilter( | |
| 408 const string16& action, | |
| 409 const string16& type, | |
| 410 const std::string& extension_id, | |
| 411 IntentServiceList* services) { | |
| 412 if (extension_service_) { | |
| 413 const Extension* extension = | |
| 414 extension_service_->GetExtensionById(extension_id, false); | |
| 415 AddMatchingServicesForExtension(*extension, | |
| 416 action, | |
| 417 services); | |
| 418 FilterServicesByType(type, services); | |
| 419 } | |
| 420 } | |
| 421 | |
| 422 void WebIntentsRegistry::RegisterDefaultIntentService( | |
| 423 const DefaultWebIntentService& default_service) { | |
| 424 DCHECK(wds_.get()); | |
| 425 wds_->AddDefaultWebIntentService(default_service); | |
| 426 } | |
| 427 | |
| 428 void WebIntentsRegistry::UnregisterDefaultIntentService( | |
| 429 const DefaultWebIntentService& default_service) { | |
| 430 DCHECK(wds_.get()); | |
| 431 wds_->RemoveDefaultWebIntentService(default_service); | |
| 432 } | |
| 433 | |
| 434 void WebIntentsRegistry::UnregisterServiceDefaults(const GURL& service_url) { | |
| 435 DCHECK(wds_.get()); | |
| 436 wds_->RemoveWebIntentServiceDefaults(service_url); | |
| 437 } | |
| 438 | |
| 439 void WebIntentsRegistry::GetDefaultIntentService( | |
| 440 const string16& action, | |
| 441 const string16& type, | |
| 442 const GURL& invoking_url, | |
| 443 const DefaultQueryCallback& callback) { | |
| 444 DCHECK(!callback.is_null()); | |
| 445 | |
| 446 const QueryParams params(action, type); | |
| 447 | |
| 448 ResultsHandler handler = base::Bind( | |
| 449 &WebIntentsRegistry::OnWebIntentsDefaultsResultReceived, | |
| 450 base::Unretained(this), | |
| 451 params, | |
| 452 callback); | |
| 453 | |
| 454 QueryAdapter* query = new QueryAdapter(this, handler); | |
| 455 query->query_handle_ = | |
| 456 wds_->GetDefaultWebIntentServicesForAction(action, query); | |
| 457 } | |
| 458 | |
| 459 void WebIntentsRegistry::RegisterIntentService( | |
| 460 const WebIntentServiceData& service) { | |
| 461 DCHECK(wds_.get()); | |
| 462 wds_->AddWebIntentService(service); | |
| 463 } | |
| 464 | |
| 465 void WebIntentsRegistry::UnregisterIntentService( | |
| 466 const WebIntentServiceData& service) { | |
| 467 DCHECK(wds_.get()); | |
| 468 wds_->RemoveWebIntentService(service); | |
| 469 } | |
| 470 | |
| 471 void WebIntentsRegistry::CollapseIntents(IntentServiceList* services) { | |
| 472 DCHECK(services); | |
| 473 | |
| 474 // No need to do anything for no services/single service. | |
| 475 if (services->size() < 2) | |
| 476 return; | |
| 477 | |
| 478 // Sort so that intents that can be collapsed must be adjacent. | |
| 479 std::sort(services->begin(), services->end(), IntentOrdering()); | |
| 480 | |
| 481 // Combine adjacent services if they are equivalent. | |
| 482 IntentServiceList::iterator write_iter = services->begin(); | |
| 483 IntentServiceList::iterator read_iter = write_iter + 1; | |
| 484 while (read_iter != services->end()) { | |
| 485 if (IntentsAreEquivalent(*write_iter, *read_iter)) { | |
| 486 // If the two intents are equivalent, join types and collapse. | |
| 487 write_iter->type += ASCIIToUTF16(",") + read_iter->type; | |
| 488 } else { | |
| 489 // Otherwise, keep both intents. | |
| 490 ++write_iter; | |
| 491 if (write_iter != read_iter) | |
| 492 *write_iter = *read_iter; | |
| 493 } | |
| 494 ++read_iter; | |
| 495 } | |
| 496 | |
| 497 // Cut off everything after the last intent copied to the list. | |
| 498 if (++write_iter != services->end()) | |
| 499 services->erase(write_iter, services->end()); | |
| 500 } | |
| 501 | |
| 502 const Extension* WebIntentsRegistry::ExtensionForURL(const std::string& url) { | |
| 503 const ExtensionSet* extensions = extension_service_->extensions(); | |
| 504 if (!extensions) | |
| 505 return NULL; | |
| 506 | |
| 507 // Use the unsafe ExtensionURLInfo constructor: we don't care if the extension | |
| 508 // is running or not. | |
| 509 GURL gurl(url); | |
| 510 ExtensionURLInfo info(gurl); | |
| 511 return extensions->GetExtensionOrAppByURL(info); | |
| 512 } | |
| 513 | |
| 514 void WebIntentsRegistry::TrackQuery(QueryAdapter* query) { | |
| 515 DCHECK(query); | |
| 516 pending_queries_.push_back(query); | |
| 517 } | |
| 518 | |
| 519 void WebIntentsRegistry::ReleaseQuery(QueryAdapter* query) { | |
| 520 QueryVector::iterator it = std::find( | |
| 521 pending_queries_.begin(), pending_queries_.end(), query); | |
| 522 if (it != pending_queries_.end()) { | |
| 523 pending_queries_.erase(it); | |
| 524 delete query; | |
| 525 } else { | |
| 526 NOTREACHED(); | |
| 527 } | |
| 528 } | |
| OLD | NEW |