Navigaor.vrEnabled
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index ea4bb0d..5defc72 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -7355,6 +7355,99 @@
   EXPECT_TRUE(is_fullscreen_allowed(root->child_at(0)->child_at(0)));
 }
 
+// Check that out-of-process frames correctly calculate their ability to enter
+// VR. A frame is allowed enter VR if the allowVR attribute is present in all of
+// its ancestor <iframe> elements.  For OOPIF, when a parent frame changes this
+// attribute, the change is replicated to the child frame and its proxies.
+//
+// The test checks the following cases:
+//
+// 1. Static attribute (<iframe allowvr>)
+// 2. Attribute injected dynamically via JavaScript
+// 3. Multiple levels of nesting (A-embed-B-embed-C)
+// 4. Cross-site subframe navigation
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, AllowVR) {
+  // Load a page with a cross-site <iframe allowvr>.
+  GURL url_1(embedded_test_server()->GetURL(
+      "a.com", "/page_with_allowvr_frame.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), url_1));
+
+  WebContentsImpl* contents = web_contents();
+  FrameTreeNode* root = contents->GetFrameTree()->root();
+
+  // Helper to check if a frame is allowed to go VR on the renderer
+  // side.
+  auto is_vr_allowed = [](FrameTreeNode* ftn) {
+    bool vr_allowed = false;
+    EXPECT_TRUE(ExecuteScriptAndExtractBool(
+        ftn,
+        "window.domAutomationController.send(navigator.vrEnabled)",
+        &vr_allowed));
+    return vr_allowed;
+  };
+
+  EXPECT_TRUE(is_vr_allowed(root));
+  EXPECT_TRUE(is_vr_allowed(root->child_at(0)));
+  EXPECT_TRUE(root->child_at(0)->frame_owner_properties().allow_vr);
+
+  // Now navigate to a page with two <iframe>'s, both without allowvr.
+  GURL url_2(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b,c)"));
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+  EXPECT_FALSE(root->child_at(0)->frame_owner_properties().allow_vr);
+  EXPECT_FALSE(root->child_at(1)->frame_owner_properties().allow_vr);
+
+  EXPECT_TRUE(is_vr_allowed(root));
+  EXPECT_FALSE(is_vr_allowed(root->child_at(0)));
+  EXPECT_FALSE(is_vr_allowed(root->child_at(1)));
+
+  // Dynamically enable VR for first subframe and check that the
+  // VR property was updated on the FrameTreeNode.
+  EXPECT_TRUE(ExecuteScript(
+      root, "document.getElementById('child-0').allowVR='true'"));
+  EXPECT_TRUE(root->child_at(0)->frame_owner_properties().allow_vr);
+
+  // Check that the first subframe is now allowed to go VR.  Other
+  // frames shouldn't be affected.
+  EXPECT_TRUE(is_vr_allowed(root));
+  EXPECT_TRUE(is_vr_allowed(root->child_at(0)));
+  EXPECT_FALSE(is_vr_allowed(root->child_at(1)));
+
+  // Now navigate to a page with two levels of nesting.
+  GURL url_3(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b(c))"));
+  EXPECT_TRUE(NavigateToURL(shell(), url_3));
+
+  EXPECT_TRUE(is_vr_allowed(root));
+  EXPECT_FALSE(is_vr_allowed(root->child_at(0)));
+  EXPECT_FALSE(is_vr_allowed(root->child_at(0)->child_at(0)));
+
+  // Dynamically enable vr for bottom subframe.
+  EXPECT_TRUE(ExecuteScript(
+      root->child_at(0),
+      "document.getElementById('child-0').allowVR='true'"));
+
+  // This still shouldn't allow the bottom child to go VR, since the
+  // top frame hasn't allowed VR for the middle frame.
+  EXPECT_TRUE(is_vr_allowed(root));
+  EXPECT_FALSE(is_vr_allowed(root->child_at(0)));
+  EXPECT_FALSE(is_vr_allowed(root->child_at(0)->child_at(0)));
+
+  // Now allow vr for the middle frame.
+  EXPECT_TRUE(ExecuteScript(
+      root, "document.getElementById('child-0').allowVR='true'"));
+
+  // All frames should be allowed to go VR now.
+  EXPECT_TRUE(is_vr_allowed(root));
+  EXPECT_TRUE(is_vr_allowed(root->child_at(0)));
+  EXPECT_TRUE(is_vr_allowed(root->child_at(0)->child_at(0)));
+
+  // Cross-site navigation should preserve the VR flags.
+  NavigateFrameToURL(root->child_at(0)->child_at(0),
+                     embedded_test_server()->GetURL("d.com", "/title1.html"));
+  EXPECT_TRUE(is_vr_allowed(root->child_at(0)->child_at(0)));
+}
+
 // Test for https://crbug.com/615575. It ensures that file chooser triggered
 // by a document in an out-of-process subframe works properly.
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, FileChooserInSubframe) {
diff --git a/content/test/data/page_with_allowvr_frame.html b/content/test/data/page_with_allowvr_frame.html
new file mode 100644
index 0000000..2a100e6
--- /dev/null
+++ b/content/test/data/page_with_allowvr_frame.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+  This page has one cross-site iframe with permission to enter VR.
+  <iframe id="child-0" src="/cross-site/b.com/title1.html" allowvr></iframe>
+</body>
+</html>
diff --git a/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp b/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp
index 203cfde..be6f067 100644
--- a/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp
+++ b/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp
@@ -98,6 +98,16 @@
     return promise;
 }
 
+bool NavigatorVR::vrEnabled(Navigator& navigator)
+{
+    return NavigatorVR::from(navigator).vrEnabled();
+}
+
+bool NavigatorVR::vrEnabled()
+{
+    return allowedToUseVR(m_frame);
+}
+
 VRController* NavigatorVR::controller()
 {
     if (!frame())
diff --git a/third_party/WebKit/Source/modules/vr/NavigatorVR.h b/third_party/WebKit/Source/modules/vr/NavigatorVR.h
index 30d5f67..dd2c5f8 100644
--- a/third_party/WebKit/Source/modules/vr/NavigatorVR.h
+++ b/third_party/WebKit/Source/modules/vr/NavigatorVR.h
@@ -27,11 +27,15 @@
 public:
     static NavigatorVR* from(Document&);
     static NavigatorVR& from(Navigator&);
+
     virtual ~NavigatorVR();
 
     static ScriptPromise getVRDisplays(ScriptState*, Navigator&);
     ScriptPromise getVRDisplays(ScriptState*);
 
+    static bool vrEnabled(Navigator&);
+    bool vrEnabled();
+
     VRController* controller();
     Document* document();
 
diff --git a/third_party/WebKit/Source/modules/vr/NavigatorVR.idl b/third_party/WebKit/Source/modules/vr/NavigatorVR.idl
index 4e999ba..e6b2a77 100644
--- a/third_party/WebKit/Source/modules/vr/NavigatorVR.idl
+++ b/third_party/WebKit/Source/modules/vr/NavigatorVR.idl
@@ -7,4 +7,5 @@
     RuntimeEnabled=WebVR,
 ] partial interface Navigator {
     [CallWith=ScriptState] Promise getVRDisplays();
+    readonly attribute boolean vrEnabled;
 };