Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(379)

Side by Side Diff: content/renderer/render_view_impl.cc

Issue 1804023002: Fix page zoom to be frame-centric for out-of-process frames. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase to master@{#386187}. Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/renderer/render_view_impl.h" 5 #include "content/renderer/render_view_impl.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <cmath> 8 #include <cmath>
9 9
10 #include "base/auto_reset.h" 10 #include "base/auto_reset.h"
(...skipping 610 matching lines...) Expand 10 before | Expand all | Expand 10 after
621 history_list_offset_(-1), 621 history_list_offset_(-1),
622 history_list_length_(0), 622 history_list_length_(0),
623 frames_in_progress_(0), 623 frames_in_progress_(0),
624 target_url_status_(TARGET_NONE), 624 target_url_status_(TARGET_NONE),
625 uses_temporary_zoom_level_(false), 625 uses_temporary_zoom_level_(false),
626 #if defined(OS_ANDROID) 626 #if defined(OS_ANDROID)
627 top_controls_constraints_(TOP_CONTROLS_STATE_BOTH), 627 top_controls_constraints_(TOP_CONTROLS_STATE_BOTH),
628 #endif 628 #endif
629 has_focus_(false), 629 has_focus_(false),
630 has_scrolled_focused_editable_node_into_rect_(false), 630 has_scrolled_focused_editable_node_into_rect_(false),
631 page_zoom_level_(params.page_zoom_level),
631 main_render_frame_(nullptr), 632 main_render_frame_(nullptr),
632 frame_widget_(nullptr), 633 frame_widget_(nullptr),
633 speech_recognition_dispatcher_(NULL), 634 speech_recognition_dispatcher_(NULL),
634 mouse_lock_dispatcher_(NULL), 635 mouse_lock_dispatcher_(NULL),
635 #if defined(OS_ANDROID) 636 #if defined(OS_ANDROID)
636 expected_content_intent_id_(0), 637 expected_content_intent_id_(0),
637 #endif 638 #endif
638 #if defined(ENABLE_PLUGINS) 639 #if defined(ENABLE_PLUGINS)
639 focused_pepper_plugin_(NULL), 640 focused_pepper_plugin_(NULL),
640 pepper_last_mouse_event_target_(NULL), 641 pepper_last_mouse_event_target_(NULL),
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after
792 GetContentClient()->renderer()->RenderViewCreated(this); 793 GetContentClient()->renderer()->RenderViewCreated(this);
793 794
794 // Ensure that sandbox flags are inherited from an opener in a different 795 // Ensure that sandbox flags are inherited from an opener in a different
795 // process. In that case, the browser process will set any inherited sandbox 796 // process. In that case, the browser process will set any inherited sandbox
796 // flags in |replicated_frame_state|, so apply them here. 797 // flags in |replicated_frame_state|, so apply them here.
797 if (opener_frame && !was_created_by_renderer && 798 if (opener_frame && !was_created_by_renderer &&
798 webview()->mainFrame()->isWebLocalFrame()) { 799 webview()->mainFrame()->isWebLocalFrame()) {
799 webview()->mainFrame()->toWebLocalFrame()->forceSandboxFlags( 800 webview()->mainFrame()->toWebLocalFrame()->forceSandboxFlags(
800 params.replicated_frame_state.sandbox_flags); 801 params.replicated_frame_state.sandbox_flags);
801 } 802 }
803
804 page_zoom_level_ = params.page_zoom_level;
802 } 805 }
803 806
804 RenderViewImpl::~RenderViewImpl() { 807 RenderViewImpl::~RenderViewImpl() {
805 DCHECK(!frame_widget_); 808 DCHECK(!frame_widget_);
806 809
807 for (BitmapMap::iterator it = disambiguation_bitmaps_.begin(); 810 for (BitmapMap::iterator it = disambiguation_bitmaps_.begin();
808 it != disambiguation_bitmaps_.end(); 811 it != disambiguation_bitmaps_.end();
809 ++it) 812 ++it)
810 delete it->second; 813 delete it->second;
811 814
(...skipping 481 matching lines...) Expand 10 before | Expand all | Expand 10 after
1293 IPC_MESSAGE_HANDLER(InputMsg_ScrollFocusedEditableNodeIntoRect, 1296 IPC_MESSAGE_HANDLER(InputMsg_ScrollFocusedEditableNodeIntoRect,
1294 OnScrollFocusedEditableNodeIntoRect) 1297 OnScrollFocusedEditableNodeIntoRect)
1295 IPC_MESSAGE_HANDLER(InputMsg_SetEditCommandsForNextKeyEvent, 1298 IPC_MESSAGE_HANDLER(InputMsg_SetEditCommandsForNextKeyEvent,
1296 OnSetEditCommandsForNextKeyEvent) 1299 OnSetEditCommandsForNextKeyEvent)
1297 IPC_MESSAGE_HANDLER(ViewMsg_CopyImageAt, OnCopyImageAt) 1300 IPC_MESSAGE_HANDLER(ViewMsg_CopyImageAt, OnCopyImageAt)
1298 IPC_MESSAGE_HANDLER(ViewMsg_SaveImageAt, OnSaveImageAt) 1301 IPC_MESSAGE_HANDLER(ViewMsg_SaveImageAt, OnSaveImageAt)
1299 IPC_MESSAGE_HANDLER(ViewMsg_SetPageScale, OnSetPageScale) 1302 IPC_MESSAGE_HANDLER(ViewMsg_SetPageScale, OnSetPageScale)
1300 IPC_MESSAGE_HANDLER(ViewMsg_Zoom, OnZoom) 1303 IPC_MESSAGE_HANDLER(ViewMsg_Zoom, OnZoom)
1301 IPC_MESSAGE_HANDLER(ViewMsg_SetZoomLevelForLoadingURL, 1304 IPC_MESSAGE_HANDLER(ViewMsg_SetZoomLevelForLoadingURL,
1302 OnSetZoomLevelForLoadingURL) 1305 OnSetZoomLevelForLoadingURL)
1303 IPC_MESSAGE_HANDLER(ViewMsg_SetZoomLevelForView,
1304 OnSetZoomLevelForView)
1305 IPC_MESSAGE_HANDLER(ViewMsg_SetPageEncoding, OnSetPageEncoding) 1306 IPC_MESSAGE_HANDLER(ViewMsg_SetPageEncoding, OnSetPageEncoding)
1306 IPC_MESSAGE_HANDLER(ViewMsg_ResetPageEncodingToDefault, 1307 IPC_MESSAGE_HANDLER(ViewMsg_ResetPageEncodingToDefault,
1307 OnResetPageEncodingToDefault) 1308 OnResetPageEncodingToDefault)
1308 IPC_MESSAGE_HANDLER(DragMsg_TargetDragEnter, OnDragTargetDragEnter) 1309 IPC_MESSAGE_HANDLER(DragMsg_TargetDragEnter, OnDragTargetDragEnter)
1309 IPC_MESSAGE_HANDLER(DragMsg_TargetDragOver, OnDragTargetDragOver) 1310 IPC_MESSAGE_HANDLER(DragMsg_TargetDragOver, OnDragTargetDragOver)
1310 IPC_MESSAGE_HANDLER(DragMsg_TargetDragLeave, OnDragTargetDragLeave) 1311 IPC_MESSAGE_HANDLER(DragMsg_TargetDragLeave, OnDragTargetDragLeave)
1311 IPC_MESSAGE_HANDLER(DragMsg_TargetDrop, OnDragTargetDrop) 1312 IPC_MESSAGE_HANDLER(DragMsg_TargetDrop, OnDragTargetDrop)
1312 IPC_MESSAGE_HANDLER(DragMsg_SourceEnded, OnDragSourceEnded) 1313 IPC_MESSAGE_HANDLER(DragMsg_SourceEnded, OnDragSourceEnded)
1313 IPC_MESSAGE_HANDLER(DragMsg_SourceSystemDragEnded, 1314 IPC_MESSAGE_HANDLER(DragMsg_SourceSystemDragEnded,
1314 OnDragSourceSystemDragEnded) 1315 OnDragSourceSystemDragEnded)
(...skipping 20 matching lines...) Expand all
1335 IPC_MESSAGE_HANDLER(ViewMsg_PluginActionAt, OnPluginActionAt) 1336 IPC_MESSAGE_HANDLER(ViewMsg_PluginActionAt, OnPluginActionAt)
1336 IPC_MESSAGE_HANDLER(ViewMsg_SetActive, OnSetActive) 1337 IPC_MESSAGE_HANDLER(ViewMsg_SetActive, OnSetActive)
1337 IPC_MESSAGE_HANDLER(ViewMsg_ShowContextMenu, OnShowContextMenu) 1338 IPC_MESSAGE_HANDLER(ViewMsg_ShowContextMenu, OnShowContextMenu)
1338 // TODO(viettrungluu): Move to a separate message filter. 1339 // TODO(viettrungluu): Move to a separate message filter.
1339 IPC_MESSAGE_HANDLER(ViewMsg_SetHistoryOffsetAndLength, 1340 IPC_MESSAGE_HANDLER(ViewMsg_SetHistoryOffsetAndLength,
1340 OnSetHistoryOffsetAndLength) 1341 OnSetHistoryOffsetAndLength)
1341 IPC_MESSAGE_HANDLER(ViewMsg_ReleaseDisambiguationPopupBitmap, 1342 IPC_MESSAGE_HANDLER(ViewMsg_ReleaseDisambiguationPopupBitmap,
1342 OnReleaseDisambiguationPopupBitmap) 1343 OnReleaseDisambiguationPopupBitmap)
1343 IPC_MESSAGE_HANDLER(ViewMsg_ForceRedraw, OnForceRedraw) 1344 IPC_MESSAGE_HANDLER(ViewMsg_ForceRedraw, OnForceRedraw)
1344 IPC_MESSAGE_HANDLER(ViewMsg_SelectWordAroundCaret, OnSelectWordAroundCaret) 1345 IPC_MESSAGE_HANDLER(ViewMsg_SelectWordAroundCaret, OnSelectWordAroundCaret)
1346
1347 // Page messages.
1345 IPC_MESSAGE_HANDLER(PageMsg_UpdateWindowScreenRect, 1348 IPC_MESSAGE_HANDLER(PageMsg_UpdateWindowScreenRect,
1346 OnUpdateWindowScreenRect) 1349 OnUpdateWindowScreenRect)
1350 IPC_MESSAGE_HANDLER(PageMsg_SetZoomLevel, OnSetZoomLevelForView)
1347 #if defined(OS_ANDROID) 1351 #if defined(OS_ANDROID)
1348 IPC_MESSAGE_HANDLER(ViewMsg_UpdateTopControlsState, 1352 IPC_MESSAGE_HANDLER(ViewMsg_UpdateTopControlsState,
1349 OnUpdateTopControlsState) 1353 OnUpdateTopControlsState)
1350 IPC_MESSAGE_HANDLER(ViewMsg_ExtractSmartClipData, OnExtractSmartClipData) 1354 IPC_MESSAGE_HANDLER(ViewMsg_ExtractSmartClipData, OnExtractSmartClipData)
1351 #elif defined(OS_MACOSX) 1355 #elif defined(OS_MACOSX)
1352 IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedText, 1356 IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedText,
1353 OnGetRenderedText) 1357 OnGetRenderedText)
1354 IPC_MESSAGE_HANDLER(ViewMsg_Close, OnClose) 1358 IPC_MESSAGE_HANDLER(ViewMsg_Close, OnClose)
1355 #endif 1359 #endif
1356 // Adding a new message? Add platform independent ones first, then put the 1360 // Adding a new message? Add platform independent ones first, then put the
(...skipping 234 matching lines...) Expand 10 before | Expand all | Expand 10 after
1591 view_params.swapped_out = false; 1595 view_params.swapped_out = false;
1592 // WebCore will take care of setting the correct name. 1596 // WebCore will take care of setting the correct name.
1593 view_params.replicated_frame_state = FrameReplicationState(); 1597 view_params.replicated_frame_state = FrameReplicationState();
1594 view_params.hidden = (params.disposition == NEW_BACKGROUND_TAB); 1598 view_params.hidden = (params.disposition == NEW_BACKGROUND_TAB);
1595 view_params.never_visible = never_visible; 1599 view_params.never_visible = never_visible;
1596 view_params.next_page_id = 1; 1600 view_params.next_page_id = 1;
1597 view_params.initial_size = initial_size; 1601 view_params.initial_size = initial_size;
1598 view_params.enable_auto_resize = false; 1602 view_params.enable_auto_resize = false;
1599 view_params.min_size = gfx::Size(); 1603 view_params.min_size = gfx::Size();
1600 view_params.max_size = gfx::Size(); 1604 view_params.max_size = gfx::Size();
1605 view_params.page_zoom_level = page_zoom_level_;
1601 1606
1602 RenderViewImpl* view = 1607 RenderViewImpl* view =
1603 RenderViewImpl::Create(compositor_deps_, view_params, true); 1608 RenderViewImpl::Create(compositor_deps_, view_params, true);
1604 view->opened_by_user_gesture_ = params.user_gesture; 1609 view->opened_by_user_gesture_ = params.user_gesture;
1605 1610
1606 return view->webview(); 1611 return view->webview();
1607 } 1612 }
1608 1613
1609 WebWidget* RenderViewImpl::createPopupMenu(blink::WebPopupType popup_type) { 1614 WebWidget* RenderViewImpl::createPopupMenu(blink::WebPopupType popup_type) {
1610 RenderWidget* widget = RenderWidget::Create(GetRoutingID(), compositor_deps_, 1615 RenderWidget* widget = RenderWidget::Create(GetRoutingID(), compositor_deps_,
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
1664 } 1669 }
1665 } 1670 }
1666 1671
1667 void RenderViewImpl::AttachWebFrameWidget(blink::WebFrameWidget* frame_widget) { 1672 void RenderViewImpl::AttachWebFrameWidget(blink::WebFrameWidget* frame_widget) {
1668 // The previous WebFrameWidget must already be detached by CloseForFrame(). 1673 // The previous WebFrameWidget must already be detached by CloseForFrame().
1669 DCHECK(!frame_widget_); 1674 DCHECK(!frame_widget_);
1670 frame_widget_ = frame_widget; 1675 frame_widget_ = frame_widget;
1671 } 1676 }
1672 1677
1673 void RenderViewImpl::SetZoomLevel(double zoom_level) { 1678 void RenderViewImpl::SetZoomLevel(double zoom_level) {
1679 // If we change the zoom level for the view, make sure any subsequent subframe
1680 // loads reflect the current zoom level.
1681 page_zoom_level_ = zoom_level;
1682
1674 webview()->setZoomLevel(zoom_level); 1683 webview()->setZoomLevel(zoom_level);
1675 FOR_EACH_OBSERVER(RenderViewObserver, observers_, OnZoomLevelChanged()); 1684 FOR_EACH_OBSERVER(RenderViewObserver, observers_, OnZoomLevelChanged());
1676 } 1685 }
1677 1686
1678 void RenderViewImpl::didCancelCompositionOnSelectionChange() { 1687 void RenderViewImpl::didCancelCompositionOnSelectionChange() {
1679 Send(new InputHostMsg_ImeCancelComposition(GetRoutingID())); 1688 Send(new InputHostMsg_ImeCancelComposition(GetRoutingID()));
1680 } 1689 }
1681 1690
1682 bool RenderViewImpl::handleCurrentKeyboardEvent() { 1691 bool RenderViewImpl::handleCurrentKeyboardEvent() {
1683 if (edit_commands_.empty()) 1692 if (edit_commands_.empty())
(...skipping 592 matching lines...) Expand 10 before | Expand all | Expand 10 after
2276 void RenderViewImpl::OnSetZoomLevelForLoadingURL(const GURL& url, 2285 void RenderViewImpl::OnSetZoomLevelForLoadingURL(const GURL& url,
2277 double zoom_level) { 2286 double zoom_level) {
2278 #if !defined(OS_ANDROID) 2287 #if !defined(OS_ANDROID)
2279 // On Android, page zoom isn't used, and in case of WebView, text zoom is used 2288 // On Android, page zoom isn't used, and in case of WebView, text zoom is used
2280 // for legacy WebView text scaling emulation. Thus, the code that resets 2289 // for legacy WebView text scaling emulation. Thus, the code that resets
2281 // the zoom level from this map will be effectively resetting text zoom level. 2290 // the zoom level from this map will be effectively resetting text zoom level.
2282 host_zoom_levels_[url] = zoom_level; 2291 host_zoom_levels_[url] = zoom_level;
2283 #endif 2292 #endif
2284 } 2293 }
2285 2294
2286 void RenderViewImpl::OnSetZoomLevelForView(bool uses_temporary_zoom_level, 2295 void RenderViewImpl::OnSetZoomLevelForView(
2287 double level) { 2296 PageMsg_SetZoomLevel_Command command,
2288 uses_temporary_zoom_level_ = uses_temporary_zoom_level; 2297 double zoom_level) {
2289 2298 switch (command) {
2299 case PageMsg_SetZoomLevel_Command::ZOOM_CLEAR_TEMPORARY:
2300 uses_temporary_zoom_level_ = false;
2301 break;
2302 case PageMsg_SetZoomLevel_Command::ZOOM_SET_TEMPORARY:
2303 uses_temporary_zoom_level_ = true;
2304 break;
2305 default: // PageMsg_SetZoomLevel_Command::ZOOM_USE_CURRENT_TEMPORARY_MODE
2306 // Don't override a temporary zoom level without an explicit SET.
2307 if (uses_temporary_zoom_level_)
2308 return;
2309 }
2290 webview()->hidePopups(); 2310 webview()->hidePopups();
2291 SetZoomLevel(level); 2311 SetZoomLevel(zoom_level);
2292 } 2312 }
2293 2313
2294 void RenderViewImpl::OnSetPageEncoding(const std::string& encoding_name) { 2314 void RenderViewImpl::OnSetPageEncoding(const std::string& encoding_name) {
2295 webview()->setPageEncoding(WebString::fromUTF8(encoding_name)); 2315 webview()->setPageEncoding(WebString::fromUTF8(encoding_name));
2296 } 2316 }
2297 2317
2298 void RenderViewImpl::OnResetPageEncodingToDefault() { 2318 void RenderViewImpl::OnResetPageEncodingToDefault() {
2299 WebString no_encoding; 2319 WebString no_encoding;
2300 webview()->setPageEncoding(no_encoding); 2320 webview()->setPageEncoding(no_encoding);
2301 } 2321 }
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after
2471 didUpdateLayout(); 2491 didUpdateLayout();
2472 } 2492 }
2473 2493
2474 void RenderViewImpl::OnDisableScrollbarsForSmallWindows( 2494 void RenderViewImpl::OnDisableScrollbarsForSmallWindows(
2475 const gfx::Size& disable_scrollbar_size_limit) { 2495 const gfx::Size& disable_scrollbar_size_limit) {
2476 disable_scrollbars_size_limit_ = disable_scrollbar_size_limit; 2496 disable_scrollbars_size_limit_ = disable_scrollbar_size_limit;
2477 } 2497 }
2478 2498
2479 void RenderViewImpl::OnSetRendererPrefs( 2499 void RenderViewImpl::OnSetRendererPrefs(
2480 const RendererPreferences& renderer_prefs) { 2500 const RendererPreferences& renderer_prefs) {
2481 double old_zoom_level = renderer_preferences_.default_zoom_level;
2482 std::string old_accept_languages = renderer_preferences_.accept_languages; 2501 std::string old_accept_languages = renderer_preferences_.accept_languages;
2483 2502
2484 renderer_preferences_ = renderer_prefs; 2503 renderer_preferences_ = renderer_prefs;
2485 2504
2486 UpdateFontRenderingFromRendererPrefs(); 2505 UpdateFontRenderingFromRendererPrefs();
2487 UpdateThemePrefs(); 2506 UpdateThemePrefs();
2488 2507
2489 #if defined(USE_DEFAULT_RENDER_THEME) 2508 #if defined(USE_DEFAULT_RENDER_THEME)
2490 if (renderer_prefs.use_custom_colors) { 2509 if (renderer_prefs.use_custom_colors) {
2491 blink::setFocusRingColor(renderer_prefs.focus_ring_color); 2510 blink::setFocusRingColor(renderer_prefs.focus_ring_color);
2492 blink::setCaretBlinkInterval(renderer_prefs.caret_blink_interval); 2511 blink::setCaretBlinkInterval(renderer_prefs.caret_blink_interval);
2493 2512
2494 if (webview()) { 2513 if (webview()) {
2495 webview()->setSelectionColors( 2514 webview()->setSelectionColors(
2496 renderer_prefs.active_selection_bg_color, 2515 renderer_prefs.active_selection_bg_color,
2497 renderer_prefs.active_selection_fg_color, 2516 renderer_prefs.active_selection_fg_color,
2498 renderer_prefs.inactive_selection_bg_color, 2517 renderer_prefs.inactive_selection_bg_color,
2499 renderer_prefs.inactive_selection_fg_color); 2518 renderer_prefs.inactive_selection_fg_color);
2500 webview()->themeChanged(); 2519 webview()->themeChanged();
2501 } 2520 }
2502 } 2521 }
2503 #endif // defined(USE_DEFAULT_RENDER_THEME) 2522 #endif // defined(USE_DEFAULT_RENDER_THEME)
2504 2523
2505 // If the zoom level for this page matches the old zoom default, and this
2506 // is not a plugin, update the zoom level to match the new default.
2507 if (webview() && webview()->mainFrame()->isWebLocalFrame() &&
2508 !webview()->mainFrame()->document().isPluginDocument() &&
2509 !ZoomValuesEqual(old_zoom_level,
2510 renderer_preferences_.default_zoom_level) &&
2511 ZoomValuesEqual(webview()->zoomLevel(), old_zoom_level)) {
2512 SetZoomLevel(renderer_preferences_.default_zoom_level);
2513 zoomLevelChanged();
2514 }
2515
2516 if (webview() && 2524 if (webview() &&
2517 old_accept_languages != renderer_preferences_.accept_languages) { 2525 old_accept_languages != renderer_preferences_.accept_languages) {
2518 webview()->acceptLanguagesChanged(); 2526 webview()->acceptLanguagesChanged();
2519 } 2527 }
2520 } 2528 }
2521 2529
2522 void RenderViewImpl::OnMediaPlayerActionAt(const gfx::Point& location, 2530 void RenderViewImpl::OnMediaPlayerActionAt(const gfx::Point& location,
2523 const WebMediaPlayerAction& action) { 2531 const WebMediaPlayerAction& action) {
2524 if (webview()) 2532 if (webview())
2525 webview()->performMediaPlayerAction(action, location); 2533 webview()->performMediaPlayerAction(action, location);
(...skipping 773 matching lines...) Expand 10 before | Expand all | Expand 10 after
3299 if (IsUseZoomForDSFEnabled()) { 3307 if (IsUseZoomForDSFEnabled()) {
3300 webview()->setZoomFactorForDeviceScaleFactor(device_scale_factor_); 3308 webview()->setZoomFactorForDeviceScaleFactor(device_scale_factor_);
3301 } else { 3309 } else {
3302 webview()->setDeviceScaleFactor(device_scale_factor_); 3310 webview()->setDeviceScaleFactor(device_scale_factor_);
3303 } 3311 }
3304 webview()->settings()->setPreferCompositingToLCDTextEnabled( 3312 webview()->settings()->setPreferCompositingToLCDTextEnabled(
3305 PreferCompositingToLCDText(compositor_deps_, device_scale_factor_)); 3313 PreferCompositingToLCDText(compositor_deps_, device_scale_factor_));
3306 } 3314 }
3307 3315
3308 } // namespace content 3316 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698