Commit 75055a13 authored by Lowell Manners's avatar Lowell Manners Committed by Commit Bot

[bfcache] Don't reuse RenderViewHosts belonging to cached pages.

After a page enters the BackForwardCache, a user may navigate to a new
page on the same site as the cached page (See
BackForwardCacheBrowserTest.NavigateToTwoPagesOnSameSite).

In the above case, it's important not to reuse the RenderViewHost(s)
owned by the frames in the BackForwardCache, because this could lead to
two main frames (the one in the cache, and the one the user just
navigated to), existing at the same time for a single RenderViewHost
(not allowed).

To solve this, we can simply stop reusing RenderViewHosts that belong
to a page when it enters the BackForwardCache, and begin reusing them
again when it leaves the BackForwardCache.

This approach also allows us to remove a work-around in
navigation_controller_impl.cc that was aggressively deleting cached
frames.

Note: RenderViewHost is owned by RenderFrameHost, this CL changes
nothing WRT object lifetimes.

Change-Id: I18698fc4f737f3945ab41270bd07d1538ca096a5
Bug: 993337, 999846
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1833616Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Lowell Manners <lowell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#705043}
parent 71ffcdde
...@@ -88,6 +88,10 @@ class BackForwardCacheBrowserTest : public ContentBrowserTest { ...@@ -88,6 +88,10 @@ class BackForwardCacheBrowserTest : public ContentBrowserTest {
return web_contents()->GetFrameTree()->root()->render_manager(); return web_contents()->GetFrameTree()->root()->render_manager();
} }
std::string DepictFrameTree(FrameTreeNode* node) {
return visualizer_.DepictFrameTree(node);
}
void ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome outcome, void ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome outcome,
base::Location location) { base::Location location) {
base::HistogramBase::Sample sample = base::HistogramBase::Sample(outcome); base::HistogramBase::Sample sample = base::HistogramBase::Sample(outcome);
...@@ -168,6 +172,7 @@ class BackForwardCacheBrowserTest : public ContentBrowserTest { ...@@ -168,6 +172,7 @@ class BackForwardCacheBrowserTest : public ContentBrowserTest {
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
FrameTreeVisualizer visualizer_;
base::HistogramTester histogram_tester_; base::HistogramTester histogram_tester_;
std::vector<base::Bucket> expected_outcomes_; std::vector<base::Bucket> expected_outcomes_;
std::vector<base::Bucket> expected_disabled_reasons_; std::vector<base::Bucket> expected_disabled_reasons_;
...@@ -932,6 +937,7 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ...@@ -932,6 +937,7 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount());
RenderFrameHostImpl* rfh_a = current_frame_host(); RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
std::string frame_tree_a = DepictFrameTree(rfh_a->frame_tree_node());
// 2. Navigate from a cacheable page to an uncacheable page (A->B). // 2. Navigate from a cacheable page to an uncacheable page (A->B).
EXPECT_TRUE(NavigateToURL(shell(), url_b)); EXPECT_TRUE(NavigateToURL(shell(), url_b));
...@@ -958,6 +964,10 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ...@@ -958,6 +964,10 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
delete_observer_rfh_b.WaitUntilDeleted(); delete_observer_rfh_b.WaitUntilDeleted();
EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount());
// Page A should still have the correct frame tree.
EXPECT_EQ(frame_tree_a,
DepictFrameTree(current_frame_host()->frame_tree_node()));
// 4. Navigate from a cacheable page to a cacheable page (A->C). // 4. Navigate from a cacheable page to a cacheable page (A->C).
EXPECT_TRUE(NavigateToURL(shell(), url_c)); EXPECT_TRUE(NavigateToURL(shell(), url_c));
EXPECT_EQ(3u, render_frame_host_manager()->GetProxyCount()); EXPECT_EQ(3u, render_frame_host_manager()->GetProxyCount());
...@@ -977,6 +987,10 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ...@@ -977,6 +987,10 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount());
// Page A should still have the correct frame tree.
EXPECT_EQ(frame_tree_a,
DepictFrameTree(current_frame_host()->frame_tree_node()));
// Page C should be in the cache. // Page C should be in the cache.
EXPECT_FALSE(delete_observer_rfh_c.deleted()); EXPECT_FALSE(delete_observer_rfh_c.deleted());
EXPECT_TRUE(rfh_c->is_in_back_forward_cache()); EXPECT_TRUE(rfh_c->is_in_back_forward_cache());
......
...@@ -147,10 +147,13 @@ BackForwardCacheTestDelegate* g_bfcache_disabled_test_observer = nullptr; ...@@ -147,10 +147,13 @@ BackForwardCacheTestDelegate* g_bfcache_disabled_test_observer = nullptr;
} // namespace } // namespace
BackForwardCacheImpl::Entry::Entry(std::unique_ptr<RenderFrameHostImpl> rfh, BackForwardCacheImpl::Entry::Entry(
RenderFrameProxyHostMap proxies) std::unique_ptr<RenderFrameHostImpl> rfh,
: render_frame_host(std::move(rfh)), proxy_hosts(std::move(proxies)) {} RenderFrameProxyHostMap proxies,
std::set<RenderViewHostImpl*> render_view_hosts)
: render_frame_host(std::move(rfh)),
proxy_hosts(std::move(proxies)),
render_view_hosts(std::move(render_view_hosts)) {}
BackForwardCacheImpl::Entry::~Entry() {} BackForwardCacheImpl::Entry::~Entry() {}
std::string BackForwardCacheImpl::CanStoreDocumentResult::ToString() { std::string BackForwardCacheImpl::CanStoreDocumentResult::ToString() {
......
...@@ -26,6 +26,7 @@ namespace content { ...@@ -26,6 +26,7 @@ namespace content {
class RenderFrameHostImpl; class RenderFrameHostImpl;
class RenderFrameProxyHost; class RenderFrameProxyHost;
class RenderViewHostImpl;
// BackForwardCache: // BackForwardCache:
// //
...@@ -41,7 +42,8 @@ class CONTENT_EXPORT BackForwardCacheImpl : public BackForwardCache { ...@@ -41,7 +42,8 @@ class CONTENT_EXPORT BackForwardCacheImpl : public BackForwardCache {
std::unique_ptr<RenderFrameProxyHost>>; std::unique_ptr<RenderFrameProxyHost>>;
Entry(std::unique_ptr<RenderFrameHostImpl> rfh, Entry(std::unique_ptr<RenderFrameHostImpl> rfh,
RenderFrameProxyHostMap proxy_hosts); RenderFrameProxyHostMap proxy_hosts,
std::set<RenderViewHostImpl*> render_view_hosts);
~Entry(); ~Entry();
// The main document being stored. // The main document being stored.
...@@ -53,6 +55,17 @@ class CONTENT_EXPORT BackForwardCacheImpl : public BackForwardCache { ...@@ -53,6 +55,17 @@ class CONTENT_EXPORT BackForwardCacheImpl : public BackForwardCache {
// cached. // cached.
RenderFrameProxyHostMap proxy_hosts; RenderFrameProxyHostMap proxy_hosts;
// RenderViewHosts belonging to the main frame, and its proxies (if any).
//
// While RenderViewHostImpl(s) are in the BackForwardCache, they aren't
// reused for pages outside the cache. This prevents us from having two main
// frames, (one in the cache, one live), associated with a single
// RenderViewHost.
//
// Keeping these here also prevents RenderFrameHostManager code from
// unwittingly iterating over RenderViewHostImpls that are in the cache.
std::set<RenderViewHostImpl*> render_view_hosts;
DISALLOW_COPY_AND_ASSIGN(Entry); DISALLOW_COPY_AND_ASSIGN(Entry);
}; };
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "content/browser/frame_host/frame_tree_node.h" #include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/navigation_controller_impl.h" #include "content/browser/frame_host/navigation_controller_impl.h"
...@@ -380,7 +381,7 @@ scoped_refptr<RenderViewHostImpl> FrameTree::CreateRenderViewHost( ...@@ -380,7 +381,7 @@ scoped_refptr<RenderViewHostImpl> FrameTree::CreateRenderViewHost(
static_cast<RenderViewHostImpl*>(RenderViewHostFactory::Create( static_cast<RenderViewHostImpl*>(RenderViewHostFactory::Create(
site_instance, render_view_delegate_, render_widget_delegate_, site_instance, render_view_delegate_, render_widget_delegate_,
routing_id, main_frame_routing_id, widget_routing_id, swapped_out)); routing_id, main_frame_routing_id, widget_routing_id, swapped_out));
render_view_host_map_[site_instance->GetId()] = rvh; RegisterRenderViewHost(rvh);
return base::WrapRefCounted(rvh); return base::WrapRefCounted(rvh);
} }
...@@ -393,7 +394,13 @@ scoped_refptr<RenderViewHostImpl> FrameTree::GetRenderViewHost( ...@@ -393,7 +394,13 @@ scoped_refptr<RenderViewHostImpl> FrameTree::GetRenderViewHost(
return base::WrapRefCounted(it->second); return base::WrapRefCounted(it->second);
} }
void FrameTree::RenderViewHostDeleted(RenderViewHost* rvh) { void FrameTree::RegisterRenderViewHost(RenderViewHostImpl* rvh) {
CHECK(
!base::Contains(render_view_host_map_, rvh->GetSiteInstance()->GetId()));
render_view_host_map_[rvh->GetSiteInstance()->GetId()] = rvh;
}
void FrameTree::UnregisterRenderViewHost(RenderViewHostImpl* rvh) {
auto it = render_view_host_map_.find(rvh->GetSiteInstance()->GetId()); auto it = render_view_host_map_.find(rvh->GetSiteInstance()->GetId());
CHECK(it != render_view_host_map_.end()); CHECK(it != render_view_host_map_.end());
CHECK_EQ(it->second, rvh); CHECK_EQ(it->second, rvh);
......
...@@ -222,11 +222,24 @@ class CONTENT_EXPORT FrameTree { ...@@ -222,11 +222,24 @@ class CONTENT_EXPORT FrameTree {
scoped_refptr<RenderViewHostImpl> GetRenderViewHost( scoped_refptr<RenderViewHostImpl> GetRenderViewHost(
SiteInstance* site_instance); SiteInstance* site_instance);
// The FrameTree maintains a list of existing RenderViewHostImpl so that // Registers a RenderViewHost so that it can be reused by other frames
// FrameTree::CreateRenderViewHost() can return them directly instead of // belonging to the same SiteInstance.
// creating a new one. Calling this function removes it from the list when the //
// |render_view_host| is deleted. // This method does not take ownership of|rvh|.
void RenderViewHostDeleted(RenderViewHost* render_view_host); //
// NOTE: This method CHECK fails if a RenderViewHost is already registered for
// |rvh|'s SiteInstance.
//
// ALSO NOTE: After calling RegisterRenderViewHost, UnregisterRenderViewHost
// *must* be called for |rvh| when it is destroyed or put into the
// BackForwardCache, to prevent FrameTree::CreateRenderViewHost from trying to
// reuse it.
void RegisterRenderViewHost(RenderViewHostImpl* rvh);
// Unregisters the RenderViewHostImpl that's available for reuse for a
// particular SiteInstance. NOTE: This method CHECK fails if it is called for
// a |render_view_host| that is not currently set for reuse.
void UnregisterRenderViewHost(RenderViewHostImpl* render_view_host);
// This is called when the frame is about to be removed and started to run // This is called when the frame is about to be removed and started to run
// unload handlers. // unload handlers.
......
...@@ -507,8 +507,12 @@ void RenderFrameHostManager::SwapOutOldFrame( ...@@ -507,8 +507,12 @@ void RenderFrameHostManager::SwapOutOldFrame(
TRACE_EVENT1("navigation", "BackForwardCache_MaybeStorePage", "can_store", TRACE_EVENT1("navigation", "BackForwardCache_MaybeStorePage", "can_store",
can_store.ToString()); can_store.ToString());
if (can_store) { if (can_store) {
std::set<RenderViewHostImpl*> old_render_view_hosts;
// Prepare the main frame. // Prepare the main frame.
back_forward_cache.Freeze(old_render_frame_host.get()); back_forward_cache.Freeze(old_render_frame_host.get());
old_render_view_hosts.insert(static_cast<RenderViewHostImpl*>(
old_render_frame_host->GetRenderViewHost()));
// Prepare the proxies. // Prepare the proxies.
RenderFrameProxyHostMap old_proxy_hosts; RenderFrameProxyHostMap old_proxy_hosts;
...@@ -517,8 +521,10 @@ void RenderFrameHostManager::SwapOutOldFrame( ...@@ -517,8 +521,10 @@ void RenderFrameHostManager::SwapOutOldFrame(
// This avoids including the proxy created when starting a // This avoids including the proxy created when starting a
// new cross-process, cross-BrowsingInstance navigation, as well as any // new cross-process, cross-BrowsingInstance navigation, as well as any
// restored proxies which are also in a different BrowsingInstance. // restored proxies which are also in a different BrowsingInstance.
if (instance->IsRelatedSiteInstance(it.second->GetSiteInstance())) if (instance->IsRelatedSiteInstance(it.second->GetSiteInstance())) {
old_render_view_hosts.insert(it.second->GetRenderViewHost());
old_proxy_hosts[it.first] = std::move(it.second); old_proxy_hosts[it.first] = std::move(it.second);
}
} }
// Remove the previously extracted proxies from the // Remove the previously extracted proxies from the
// RenderFrameHostManager, which also remove their respective // RenderFrameHostManager, which also remove their respective
...@@ -526,8 +532,13 @@ void RenderFrameHostManager::SwapOutOldFrame( ...@@ -526,8 +532,13 @@ void RenderFrameHostManager::SwapOutOldFrame(
for (auto& it : old_proxy_hosts) for (auto& it : old_proxy_hosts)
DeleteRenderFrameProxyHost(it.second->GetSiteInstance()); DeleteRenderFrameProxyHost(it.second->GetSiteInstance());
// Ensures RenderViewHosts are not reused while they are in the cache.
for (RenderViewHostImpl* rvh : old_render_view_hosts)
rvh->EnterBackForwardCache();
auto entry = std::make_unique<BackForwardCacheImpl::Entry>( auto entry = std::make_unique<BackForwardCacheImpl::Entry>(
std::move(old_render_frame_host), std::move(old_proxy_hosts)); std::move(old_render_frame_host), std::move(old_proxy_hosts),
std::move(old_render_view_hosts));
back_forward_cache.StoreEntry(std::move(entry)); back_forward_cache.StoreEntry(std::move(entry));
return; return;
} }
...@@ -2494,9 +2505,9 @@ void RenderFrameHostManager::CommitPending( ...@@ -2494,9 +2505,9 @@ void RenderFrameHostManager::CommitPending(
// If a document is being restored from the BackForwardCache, restore all // If a document is being restored from the BackForwardCache, restore all
// cached state now. // cached state now.
if (pending_bfcache_entry) { if (pending_bfcache_entry) {
RenderFrameProxyHostMap pending_proxy_hosts = RenderFrameProxyHostMap proxy_hosts_to_restore =
std::move(pending_bfcache_entry->proxy_hosts); std::move(pending_bfcache_entry->proxy_hosts);
for (auto& proxy : pending_proxy_hosts) { for (auto& proxy : proxy_hosts_to_restore) {
// We only cache pages when swapping BrowsingInstance, so we should never // We only cache pages when swapping BrowsingInstance, so we should never
// be reusing SiteInstances. // be reusing SiteInstances.
CHECK(!base::Contains(proxy_hosts_, CHECK(!base::Contains(proxy_hosts_,
...@@ -2505,6 +2516,11 @@ void RenderFrameHostManager::CommitPending( ...@@ -2505,6 +2516,11 @@ void RenderFrameHostManager::CommitPending(
->AddObserver(this); ->AddObserver(this);
proxy_hosts_.insert(std::move(proxy)); proxy_hosts_.insert(std::move(proxy));
} }
std::set<RenderViewHostImpl*> render_view_hosts_to_restore =
std::move(pending_bfcache_entry->render_view_hosts);
for (RenderViewHostImpl* rvh : render_view_hosts_to_restore)
rvh->LeaveBackForwardCache();
} }
// For top-level frames, the RenderWidget{Host} will not be destroyed when the // For top-level frames, the RenderWidget{Host} will not be destroyed when the
......
...@@ -224,10 +224,6 @@ RenderViewHostImpl::RenderViewHostImpl( ...@@ -224,10 +224,6 @@ RenderViewHostImpl::RenderViewHostImpl(
is_swapped_out_(swapped_out), is_swapped_out_(swapped_out),
routing_id_(routing_id), routing_id_(routing_id),
main_frame_routing_id_(main_frame_routing_id), main_frame_routing_id_(main_frame_routing_id),
is_waiting_for_close_ack_(false),
sudden_termination_allowed_(false),
updating_web_preferences_(false),
has_notified_about_creation_(false),
visual_properties_manager_(this) { visual_properties_manager_(this) {
DCHECK(instance_.get()); DCHECK(instance_.get());
CHECK(delegate_); // http://crbug.com/82827 CHECK(delegate_); // http://crbug.com/82827
...@@ -287,8 +283,12 @@ RenderViewHostImpl::~RenderViewHostImpl() { ...@@ -287,8 +283,12 @@ RenderViewHostImpl::~RenderViewHostImpl() {
// This can be called inside the FrameTree destructor. When the delegate is // This can be called inside the FrameTree destructor. When the delegate is
// the InterstialPageImpl, the |frame_tree| is set to null before deleting it. // the InterstialPageImpl, the |frame_tree| is set to null before deleting it.
if (FrameTree* frame_tree = GetDelegate()->GetFrameTree()) if (FrameTree* frame_tree = GetDelegate()->GetFrameTree()) {
frame_tree->RenderViewHostDeleted(this); // If |this| is in the BackForwardCache, then it was already removed from
// the FrameTree at the time it entered the BackForwardCache.
if (!is_in_back_forward_cache_)
frame_tree->UnregisterRenderViewHost(this);
}
} }
RenderViewHostDelegate* RenderViewHostImpl::GetDelegate() { RenderViewHostDelegate* RenderViewHostImpl::GetDelegate() {
...@@ -405,6 +405,20 @@ void RenderViewHostImpl::SetMainFrameRoutingId(int routing_id) { ...@@ -405,6 +405,20 @@ void RenderViewHostImpl::SetMainFrameRoutingId(int routing_id) {
GetWidget()->UpdatePriority(); GetWidget()->UpdatePriority();
} }
void RenderViewHostImpl::EnterBackForwardCache() {
FrameTree* frame_tree = GetDelegate()->GetFrameTree();
frame_tree->UnregisterRenderViewHost(this);
is_in_back_forward_cache_ = true;
}
void RenderViewHostImpl::LeaveBackForwardCache() {
FrameTree* frame_tree = GetDelegate()->GetFrameTree();
// At this point, the frames |this| RenderViewHostImpl belongs to are
// guaranteed to be committed, so it should be reused going forward.
frame_tree->RegisterRenderViewHost(this);
is_in_back_forward_cache_ = false;
}
bool RenderViewHostImpl::IsRenderViewLive() { bool RenderViewHostImpl::IsRenderViewLive() {
return GetProcess()->IsInitializedAndNotDead() && return GetProcess()->IsInitializedAndNotDead() &&
GetWidget()->renderer_initialized(); GetWidget()->renderer_initialized();
......
...@@ -207,6 +207,15 @@ class CONTENT_EXPORT RenderViewHostImpl ...@@ -207,6 +207,15 @@ class CONTENT_EXPORT RenderViewHostImpl
// view is not considered active. // view is not considered active.
void SetMainFrameRoutingId(int routing_id); void SetMainFrameRoutingId(int routing_id);
// Called when the RenderFrameHostImpls/RenderFrameProxyHosts that own this
// RenderViewHost enter the BackForwardCache.
void EnterBackForwardCache();
// Called when the RenderFrameHostImpls/RenderFrameProxyHosts that own this
// RenderViewHost leave the BackForwardCache. This occurs immediately before a
// restored document is committed.
void LeaveBackForwardCache();
// Called during frame eviction to return all SurfaceIds in the frame tree. // Called during frame eviction to return all SurfaceIds in the frame tree.
// Marks all views in the frame tree as evicted. // Marks all views in the frame tree as evicted.
std::vector<viz::SurfaceId> CollectSurfaceIdsForEviction(); std::vector<viz::SurfaceId> CollectSurfaceIdsForEviction();
...@@ -329,10 +338,10 @@ class CONTENT_EXPORT RenderViewHostImpl ...@@ -329,10 +338,10 @@ class CONTENT_EXPORT RenderViewHostImpl
// Set to true when waiting for a ViewHostMsg_ClosePageACK. // Set to true when waiting for a ViewHostMsg_ClosePageACK.
// TODO(creis): Move to RenderFrameHost and RenderWidgetHost. // TODO(creis): Move to RenderFrameHost and RenderWidgetHost.
// See http://crbug.com/418265. // See http://crbug.com/418265.
bool is_waiting_for_close_ack_; bool is_waiting_for_close_ack_ = false;
// True if the render view can be shut down suddenly. // True if the render view can be shut down suddenly.
bool sudden_termination_allowed_; bool sudden_termination_allowed_ = false;
// This is updated every time UpdateWebkitPreferences is called. That method // This is updated every time UpdateWebkitPreferences is called. That method
// is in turn called when any of the settings change that the WebPreferences // is in turn called when any of the settings change that the WebPreferences
...@@ -348,14 +357,17 @@ class CONTENT_EXPORT RenderViewHostImpl ...@@ -348,14 +357,17 @@ class CONTENT_EXPORT RenderViewHostImpl
// This monitors input changes so they can be reflected to the interaction MQ. // This monitors input changes so they can be reflected to the interaction MQ.
std::unique_ptr<InputDeviceChangeObserver> input_device_change_observer_; std::unique_ptr<InputDeviceChangeObserver> input_device_change_observer_;
bool updating_web_preferences_; bool updating_web_preferences_ = false;
// This tracks whether this RenderViewHost has notified observers about its // This tracks whether this RenderViewHost has notified observers about its
// creation with RenderViewCreated. RenderViewHosts may transition from // creation with RenderViewCreated. RenderViewHosts may transition from
// active (with a RenderFrameHost for the main frame) to inactive state and // active (with a RenderFrameHost for the main frame) to inactive state and
// then back to active, and for the latter transition, this avoids firing // then back to active, and for the latter transition, this avoids firing
// duplicate RenderViewCreated events. // duplicate RenderViewCreated events.
bool has_notified_about_creation_; bool has_notified_about_creation_ = false;
// BackForwardCache:
bool is_in_back_forward_cache_ = false;
// Used to send Page and Widget visual properties to Renderers. // Used to send Page and Widget visual properties to Renderers.
VisualPropertiesManager visual_properties_manager_; VisualPropertiesManager visual_properties_manager_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment