blob: c0bfe8b83babbac29d2905db6dd1a787b2ed8ff3 [file] [log] [blame]
/*
* Copyright 2014 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: stevensr@google.com (Ryan Stevens)
#ifndef NET_INSTAWEB_REWRITER_PUBLIC_MOBILIZE_REWRITE_FILTER_H_
#define NET_INSTAWEB_REWRITER_PUBLIC_MOBILIZE_REWRITE_FILTER_H_
#include "net/instaweb/rewriter/public/common_filter.h"
#include "net/instaweb/rewriter/public/mobilize_decision_trees.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/static_asset_manager.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/statistics.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/kernel/html/html_node.h"
#include "pagespeed/kernel/http/user_agent_matcher.h"
namespace net_instaweb {
// Rewrite HTML to be mobile-friendly based on "data-mobile-role" attributes in
// the HTML tags. To reorganize the DOM, the filter puts containers at the end
// of the body into which we move tagged elements. The containers are later
// removed after the filter is done processing the document body. The filter
// applies the following transformations:
// - Add mobile <style> and <meta name="viewport"...> tags to the head.
// - Remove all table tags (but keep the content). Almost all tables in desktop
// HTML are for formatting, not displaying data, and they tend not to resize
// well for mobile. The easiest thing to do is to simply strip out the
// formatting and hope the content reflows properly.
// - Reorder body of the HTML DOM elements based on mobile role. Any elements
// which don't have an important parent will get removed, except for a
// special set of "keeper" tags (like <script> or <style>). The keeper tags
// are retained because they are often necessary for the website to work
// properly, and because they have no visible appearance on the page.
// - Remove all elements from inside data-mobile-role="navigational" elements
// except in a special set of nav tags (notably <a>). Nav sections often do
// not resize well due to fixed width formatting and drop-down menus, so it
// is often necessary to pull out what you want, instead of shuffling around
// what is there.
//
// Remaining todos:
// - TODO (stevensr): This script does not handle flush windows in the body.
// - TODO (stevensr): It would be nice to tweak the table-xform behavior via
// options. Also, there has been mention that removing tables across flush
// windows could be problematic. This should be addressed at some point.
// - TODO (stevensr): Enable this filter only for mobile UAs, and have a query
// param option to turn it on for all UAs for debugging.
// - TODO (stevensr): Write pcache entry if rewriting page fails. We should
// then probably inject some JS to auto-refresh the page so the user does not
// see the badly rewritten result.
// - TODO (stevensr): Add a separate wildcard option to allow/disallow URLs
// from using this filter. Of course sites can use our existing Allow and
// Disallow directives but that turns off all optimizations, and this one is
// one that might be extra finicky (e.g. don't touch my admin pages).
// - TODO (stevensr): Turn on css_move_to_head_filter.cc to reorder elements
// we inject into the head.
class MobilizeRewriteFilter : public CommonFilter {
public:
static const char kPagesMobilized[];
static const char kKeeperBlocks[];
static const char kHeaderBlocks[];
static const char kNavigationalBlocks[];
static const char kContentBlocks[];
static const char kMarginalBlocks[];
static const char kDeletedElements[];
explicit MobilizeRewriteFilter(RewriteDriver* rewrite_driver);
virtual ~MobilizeRewriteFilter();
static void InitStats(Statistics* statistics);
// True if options or request UA suggest we will actually do mobilization.
static bool IsApplicableFor(RewriteDriver* driver);
static bool IsApplicableFor(const RewriteOptions* options,
const char* user_agent,
const UserAgentMatcher* matcher);
virtual void DetermineEnabled(GoogleString* disabled_reason);
virtual void StartDocumentImpl();
virtual void EndDocument();
virtual void RenderDone();
virtual void StartElementImpl(HtmlElement* element);
virtual void EndElementImpl(HtmlElement* element);
virtual void Characters(HtmlCharactersNode* characters);
virtual const char* Name() const { return "MobilizeRewrite"; }
private:
void AppendStylesheet(StringPiece css_file_name,
StaticAssetEnum::StaticAsset asset,
HtmlElement* element);
void AddStyle(HtmlElement* element);
MobileRole::Level GetMobileRole(HtmlElement* element);
bool CheckForKeyword(
const HtmlName::Keyword* sorted_list, int len, HtmlName::Keyword keyword);
void LogEncounteredBlock(MobileRole::Level level);
GoogleString GetMobJsInitScript();
int body_element_depth_;
int keeper_element_depth_;
bool reached_reorder_containers_;
bool added_viewport_;
bool added_style_;
bool added_containers_;
bool added_progress_;
bool added_spacer_;
bool config_mode_;
bool in_script_;
bool saw_end_document_;
bool use_js_layout_;
bool use_js_nav_;
bool labeled_mode_;
bool use_static_;
bool rewrite_js_;
GoogleString static_file_prefix_;
// Statistics
// Number of web pages we have mobilized.
Variable* num_pages_mobilized_;
// Number of blocks of each mobile role encountered and reordered.
Variable* num_keeper_blocks_;
Variable* num_header_blocks_;
Variable* num_navigational_blocks_;
Variable* num_content_blocks_;
Variable* num_marginal_blocks_;
// Number of elements deleted.
Variable* num_elements_deleted_;
// Used for overriding default behavior in testing.
friend class MobilizeRewriteFilterTest;
DISALLOW_COPY_AND_ASSIGN(MobilizeRewriteFilter);
};
} // namespace net_instaweb
#endif // NET_INSTAWEB_REWRITER_PUBLIC_MOBILIZE_REWRITE_FILTER_H_