910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <optional>
#include <string>

#include "chrome/browser/controlled_frame/controlled_frame_permission_request_test_base.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::StartsWith;
using testing::UnorderedElementsAre;

namespace controlled_frame {

class ControlledFrameNewWindowBrowserTest
    : public ControlledFrameTestBase,
      public testing::WithParamInterface<bool> {
 public:
  void SetUpOnMainThread() override {
    ControlledFrameTestBase::SetUpOnMainThread();
    StartContentServer("web_apps/simple_isolated_app");
  }
};

IN_PROC_BROWSER_TEST_F(ControlledFrameNewWindowBrowserTest, AttachSucceeds) {
  auto [app_frame, controlled_frame] =
      InstallAndOpenIwaThenCreateControlledFrame(
          /*controlled_frame_host_name=*/std::nullopt,
          "/controlled_frame.html");

  auto test_script = content::JsReplace(
      R"(
(async function() {
  try {
    await new Promise((resolve, reject) => {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame) {
        reject('Could not find a controlledframe element.');
      }
      frame.addEventListener('newwindow', (e) => {
        if (!e.window.attach) {
          reject('window.attach does not exist ');
        }
        const newcontrolledframe = document.createElement('controlledframe');
        // Attach the new window to the new <controlledframe>.
        try {
          newcontrolledframe.addEventListener(
              'loadstop', resolve);
          newcontrolledframe.addEventListener(
              'loadabort', reject);
          e.window.attach(newcontrolledframe);
          document.body.appendChild(newcontrolledframe);
        } catch (err) {
          reject(err.message);
        }
      });
      frame.executeScript({code: 'window.open($1);'});
    });

    const frames = document.getElementsByTagName('controlledframe');
    if (frames.length !== 2) {
      return [
        'FAIL: expected 2 <controlledframe> elements, found ' + frames.length
      ];
    }

    async function getCurrentLocationOfControlledFrame(frame) {
      const result = await frame.executeScript({code: 'window.location.href;'});
      if (!result) {
        return 'FAIL: executeScript() returned no result';
      }
      return result[0];
    };

    return [
      await getCurrentLocationOfControlledFrame(frames[0]),
      await getCurrentLocationOfControlledFrame(frames[1]),
    ];
  } catch (e) {
    return ['FAIL: ' + e.message];
  }
})();
    )",
      embedded_https_test_server().GetURL("/index.html"));

  EXPECT_THAT(
      content::EvalJs(app_frame, test_script).TakeValue().TakeList(),
      UnorderedElementsAre(
          embedded_https_test_server().GetURL("/controlled_frame.html").spec(),
          embedded_https_test_server().GetURL("/index.html").spec()));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameNewWindowBrowserTest, DiscardSucceeds) {
  auto [app_frame, controlled_frame] =
      InstallAndOpenIwaThenCreateControlledFrame(
          /*controlled_frame_host_name=*/std::nullopt,
          "/controlled_frame.html");

  std::string test_script =
      content::JsReplace(R"(
(async function() {
  try {
    return await new Promise ((resolve) => {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame) {
        resolve('FAIL: Could not find a controlledframe element.');
      }
      frame.addEventListener('newwindow', (e) => {
        try {
          e.window.discard();
          resolve('SUCCESS');
        } catch (err) {
          resolve('FAIL: ' + err.message);
        }
      });
      frame.executeScript({code: 'window.open($1);'});
    });
  } catch (err) {
    return "FAIL: " + err.message;
  }
})();
    )",
                         embedded_https_test_server().GetURL("/index.html"));

  ASSERT_EQ("SUCCESS", content::EvalJs(app_frame, test_script));

  EXPECT_EQ(1, content::EvalJs(
                   app_frame,
                   "document.getElementsByTagName('controlledframe').length;"));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameNewWindowBrowserTest,
                       PostMessageAfterAttachSucceeds) {
  auto [app_frame, controlled_frame] =
      InstallAndOpenIwaThenCreateControlledFrame(
          /*controlled_frame_host_name=*/std::nullopt,
          "/controlled_frame.html");

  auto test_script = content::JsReplace(
      R"(
async function executeScriptOnFrame(frame, script) {
  const result = await frame.executeScript({code: script});
  if (!result) {
    throw new Error('executeScript returned no result');
  }
  if (result[0] !== 'SUCCESS') {
    throw new Error('expected SUCCESS but got ' + result[0]);
  }
  return 'SUCCESS';
};

(async function() {
  try {
    await new Promise((resolve, reject) => {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame) {
        reject('Could not find a controlledframe element.');
      }

      frame.addEventListener('newwindow', (e) => {
        if (!e.window.attach) {
          reject('window.attach does not exist ');
        }
        const newcontrolledframe = document.createElement('controlledframe');
        // Attach the new window to the new <controlledframe>.
        try {
          newcontrolledframe.addEventListener('loadstop', resolve);
          newcontrolledframe.addEventListener('loadabort', reject);
          e.window.attach(newcontrolledframe);
          document.body.appendChild(newcontrolledframe);
        } catch (err) {
          reject(err.message);
        }
      });
      frame.executeScript({code: 'document.openedWindow = window.open($1);'});
    });

    const listenscript = `
      (function() {
        window.addEventListener('message', (e) => {document.lastMessage = e;});
        return 'SUCCESS';
      })();
    `;
    const sendscript = `
      (function() {
        const target = document.openedWindow || window.opener;
        if (target === null) {
          return 'missing postMessage target';
        }
        target.postMessage('hello test');
        return 'SUCCESS';
      })();
    `;
    const verifyscript = `
      (function() {
        if (!document.lastMessage) {
          return 'no message received';
        }
        if (document.lastMessage.data !== 'hello test') {
          return 'unexpected lastMessage\\nexpected: hello test\\nactual: ' +
              document.lastMessage.data;
        }
        return 'SUCCESS';
      })();
    `;

    const frames = Array.from(document.getElementsByTagName('controlledframe'));
    if (frames.length !== 2) {
      throw new Error(
          'expected 2 <controlledframe> elements, found ' + frames.length);
    }
    for (const frame of frames) {
      await executeScriptOnFrame(frame, listenscript);
    }
    for (const frame of frames) {
      await executeScriptOnFrame(frame, sendscript);
    }
    // Trigger resolve() in 0.1s after sending out messages.
    await new Promise((resolve) => {
      setTimeout(resolve, 100);
    });
    for (const frame of frames) {
      await executeScriptOnFrame(frame, verifyscript);
    }
    return 'SUCCESS';
  } catch (e) {
    return 'FAIL: ' + e.message;
  }
})();
    )",
      embedded_https_test_server().GetURL("/index.html"));

  EXPECT_EQ("SUCCESS", content::EvalJs(app_frame, test_script));
}

}  // namespace controlled_frame