| /* |
| * Copyright 2013 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: sriharis@google.com (Srihari Sukumaran) |
| |
| #include "net/instaweb/rewriter/public/fix_reflow_filter.h" |
| |
| #include <map> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "net/instaweb/http/public/log_record.h" |
| #include "net/instaweb/rewriter/public/js_defer_disabled_filter.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "net/instaweb/util/public/property_cache.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/html/html_element.h" |
| #include "pagespeed/kernel/html/html_name.h" |
| #include "pagespeed/opt/logging/enums.pb.h" |
| |
| namespace { |
| |
| const char kReflowValueSeparators[] = ",:"; |
| const char kReflowClassAttribute[] = "data-pagespeed-fix-reflow"; |
| |
| } // namespace |
| |
| namespace net_instaweb { |
| |
| const char FixReflowFilter::kElementRenderedHeightPropertyName[] = |
| "element_rendered_height"; |
| |
| FixReflowFilter::FixReflowFilter(RewriteDriver* driver) |
| : rewrite_driver_(driver) { |
| } |
| |
| FixReflowFilter::~FixReflowFilter() { |
| } |
| |
| void FixReflowFilter::DetermineEnabled(GoogleString* disabled_reason) { |
| set_is_enabled(JsDeferDisabledFilter::ShouldApply(rewrite_driver_) && |
| // Can we also share the following conditions with |
| // JsDeferDisabledFilter. |
| !rewrite_driver_->flushing_cached_html() && |
| !rewrite_driver_->flushed_cached_html()); |
| if (!is_enabled()) { |
| rewrite_driver_->log_record()->LogRewriterHtmlStatus( |
| RewriteOptions::FilterId(RewriteOptions::kFixReflows), |
| RewriterHtmlApplication::DISABLED); |
| } |
| } |
| |
| void FixReflowFilter::StartDocument() { |
| bool pcache_miss = true; |
| PropertyPage* page = rewrite_driver_->property_page(); |
| const PropertyCache::Cohort* cohort = |
| rewrite_driver_->server_context()->fix_reflow_cohort(); |
| if (page != NULL && cohort != NULL) { |
| PropertyValue* property_value = page->GetProperty( |
| cohort, kElementRenderedHeightPropertyName); |
| VLOG(1) << "Property value: " << property_value << " has value? " |
| << property_value->has_value(); |
| const int64 cache_ttl_ms = rewrite_driver_->options()-> |
| finder_properties_cache_expiration_time_ms(); |
| PropertyCache* property_cache = |
| rewrite_driver_->server_context()->page_property_cache(); |
| if (property_value != NULL && |
| property_value->has_value() && |
| !property_cache->IsExpired(property_value, cache_ttl_ms)) { |
| pcache_miss = false; |
| VLOG(1) << "FixReflowFilter. Valid value in pcache."; |
| // Parse property_value->value() into "id:height" and keep these locally. |
| StringPieceVector element_height_vector; |
| SplitStringPieceToVector( |
| property_value->value(), kReflowValueSeparators, |
| &element_height_vector, true); |
| for (int i = 0, n = element_height_vector.size(); i < n - 1; i += 2) { |
| element_height_map_.insert(make_pair( |
| element_height_vector[i].as_string(), |
| element_height_vector[i+1].as_string())); |
| } |
| } |
| } |
| if (pcache_miss) { |
| rewrite_driver_->log_record()->LogRewriterHtmlStatus( |
| RewriteOptions::FilterId(RewriteOptions::kFixReflows), |
| RewriterHtmlApplication::PROPERTY_CACHE_MISS); |
| } else { |
| rewrite_driver_->log_record()->LogRewriterHtmlStatus( |
| RewriteOptions::FilterId(RewriteOptions::kFixReflows), |
| RewriterHtmlApplication::ACTIVE); |
| } |
| } |
| |
| void FixReflowFilter::StartElement(HtmlElement* element) { |
| // See if element has attribute id matching any one from "id:height" pairs. |
| // If yes insert a style attribute with height. |
| if (element->keyword() == HtmlName::kDiv) { |
| const char* id = element->AttributeValue(HtmlName::kId); |
| if (id != NULL) { |
| ElementHeightMap::const_iterator i = element_height_map_.find(id); |
| if (i != element_height_map_.end()) { |
| rewrite_driver_->log_record()->SetRewriterLoggingStatus( |
| RewriteOptions::FilterId(RewriteOptions::kFixReflows), |
| RewriterApplication::APPLIED_OK); |
| VLOG(1) << "div " << id << " has height " << i->second; |
| element->AddAttribute(rewrite_driver_->MakeName(HtmlName::kStyle), |
| StrCat("min-height:", i->second), |
| HtmlElement::DOUBLE_QUOTE); |
| element->AddAttribute(rewrite_driver_->MakeName(kReflowClassAttribute), |
| "", HtmlElement::DOUBLE_QUOTE); |
| // TODO(sriharis): Should we add js to delete the added style |
| // attributes? Maybe a function that is called from js_defer.js's |
| // AfterDefer hook. |
| } |
| } |
| } |
| } |
| |
| } // namespace net_instaweb |