| // Copyright 2016 The Fuchsia 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 "topaz/runtime/web_view/schema_org_context.h" |
| |
| #include "lib/fxl/files/file.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/url/gurl.h" |
| #include "peridot/lib/rapidjson/rapidjson.h" |
| |
| #include "WebView.h" |
| |
| namespace { |
| |
| constexpr char script_path[] = "/system/data/js/extract_schema_org.js"; |
| |
| constexpr char get_json[] = "JSON.stringify(document['fuchsia:entities'])"; |
| |
| constexpr char type_field[] = "@type"; |
| constexpr char context_field[] = "@context"; |
| |
| std::string ExtractTypeFromEntity(const rapidjson::Value& entity) { |
| FXL_DCHECK(entity.IsObject()); |
| std::string context; |
| if (entity.HasMember(context_field)) { |
| const auto& context_json = entity[context_field]; |
| // TODO(ianloic): handle more complex @context. |
| if (!context_json.IsString()) { |
| FXL_LOG(WARNING) << "Expected " << context_field << " to be a string in: " |
| << modular::JsonValueToPrettyString(entity); |
| return ""; |
| } |
| context.assign(context_json.GetString(), context_json.GetStringLength()); |
| } |
| |
| if (!entity.HasMember(type_field)) { |
| FXL_LOG(WARNING) << "Expected JSON-LD to have a " << type_field << ": " |
| << modular::JsonValueToPrettyString(entity); |
| return ""; |
| } |
| |
| const auto& type_json = entity[type_field]; |
| // TODO(ianloic): can @type be an array? |
| if (!type_json.IsString()) { |
| FXL_LOG(WARNING) << "Expected " << type_field << " to be a string in: " |
| << modular::JsonValueToPrettyString(entity); |
| return ""; |
| } |
| |
| std::string type(type_json.GetString(), type_json.GetStringLength()); |
| |
| if (!context.size()) { |
| return type; |
| } |
| |
| url::GURL context_url(context); |
| if (!context_url.is_valid()) { |
| FXL_LOG(WARNING) << context_field << " not valid in: " |
| << modular::JsonValueToPrettyString(entity); |
| return ""; |
| } |
| |
| url::GURL type_url = context_url.Resolve(type); |
| if (!type_url.is_valid()) { |
| FXL_LOG(WARNING) << "Couldn't resolve " << type_field << " relative to " |
| << context_field |
| << " in: " << modular::JsonValueToPrettyString(entity); |
| return ""; |
| } |
| |
| return type_url.spec(); |
| } |
| |
| } // namespace |
| |
| SchemaOrgContext::SchemaOrgContext(WebView& web_view) : web_view_(web_view) { |
| bool script_loaded = files::ReadFileToString(script_path, &script_); |
| if (!script_loaded) { |
| FXL_LOG(WARNING) << "Failed to load script: " << script_path; |
| return; |
| } |
| web_view_.setDidFinishLoadDelegate([this] { PageLoaded(); }); |
| } |
| |
| void SchemaOrgContext::PageLoaded() { |
| FXL_LOG(INFO) << "Page Loaded"; |
| web_view_.addCustomEventHandler("fuchsia-entities-changed", |
| [this] { EntitiesChanged(); }); |
| web_view_.stringByEvaluatingJavaScriptFromString(script_); |
| } |
| |
| void SchemaOrgContext::EntitiesChanged() { |
| FXL_LOG(INFO) << "Entities Changed"; |
| // Remove any existing values. |
| context_values_.clear(); |
| |
| // Get new entity JSON from the web page. |
| std::string json = web_view_.stringByEvaluatingJavaScriptFromString(get_json); |
| modular::JsonDoc parsed; |
| parsed.Parse(json); |
| if (!parsed.IsArray()) { |
| FXL_LOG(WARNING) << "Root JSON object not an array: " << json; |
| return; |
| } |
| |
| // Process the JSON into entities. |
| for (auto i = parsed.Begin(); i != parsed.End(); ++i) { |
| const auto& entity_json = *i; |
| if (!entity_json.IsObject()) { |
| FXL_LOG(WARNING) << "Expected JSON-LD object, got: " |
| << modular::JsonValueToPrettyString(entity_json); |
| continue; |
| } |
| std::string type = ExtractTypeFromEntity(entity_json); |
| FXL_LOG(INFO) << "entity type: " << type; |
| if (type.size()) { |
| fidl::VectorPtr<fuchsia::modular::TypeToDataEntryPtr> type_to_data_array; |
| fuchsia::modular::TypeToDataEntryPtr entry = |
| fuchsia::modular::TypeToDataEntry::New(); |
| entry->type = type; |
| entry->data = modular::JsonValueToPrettyString(*i); |
| type_to_data_array.push_back(std::move(entry)); |
| component_context_->CreateEntityWithData( |
| std::move(type_to_data_array), |
| [this](const std::string& entity_reference) { |
| fuchsia::modular::ContextValueWriterPtr value; |
| context_writer_->CreateValue( |
| value.NewRequest(), fuchsia::modular::ContextValueType::ENTITY); |
| value->Set(entity_reference, nullptr /* metadata */); |
| // TODO: there's a potential race here if EntitiesChanged is |
| // called while there are outstanding CreateEntityWithData |
| // callbacks. |
| context_values_.push_back(std::move(value)); |
| }); |
| } |
| } |
| } |