diff options
author | slightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 05:11:58 +0000 |
---|---|---|
committer | slightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 05:11:58 +0000 |
commit | f781782dd67077478e117c61dca4ea5eefce3544 (patch) | |
tree | 4801f724123cfdcbb69c4e7fe40a565b331723ae /chrome_frame/test | |
parent | 63cf4759efa2373e33436fb5df6849f930081226 (diff) | |
download | chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.zip chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.tar.gz chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.tar.bz2 |
Initial import of the Chrome Frame codebase. Integration in chrome.gyp coming in a separate CL.
BUG=None
TEST=None
Review URL: http://codereview.chromium.org/218019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@27042 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame/test')
83 files changed, 9625 insertions, 0 deletions
diff --git a/chrome_frame/test/ChromeTab_UnitTests.vcproj b/chrome_frame/test/ChromeTab_UnitTests.vcproj new file mode 100644 index 0000000..9f4369b --- /dev/null +++ b/chrome_frame/test/ChromeTab_UnitTests.vcproj @@ -0,0 +1,273 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometab_unittests"
+ ProjectGUID="{BA08FE92-567D-4411-B344-17ADAECA2B5A}"
+ RootNamespace="ChromeTab_UnitTests"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;.\chrometab_unittests.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)..\build\release.vsprops;.\chrometab_unittests.vsprops"
+ UseOfATL="1"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ OmitFramePointers="false"
+ WholeProgramOptimization="false"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;"
+ StringPooling="true"
+ BasicRuntimeChecks="0"
+ RuntimeLibrary="0"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories="$(ConfigurationName)\lib;"
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ OptimizeForWindows98="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <Filter
+ Name="Common"
+ >
+ <File
+ RelativePath=".\cf_test_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\cf_test_utils.h"
+ >
+ </File>
+ <File
+ RelativePath=".\chrometab_unittests.h"
+ >
+ </File>
+ <File
+ RelativePath=".\run_all_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\test_suite.h"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Tests"
+ >
+ <File
+ RelativePath="..\chrome_frame_automation.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\chrometab_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\com_message_event.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\com_message_event_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\function_stub_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\html_util_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\html_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\icu_stubs_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\util_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\utils.cc"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/chrome_frame/test/chrome_frame_automation_mock.cc b/chrome_frame/test/chrome_frame_automation_mock.cc new file mode 100644 index 0000000..d900176 --- /dev/null +++ b/chrome_frame/test/chrome_frame_automation_mock.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/chrome_frame_automation_mock.h" +#include "testing/gtest/include/gtest/gtest.h" + +const int kLongWaitTimeout = 25 * 1000; +const int kShortWaitTimeout = 5 * 1000; + +TEST(ChromeFrame, Launch) { + MessageLoopForUI loop; + AutomationMockLaunch mock_launch(&loop, kLongWaitTimeout); + + mock_launch.Navigate("about:blank"); + loop.Run(NULL); + EXPECT_EQ(true, mock_launch.launch_result()); +} + +TEST(ChromeFrame, Navigate) { + MessageLoopForUI loop; + AutomationMockNavigate mock_navigate(&loop, kLongWaitTimeout); + + mock_navigate.NavigateRelativeFile(L"postmessage_basic_frame.html"); + loop.Run(NULL); + EXPECT_EQ(true, mock_navigate.navigation_result()); +} + +TEST(ChromeFrame, PostMessage) { + MessageLoopForUI loop; + AutomationMockPostMessage mock_postmessage(&loop, kLongWaitTimeout); + + mock_postmessage.NavigateRelativeFile(L"postmessage_basic_frame.html"); + loop.Run(NULL); + EXPECT_EQ(true, mock_postmessage.postmessage_result()); +} + +TEST(ChromeFrame, RequestStart) { + MessageLoopForUI loop; + AutomationMockHostNetworkRequestStart mock_request_start(&loop, + kLongWaitTimeout); + + mock_request_start.NavigateRelative(L"postmessage_basic_frame.html"); + loop.Run(NULL); + EXPECT_EQ(true, mock_request_start.request_start_result()); +} + diff --git a/chrome_frame/test/chrome_frame_automation_mock.h b/chrome_frame/test/chrome_frame_automation_mock.h new file mode 100644 index 0000000..4c3fe7b --- /dev/null +++ b/chrome_frame/test/chrome_frame_automation_mock.h @@ -0,0 +1,208 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_ +#define CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/path_service.h" +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/chrome_frame_plugin.h" +#include "chrome_frame/test/http_server.h" + +template <typename T> +class AutomationMockDelegate + : public CWindowImpl<T>, + public ChromeFramePlugin<T> { + public: + AutomationMockDelegate(MessageLoop* caller_message_loop, + int launch_timeout, bool perform_version_check, + const std::wstring& profile_name, + const std::wstring& extra_chrome_arguments, bool incognito) + : caller_message_loop_(caller_message_loop), is_connected_(false) { + test_server_.SetUp(); + automation_client_.reset(new ChromeFrameAutomationClient); + automation_client_->Initialize(this, launch_timeout, perform_version_check, + profile_name, extra_chrome_arguments, incognito); + } + ~AutomationMockDelegate() { + if (automation_client_.get()) { + automation_client_->Uninitialize(); + automation_client_.reset(); + } + if (IsWindow()) + DestroyWindow(); + + test_server_.TearDown(); + } + + // Navigate external tab to the specified url through automation + bool Navigate(const std::string& url) { + url_ = GURL(url); + return automation_client_->InitiateNavigation(url); + } + + // Navigate the external to a 'file://' url for unit test files + bool NavigateRelativeFile(const std::wstring& file) { + FilePath cf_source_path; + PathService::Get(base::DIR_SOURCE_ROOT, &cf_source_path); + std::wstring file_url(L"file://"); + file_url.append(cf_source_path.Append( + FILE_PATH_LITERAL("chrome_frame")).Append( + FILE_PATH_LITERAL("test")).Append( + FILE_PATH_LITERAL("data")).Append(file).value()); + return Navigate(WideToUTF8(file_url)); + } + + bool NavigateRelative(const std::wstring& relative_url) { + return Navigate(test_server_.Resolve(relative_url.c_str()).spec()); + } + + virtual void OnAutomationServerReady() { + if (automation_client_.get()) { + Create(NULL, 0, NULL, WS_OVERLAPPEDWINDOW); + DCHECK(IsWindow()); + is_connected_ = true; + } else { + NOTREACHED(); + } + } + + virtual void OnAutomationServerLaunchFailed() { + QuitMessageLoop(); + } + + virtual void OnLoad(int tab_handle, const GURL& url) { + if (url_ == url) { + navigation_result_ = true; + } else { + QuitMessageLoop(); + } + } + + virtual void OnLoadFailed(int error_code, const std::string& url) { + QuitMessageLoop(); + } + + ChromeFrameAutomationClient* automation() { + return automation_client_.get(); + } + const GURL& url() const { + return url_; + } + bool is_connected() const { + return is_connected_; + } + bool navigation_result() const { + return navigation_result_; + } + + BEGIN_MSG_MAP(AutomationMockDelegate) + END_MSG_MAP() + + protected: + void QuitMessageLoop() { + // Quit on the caller message loop has to be called on the caller + // thread. + caller_message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask); + } + + private: + ChromeFrameHTTPServer test_server_; + MessageLoop* caller_message_loop_; + GURL url_; + bool is_connected_; + bool navigation_result_; +}; + +class AutomationMockLaunch + : public AutomationMockDelegate<AutomationMockLaunch> { + public: + typedef AutomationMockDelegate<AutomationMockLaunch> Base; + AutomationMockLaunch(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false) { + } + virtual void OnAutomationServerReady() { + Base::OnAutomationServerReady(); + QuitMessageLoop(); + } + bool launch_result() const { + return is_connected(); + } +}; + +class AutomationMockNavigate + : public AutomationMockDelegate<AutomationMockNavigate> { + public: + typedef AutomationMockDelegate<AutomationMockNavigate> Base; + AutomationMockNavigate(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false) { + } + virtual void OnLoad(int tab_handle, const GURL& url) { + Base::OnLoad(tab_handle, url); + QuitMessageLoop(); + } +}; + +class AutomationMockPostMessage + : public AutomationMockDelegate<AutomationMockPostMessage> { + public: + typedef AutomationMockDelegate<AutomationMockPostMessage> Base; + AutomationMockPostMessage(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false), + postmessage_result_(false) {} + bool postmessage_result() const { + return postmessage_result_; + } + virtual void OnLoad(int tab_handle, const GURL& url) { + Base::OnLoad(tab_handle, url); + if (navigation_result()) { + automation()->ForwardMessageFromExternalHost("Test", "null", "*"); + } + } + virtual void OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target) { + postmessage_result_ = true; + QuitMessageLoop(); + } + private: + bool postmessage_result_; +}; + +class AutomationMockHostNetworkRequestStart + : public AutomationMockDelegate<AutomationMockHostNetworkRequestStart> { + public: + typedef AutomationMockDelegate<AutomationMockHostNetworkRequestStart> Base; + AutomationMockHostNetworkRequestStart(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false), + request_start_result_(false) { + if (automation()) { + automation()->set_use_chrome_network(false); + } + } + bool request_start_result() const { + return request_start_result_; + } + virtual void OnRequestStart(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + request_start_result_ = true; + QuitMessageLoop(); + } + virtual void OnLoad(int tab_handle, const GURL& url) { + Base::OnLoad(tab_handle, url); + } + private: + bool request_start_result_; +}; + + +#endif // CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_ + diff --git a/chrome_frame/test/chrome_frame_test_utils.cc b/chrome_frame/test/chrome_frame_test_utils.cc new file mode 100644 index 0000000..a75d791 --- /dev/null +++ b/chrome_frame/test/chrome_frame_test_utils.cc @@ -0,0 +1,416 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/chrome_frame_test_utils.h" + +#include <atlbase.h> +#include <atlwin.h> +#include <iepmapi.h> + +#include "base/registry.h" // to find IE and firefox +#include "base/scoped_handle.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/common/chrome_switches.h" + +namespace chrome_frame_test { + +const wchar_t kIEImageName[] = L"iexplore.exe"; +const wchar_t kIEBrokerImageName[] = L"ieuser.exe"; +const wchar_t kFirefoxImageName[] = L"firefox.exe"; +const wchar_t kOperaImageName[] = L"opera.exe"; +const wchar_t kSafariImageName[] = L"safari.exe"; +const wchar_t kChromeImageName[] = L"chrome.exe"; + +bool IsTopLevelWindow(HWND window) { + long style = GetWindowLong(window, GWL_STYLE); // NOLINT + if (!(style & WS_CHILD)) + return true; + + HWND parent = GetParent(window); + if (!parent) + return true; + + if (parent == GetDesktopWindow()) + return true; + + return false; +} + +// Callback function for EnumThreadWindows. +BOOL CALLBACK CloseWindowsThreadCallback(HWND hwnd, LPARAM param) { + int& count = *reinterpret_cast<int*>(param); + if (IsWindowVisible(hwnd)) { + if (IsWindowEnabled(hwnd)) { + DWORD results = 0; + if (!::SendMessageTimeout(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0, SMTO_BLOCK, + 10000, &results)) { + DLOG(WARNING) << "Window hung: " << StringPrintf(L"%08X", hwnd); + } + count++; + } else { + DLOG(WARNING) << "Skipping disabled window: " + << StringPrintf(L"%08X", hwnd); + } + } + return TRUE; // continue enumeration +} + +// Attempts to close all non-child, visible windows on the given thread. +// The return value is the number of visible windows a close request was +// sent to. +int CloseVisibleTopLevelWindowsOnThread(DWORD thread_id) { + int window_close_attempts = 0; + EnumThreadWindows(thread_id, CloseWindowsThreadCallback, + reinterpret_cast<LPARAM>(&window_close_attempts)); + return window_close_attempts; +} + +// Enumerates the threads of a process and attempts to close visible non-child +// windows on all threads of the process. +// The return value is the number of visible windows a close request was +// sent to. +int CloseVisibleWindowsOnAllThreads(HANDLE process) { + DWORD process_id = ::GetProcessId(process); + if (process_id == 0) { + NOTREACHED(); + return 0; + } + + ScopedHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)); + if (!snapshot.IsValid()) { + NOTREACHED(); + return 0; + } + + int window_close_attempts = 0; + THREADENTRY32 te = { sizeof(THREADENTRY32) }; + if (Thread32First(snapshot, &te)) { + do { + if (RTL_CONTAINS_FIELD(&te, te.dwSize, th32OwnerProcessID) && + te.th32OwnerProcessID == process_id) { + window_close_attempts += + CloseVisibleTopLevelWindowsOnThread(te.th32ThreadID); + } + te.dwSize = sizeof(te); + } while (Thread32Next(snapshot, &te)); + } + + return window_close_attempts; +} + +class ForegroundHelperWindow : public CWindowImpl<ForegroundHelperWindow> { + public: +BEGIN_MSG_MAP(ForegroundHelperWindow) + MESSAGE_HANDLER(WM_HOTKEY, OnHotKey) +END_MSG_MAP() + + HRESULT SetForeground(HWND window) { + DCHECK(::IsWindow(window)); + if (NULL == Create(NULL, NULL, NULL, WS_POPUP)) + return AtlHresultFromLastError(); + + static const int hotkey_id = 0x0000baba; + + SetWindowLongPtr(GWLP_USERDATA, reinterpret_cast<ULONG_PTR>(window)); + RegisterHotKey(m_hWnd, hotkey_id, 0, VK_F22); + + MSG msg = {0}; + PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); + + INPUT hotkey = {0}; + hotkey.type = INPUT_KEYBOARD; + hotkey.ki.wVk = VK_F22; + SendInput(1, &hotkey, sizeof(hotkey)); + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + if (WM_HOTKEY == msg.message) + break; + } + + UnregisterHotKey(m_hWnd, hotkey_id); + DestroyWindow(); + + return S_OK; + } + + LRESULT OnHotKey(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled) { // NOLINT + HWND window = reinterpret_cast<HWND>(GetWindowLongPtr(GWLP_USERDATA)); + SetForegroundWindow(window); + return 1; + } +}; + +bool ForceSetForegroundWindow(HWND window) { + if (GetForegroundWindow() == window) + return true; + ForegroundHelperWindow foreground_helper_window; + HRESULT hr = foreground_helper_window.SetForeground(window); + return SUCCEEDED(hr); +} + +struct PidAndWindow { + base::ProcessId pid; + HWND hwnd; +}; + +BOOL CALLBACK FindWindowInProcessCallback(HWND hwnd, LPARAM param) { + PidAndWindow* paw = reinterpret_cast<PidAndWindow*>(param); + base::ProcessId pid; + GetWindowThreadProcessId(hwnd, &pid); + if (pid == paw->pid && IsWindowVisible(hwnd)) { + paw->hwnd = hwnd; + return FALSE; + } + + return TRUE; +} + +bool EnsureProcessInForeground(base::ProcessId process_id) { + HWND hwnd = GetForegroundWindow(); + base::ProcessId current_foreground_pid = 0; + DWORD active_thread_id = GetWindowThreadProcessId(hwnd, + ¤t_foreground_pid); + if (current_foreground_pid == process_id) + return true; + + PidAndWindow paw = { process_id }; + EnumWindows(FindWindowInProcessCallback, reinterpret_cast<LPARAM>(&paw)); + if (!IsWindow(paw.hwnd)) { + DLOG(ERROR) << "failed to find process window"; + return false; + } + + bool ret = ForceSetForegroundWindow(paw.hwnd); + DLOG_IF(ERROR, !ret) << "ForceSetForegroundWindow: " << ret; + + return ret; +} + +// Iterates through all the characters in the string and simulates +// keyboard input. The input goes to the currently active application. +bool SendString(const wchar_t* string) { + DCHECK(string != NULL); + + INPUT input[2] = {0}; + input[0].type = INPUT_KEYBOARD; + input[0].ki.dwFlags = KEYEVENTF_UNICODE; // to avoid shift, etc. + input[1] = input[0]; + input[1].ki.dwFlags |= KEYEVENTF_KEYUP; + + for (const wchar_t* p = string; *p; p++) { + input[0].ki.wScan = input[1].ki.wScan = *p; + SendInput(2, input, sizeof(INPUT)); + } + + return true; +} + +void SendVirtualKey(int16 key) { + INPUT input = { INPUT_KEYBOARD }; + input.ki.wVk = key; + SendInput(1, &input, sizeof(input)); + input.ki.dwFlags = KEYEVENTF_KEYUP; + SendInput(1, &input, sizeof(input)); +} + +void SendChar(char c) { + SendVirtualKey(VkKeyScanA(c)); +} + +void SendString(const char* s) { + while (*s) { + SendChar(*s); + s++; + } +} + +// Sends a keystroke to the currently active application with optional +// modifiers set. +bool SendMnemonic(WORD mnemonic_char, bool shift_pressed, bool control_pressed, + bool alt_pressed) { + INPUT special_keys[3] = {0}; + for (int index = 0; index < arraysize(special_keys); ++index) { + special_keys[index].type = INPUT_KEYBOARD; + special_keys[index].ki.dwFlags = 0; + } + + int num_special_keys = 0; + if (shift_pressed) { + special_keys[num_special_keys].ki.wVk = VK_SHIFT; + num_special_keys++; + } + + if (control_pressed) { + special_keys[num_special_keys].ki.wVk = VK_CONTROL; + num_special_keys++; + } + + if (alt_pressed) { + special_keys[num_special_keys].ki.wVk = VK_MENU; + num_special_keys++; + } + + // Depress the modifiers. + SendInput(num_special_keys, special_keys, sizeof(INPUT)); + + Sleep(100); + + INPUT mnemonic = {0}; + mnemonic.type = INPUT_KEYBOARD; + mnemonic.ki.wVk = mnemonic_char; + + // Depress and release the mnemonic. + SendInput(1, &mnemonic, sizeof(INPUT)); + Sleep(100); + + mnemonic.ki.dwFlags |= KEYEVENTF_KEYUP; + SendInput(1, &mnemonic, sizeof(INPUT)); + Sleep(100); + + // Now release the modifiers. + for (int index = 0; index < num_special_keys; index++) { + special_keys[index].ki.dwFlags |= KEYEVENTF_KEYUP; + } + + SendInput(num_special_keys, special_keys, sizeof(INPUT)); + Sleep(100); + + return true; +} + +std::wstring GetExecutableAppPath(const std::wstring& file) { + std::wstring kAppPathsKey = + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\"; + + std::wstring app_path; + RegKey key(HKEY_LOCAL_MACHINE, (kAppPathsKey + file).c_str()); + if (key.Handle()) { + key.ReadValue(NULL, &app_path); + } + + return app_path; +} + +std::wstring FormatCommandForApp(const std::wstring& exe_name, + const std::wstring& argument) { + std::wstring reg_path(StringPrintf(L"Applications\\%ls\\shell\\open\\command", + exe_name.c_str())); + RegKey key(HKEY_CLASSES_ROOT, reg_path.c_str()); + + std::wstring command; + if (key.Handle()) { + key.ReadValue(NULL, &command); + int found = command.find(L"%1"); + if (found >= 0) { + command.replace(found, 2, argument); + } + } + return command; +} + +base::ProcessHandle LaunchExecutable(const std::wstring& executable, + const std::wstring& argument) { + base::ProcessHandle process = NULL; + std::wstring path = GetExecutableAppPath(executable); + if (path.empty()) { + path = FormatCommandForApp(executable, argument); + if (path.empty()) { + DLOG(ERROR) << "Failed to find executable: " << executable; + } else { + CommandLine cmdline(L""); + cmdline.ParseFromString(path); + base::LaunchApp(cmdline, false, false, &process); + } + } else { + CommandLine cmdline(path); + cmdline.AppendLooseValue(argument); + base::LaunchApp(cmdline, false, false, &process); + } + return process; +} + +base::ProcessHandle LaunchFirefox(const std::wstring& url) { + return LaunchExecutable(kFirefoxImageName, url); +} + +base::ProcessHandle LaunchSafari(const std::wstring& url) { + return LaunchExecutable(kSafariImageName, url); +} + +base::ProcessHandle LaunchChrome(const std::wstring& url) { + return LaunchExecutable(kChromeImageName, + StringPrintf(L"--%ls ", switches::kNoFirstRun) + url); +} + +base::ProcessHandle LaunchOpera(const std::wstring& url) { + // NOTE: For Opera tests to work it must be configured to start up with + // a blank page. There is an command line switch, -nosession, that's supposed + // to avoid opening up the previous session, but that switch is not working. + // TODO(tommi): Include a special ini file (opera6.ini) for opera and launch + // with our required settings. This file is by default stored here: + // "%USERPROFILE%\Application Data\Opera\Opera\profile\opera6.ini" + return LaunchExecutable(kOperaImageName, url); +} + +base::ProcessHandle LaunchIEOnVista(const std::wstring& url) { + typedef HRESULT (WINAPI* IELaunchURLPtr)( + const wchar_t* url, + PROCESS_INFORMATION *pi, + VOID *info); + + IELaunchURLPtr launch; + PROCESS_INFORMATION pi = {0}; + IELAUNCHURLINFO info = {sizeof info, 0}; + HMODULE h = LoadLibrary(L"ieframe.dll"); + if (!h) + return NULL; + launch = reinterpret_cast<IELaunchURLPtr>(GetProcAddress(h, "IELaunchURL")); + HRESULT hr = launch(url.c_str(), &pi, &info); + FreeLibrary(h); + if (SUCCEEDED(hr)) + CloseHandle(pi.hThread); + return pi.hProcess; +} + +base::ProcessHandle LaunchIE(const std::wstring& url) { + if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA) { + return LaunchIEOnVista(url); + } else { + return LaunchExecutable(kIEImageName, url); + } +} + +int CloseAllIEWindows() { + int ret = 0; + + ScopedComPtr<IShellWindows> windows; + HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL, + IID_IShellWindows, reinterpret_cast<void**>(windows.Receive())); + DCHECK(SUCCEEDED(hr)); + + if (SUCCEEDED(hr)) { + long count = 0; // NOLINT + windows->get_Count(&count); + VARIANT i = { VT_I4 }; + for (i.lVal = 0; i.lVal < count; ++i.lVal) { + ScopedComPtr<IDispatch> folder; + windows->Item(i, folder.Receive()); + if (folder != NULL) { + ScopedComPtr<IWebBrowser2> browser; + if (SUCCEEDED(browser.QueryFrom(folder))) { + browser->Quit(); + ++ret; + } + } + } + } + + return ret; +} + +} // namespace chrome_frame_test diff --git a/chrome_frame/test/chrome_frame_test_utils.h b/chrome_frame/test/chrome_frame_test_utils.h new file mode 100644 index 0000000..95e0c9b --- /dev/null +++ b/chrome_frame/test/chrome_frame_test_utils.h @@ -0,0 +1,61 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_ +#define CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/process_util.h" + +namespace chrome_frame_test { + +bool IsTopLevelWindow(HWND window); +int CloseVisibleWindowsOnAllThreads(HANDLE process); +bool ForceSetForegroundWindow(HWND window); +bool EnsureProcessInForeground(base::ProcessId process_id); + +// Iterates through all the characters in the string and simulates +// keyboard input. The input goes to the currently active application. +bool SendString(const wchar_t* s); + +// Sends a virtual key such as VK_TAB, VK_RETURN or a character that has been +// translated to a virtual key. +void SendVirtualKey(int16 key); + +// Translates a single char to a virtual key and calls SendVirtualKey. +void SendChar(char c); + +// Sends an ascii string, char by char (calls SendChar for each). +void SendString(const char* s); + +// Sends a keystroke to the currently active application with optional +// modifiers set. +bool SendMnemonic(WORD mnemonic_char, bool shift_pressed, bool control_pressed, + bool alt_pressed); + +base::ProcessHandle LaunchFirefox(const std::wstring& url); +base::ProcessHandle LaunchOpera(const std::wstring& url); +base::ProcessHandle LaunchIE(const std::wstring& url); +base::ProcessHandle LaunchSafari(const std::wstring& url); +base::ProcessHandle LaunchChrome(const std::wstring& url); + +// Attempts to close all open IE windows. +// The return value is the number of windows closed. +// @note: this function requires COM to be initialized on the calling thread. +// Since the caller might be running in either MTA or STA, the function does +// not perform this initialization itself. +int CloseAllIEWindows(); + +extern const wchar_t kIEImageName[]; +extern const wchar_t kIEBrokerImageName[]; +extern const wchar_t kFirefoxImageName[]; +extern const wchar_t kOperaImageName[]; +extern const wchar_t kSafariImageName[]; +extern const wchar_t kChromeImageName[]; + +} // namespace chrome_frame_test + +#endif // CHROME_FRAME_CHROMETAB_UNITTESTS_CF_TEST_UTILS_H_ diff --git a/chrome_frame/test/chrome_frame_unittests.cc b/chrome_frame/test/chrome_frame_unittests.cc new file mode 100644 index 0000000..20826b1 --- /dev/null +++ b/chrome_frame/test/chrome_frame_unittests.cc @@ -0,0 +1,1510 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include <windows.h> +#include <stdarg.h> + +// IShellWindows includes. Unfortunately we can't keep these in +// alphabetic order since exdisp will bark if some interfaces aren't fully +// defined. +#include <mshtml.h> +#include <exdisp.h> + +#include "base/basictypes.h" +#include "base/file_version_info.h" +#include "base/file_util.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" +#include "base/sys_info.h" +#include "gmock/gmock.h" +#include "net/url_request/url_request_unittest.h" +#include "chrome_frame/test/chrome_frame_unittests.h" +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/chrome_frame_delegate.h" +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/helper_gmock.h" +#include "chrome_frame/test_utils.h" +#include "chrome_frame/utils.h" +#include "chrome_frame/vectored_handler-impl.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/helper.h" + +const wchar_t kDocRoot[] = L"chrome_frame\\test\\data"; +const int kLongWaitTimeout = 60 * 1000; +const int kShortWaitTimeout = 25 * 1000; + +_ATL_FUNC_INFO WebBrowserEventSink::kNavigateErrorInfo = {
+ CC_STDCALL, VT_EMPTY, 5, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_BOOL | VT_BYREF, + } +}; + +_ATL_FUNC_INFO WebBrowserEventSink::kNavigateComplete2Info = { + CC_STDCALL, VT_EMPTY, 2, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF + } +}; + +_ATL_FUNC_INFO WebBrowserEventSink::kBeforeNavigate2Info = {
+ CC_STDCALL, VT_EMPTY, 7, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_BOOL | VT_BYREF + } +}; + + + +void ChromeFrameTestWithWebServer::SetUp() { + server_.SetUp(); + results_dir_ = server_.GetDataDir(); + file_util::AppendToPath(&results_dir_, L"dump"); +} + +void ChromeFrameTestWithWebServer::TearDown() { + CloseBrowser(); + + // Web browsers tend to relaunch themselves in other processes, meaning the + // KillProcess stuff above might not have actually cleaned up all our browser + // instances, so make really sure browsers are dead. + base::KillProcesses(chrome_frame_test::kIEImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kIEBrokerImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kFirefoxImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kSafariImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kChromeImageName, 0, NULL); + + server_.TearDown(); +} + +bool ChromeFrameTestWithWebServer::LaunchBrowser(BrowserKind browser, + const wchar_t* page) { + std::wstring url = UTF8ToWide(server_.Resolve(page).spec()); + browser_ = browser; + if (browser == IE) { + browser_handle_.Set(chrome_frame_test::LaunchIE(url)); + } else if (browser == FIREFOX) { + browser_handle_.Set(chrome_frame_test::LaunchFirefox(url)); + } else if (browser == OPERA) { + browser_handle_.Set(chrome_frame_test::LaunchOpera(url)); + } else if (browser == SAFARI) { + browser_handle_.Set(chrome_frame_test::LaunchSafari(url)); + } else if (browser == CHROME) { + browser_handle_.Set(chrome_frame_test::LaunchChrome(url)); + } else { + NOTREACHED(); + } + + return browser_handle_.IsValid(); +} + +void ChromeFrameTestWithWebServer::CloseBrowser() { + if (!browser_handle_.IsValid()) + return; + + int attempts = 0; + if (browser_ == IE) { + attempts = chrome_frame_test::CloseAllIEWindows(); + } else { + attempts = chrome_frame_test::CloseVisibleWindowsOnAllThreads( + browser_handle_); + } + + if (attempts > 0) { + DWORD wait = ::WaitForSingleObject(browser_handle_, 20000); + if (wait == WAIT_OBJECT_0) { + browser_handle_.Close(); + } else { + DLOG(ERROR) << "WaitForSingleObject returned " << wait; + } + } else { + DLOG(ERROR) << "No attempts to close browser windows"; + } + + if (browser_handle_.IsValid()) { + DWORD exit_code = 0; + if (!::GetExitCodeProcess(browser_handle_, &exit_code) || + exit_code == STILL_ACTIVE) { + DLOG(ERROR) << L"Forcefully killing browser process. Exit:" << exit_code; + base::KillProcess(browser_handle_, 0, true); + } + browser_handle_.Close(); + } +} + +bool ChromeFrameTestWithWebServer::BringBrowserToTop() { + return chrome_frame_test::EnsureProcessInForeground(GetProcessId( + browser_handle_)); +} + +bool ChromeFrameTestWithWebServer::WaitForTestToComplete(int milliseconds) { + return server_.WaitToFinish(milliseconds); +} + +bool ChromeFrameTestWithWebServer::WaitForOnLoad(int milliseconds) { + DWORD start = ::GetTickCount(); + std::string data; + while (!ReadResultFile(L"OnLoadEvent", &data) || data.length() == 0) { + DWORD now = ::GetTickCount(); + if (start > now) { + // Very simple check for overflow. In that case we just restart the + // wait. + start = now; + } else if (static_cast<int>(now - start) > milliseconds) { + break; + } + Sleep(100); + } + + return data.compare("loaded") == 0; +} + +bool ChromeFrameTestWithWebServer::ReadResultFile(const std::wstring& file_name, + std::string* data) { + std::wstring full_path = results_dir_; + file_util::AppendToPath(&full_path, file_name); + return file_util::ReadFileToString(full_path, data); +} + +bool ChromeFrameTestWithWebServer::CheckResultFile( + const std::wstring& file_name, const std::string& expected_result) { + std::string data; + bool ret = ReadResultFile(file_name, &data); + if (ret) + ret = (data == expected_result); + + if (!ret) { + DLOG(ERROR) << "Error text: " << (data.empty() ? "<empty>" : data.c_str()); + } + + return ret; +} + +void ChromeFrameTestWithWebServer::SimpleBrowserTest(BrowserKind browser, + const wchar_t* page, const wchar_t* result_file_to_check) { + EXPECT_TRUE(LaunchBrowser(browser, page)); + ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout)); + ASSERT_TRUE(CheckResultFile(result_file_to_check, "OK")); +} + +void ChromeFrameTestWithWebServer::OptionalBrowserTest(BrowserKind browser, + const wchar_t* page, const wchar_t* result_file_to_check) { + if (!LaunchBrowser(browser, page)) { + DLOG(ERROR) << "Failed to launch browser " << ToString(browser); + } else { + ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout)); + ASSERT_TRUE(CheckResultFile(result_file_to_check, "OK")); + } +} + +void ChromeFrameTestWithWebServer::VersionTest(BrowserKind browser, + const wchar_t* page, const wchar_t* result_file_to_check) { + std::wstring plugin_path; + PathService::Get(base::DIR_MODULE, &plugin_path); + file_util::AppendToPath(&plugin_path, L"servers/npchrome_tab.dll"); + + static FileVersionInfo* version_info = + FileVersionInfo::CreateFileVersionInfo(plugin_path); + + std::wstring version; + if (version_info) + version = version_info->product_version(); + + // If we can't find the npchrome_tab.dll in the src tree, we turn to + // the directory where chrome is installed. + if (!version_info) { + installer::Version* ver_system = InstallUtil::GetChromeVersion(true); + installer::Version* ver_user = InstallUtil::GetChromeVersion(false); + ASSERT_TRUE(ver_system || ver_user); + + bool system_install = ver_system ? true : false; + std::wstring npchrome_path(installer::GetChromeInstallPath(system_install)); + file_util::AppendToPath(&npchrome_path, + ver_system ? ver_system->GetString() : ver_user->GetString()); + file_util::AppendToPath(&npchrome_path, L"npchrome_tab.dll"); + version_info = FileVersionInfo::CreateFileVersionInfo(npchrome_path); + if (version_info) + version = version_info->product_version(); + } + + EXPECT_TRUE(version_info); + EXPECT_FALSE(version.empty()); + EXPECT_TRUE(LaunchBrowser(browser, page)); + ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout)); + ASSERT_TRUE(CheckResultFile(result_file_to_check, WideToUTF8(version))); +} + +const wchar_t kPostMessageBasicPage[] = L"files/postmessage_basic_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PostMessageBasic) { + SimpleBrowserTest(IE, kPostMessageBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_PostMessageBasic) { + SimpleBrowserTest(FIREFOX, kPostMessageBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_PostMessageBasic) { + OptionalBrowserTest(OPERA, kPostMessageBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, FullTabIE_MIMEFilterBasic) { + const wchar_t kMIMEFilterBasicPage[] = + L"files/chrome_frame_mime_filter_test.html"; + + SimpleBrowserTest(IE, kMIMEFilterBasicPage, L"MIMEFilter"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Resize) { + SimpleBrowserTest(IE, L"files/chrome_frame_resize.html", L"Resize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_Resize) { + SimpleBrowserTest(FIREFOX, L"files/chrome_frame_resize.html", L"Resize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_Resize) { + OptionalBrowserTest(OPERA, L"files/chrome_frame_resize.html", L"Resize"); +} + +const wchar_t kNavigateURLAbsolutePage[] = + L"files/navigateurl_absolute_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLAbsolute) { + SimpleBrowserTest(IE, kNavigateURLAbsolutePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_NavigateURLAbsolute) { + SimpleBrowserTest(FIREFOX, kNavigateURLAbsolutePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_NavigateURLAbsolute) { + OptionalBrowserTest(OPERA, kNavigateURLAbsolutePage, L"NavigateURL"); +} + +const wchar_t kNavigateURLRelativePage[] = + L"files/navigateurl_relative_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLRelative) { + SimpleBrowserTest(IE, kNavigateURLRelativePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_NavigateURLRelative) { + SimpleBrowserTest(FIREFOX, kNavigateURLRelativePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_NavigateURLRelative) { + OptionalBrowserTest(OPERA, kNavigateURLRelativePage, L"NavigateURL"); +} + +const wchar_t kNavigateSimpleObjectFocus[] = L"files/simple_object_focus.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_ObjectFocus) { + SimpleBrowserTest(FIREFOX, kNavigateSimpleObjectFocus, L"ObjectFocus"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_ObjectFocus) { + SimpleBrowserTest(IE, kNavigateSimpleObjectFocus, L"ObjectFocus"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_ObjectFocus) { + if (!LaunchBrowser(OPERA, kNavigateSimpleObjectFocus)) { + DLOG(ERROR) << "Failed to launch browser " << ToString(OPERA); + } else { + ASSERT_TRUE(WaitForOnLoad(kLongWaitTimeout)); + BringBrowserToTop(); + // Tab through a couple of times. Once should be enough in theory + // but in practice activating the browser can take a few milliseconds more. + bool ok; + for (int i = 0; + i < 5 && (ok = CheckResultFile(L"ObjectFocus", "OK")) == false; + ++i) { + Sleep(300); + chrome_frame_test::SendMnemonic(VK_TAB, false, false, false); + } + ASSERT_TRUE(ok); + } +} + +const wchar_t kiframeBasicPage[] = L"files/iframe_basic_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_iframeBasic) { + SimpleBrowserTest(IE, kiframeBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_iframeBasic) { + SimpleBrowserTest(FIREFOX, kiframeBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_iframeBasic) { + OptionalBrowserTest(OPERA, kiframeBasicPage, L"PostMessage"); +} + +const wchar_t kSrcPropertyTestPage[] = L"files/src_property_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_SrcProperty) { + SimpleBrowserTest(IE, kSrcPropertyTestPage, L"SrcProperty"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_SrcProperty) { + SimpleBrowserTest(FIREFOX, kSrcPropertyTestPage, L"SrcProperty"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_SrcProperty) { + OptionalBrowserTest(OPERA, kSrcPropertyTestPage, L"SrcProperty"); +} + +const wchar_t kCFInstanceBasicTestPage[] = L"files/CFInstance_basic_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceBasic) { + SimpleBrowserTest(IE, kCFInstanceBasicTestPage, L"CFInstanceBasic"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceBasic) { + SimpleBrowserTest(FIREFOX, kCFInstanceBasicTestPage, L"CFInstanceBasic"); +} + +const wchar_t kCFISingletonPage[] = L"files/CFInstance_singleton_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceSingleton) { + SimpleBrowserTest(IE, kCFISingletonPage, L"CFInstanceSingleton"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceSingleton) { + SimpleBrowserTest(FIREFOX, kCFISingletonPage, L"CFInstanceSingleton"); +} + +const wchar_t kCFIDelayPage[] = L"files/CFInstance_delay_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeIE_CFInstanceDelay) { + SimpleBrowserTest(IE, kCFIDelayPage, L"CFInstanceDelay"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeFF_CFInstanceDelay) { + SimpleBrowserTest(FIREFOX, kCFIDelayPage, L"CFInstanceDelay"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstanceDelay) { + OptionalBrowserTest(OPERA, kCFIDelayPage, L"CFInstanceDelay"); +} + +const wchar_t kCFIFallbackPage[] = L"files/CFInstance_fallback_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceFallback) { + SimpleBrowserTest(IE, kCFIFallbackPage, L"CFInstanceFallback"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceFallback) { + SimpleBrowserTest(FIREFOX, kCFIFallbackPage, L"CFInstanceFallback"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceFallback) { + OptionalBrowserTest(OPERA, kCFIFallbackPage, L"CFInstanceFallback"); +} + +const wchar_t kCFINoSrcPage[] = L"files/CFInstance_no_src_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceNoSrc) { + SimpleBrowserTest(IE, kCFINoSrcPage, L"CFInstanceNoSrc"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceNoSrc) { + SimpleBrowserTest(FIREFOX, kCFINoSrcPage, L"CFInstanceNoSrc"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceNoSrc) { + OptionalBrowserTest(OPERA, kCFINoSrcPage, L"CFInstanceNoSrc"); +} + +const wchar_t kCFIIfrOnLoadPage[] = L"files/CFInstance_iframe_onload_host.html"; + +// disabled since it's unlikely that we care about this case +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeIE_CFInstanceIfrOnLoad) { + SimpleBrowserTest(IE, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceIfrOnLoad) { + SimpleBrowserTest(FIREFOX, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceIfrOnLoad) { + OptionalBrowserTest(OPERA, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad"); +} + +const wchar_t kCFIZeroSizePage[] = L"files/CFInstance_zero_size_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceZeroSize) { + SimpleBrowserTest(IE, kCFIZeroSizePage, L"CFInstanceZeroSize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceZeroSize) { + SimpleBrowserTest(FIREFOX, kCFIZeroSizePage, L"CFInstanceZeroSize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceZeroSize) { + OptionalBrowserTest(OPERA, kCFIZeroSizePage, L"CFInstanceZeroSize"); +} + +const wchar_t kCFIIfrPostPage[] = L"files/CFInstance_iframe_post_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceIfrPost) { + SimpleBrowserTest(IE, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceIfrPost) { + SimpleBrowserTest(FIREFOX, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceIfrPost) { + OptionalBrowserTest(CHROME, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceIfrPost) { + OptionalBrowserTest(SAFARI, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceIfrPost) { + OptionalBrowserTest(OPERA, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +const wchar_t kCFIPostPage[] = L"files/CFInstance_post_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstancePost) { + SimpleBrowserTest(IE, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstancePost) { + SimpleBrowserTest(FIREFOX, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstancePost) { + OptionalBrowserTest(CHROME, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstancePost) { + OptionalBrowserTest(SAFARI, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstancePost) { + OptionalBrowserTest(OPERA, kCFIPostPage, L"CFInstancePost"); +} + +const wchar_t kCFIRPCPage[] = L"files/CFInstance_rpc_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPC) { + SimpleBrowserTest(IE, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceRPC) { + SimpleBrowserTest(FIREFOX, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPC) { + OptionalBrowserTest(CHROME, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceRPC) { + OptionalBrowserTest(SAFARI, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstanceRPC) { + OptionalBrowserTest(OPERA, kCFIRPCPage, L"CFInstanceRPC"); +} + +const wchar_t kCFIRPCInternalPage[] = + L"files/CFInstance_rpc_internal_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPCInternal) { + SimpleBrowserTest(IE, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +// Disabled: http://b/issue?id=2050201 +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeFF_CFInstanceRPCInternal) { + SimpleBrowserTest(FIREFOX, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPCInternal) { + OptionalBrowserTest(CHROME, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceRPCInternal) { + OptionalBrowserTest(SAFARI, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +const wchar_t kCFIDefaultCtorPage[] = + L"files/CFInstance_default_ctor_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceDefaultCtor) { + SimpleBrowserTest(IE, kCFIDefaultCtorPage, L"CFInstanceDefaultCtor"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceDefaultCtor) { + SimpleBrowserTest(FIREFOX, kCFIDefaultCtorPage, L"CFInstanceDefaultCtor"); +} + +// Class that mocks external call from VectoredHandlerT for testing purposes. +class EMock : public VEHTraitsBase { + public: + static inline bool WriteDump(EXCEPTION_POINTERS* p) { + g_dump_made = true; + return true; + } + + static inline void* Register(PVECTORED_EXCEPTION_HANDLER func, + const void* module_start, + const void* module_end) { + VEHTraitsBase::SetModule(module_start, module_end); + // Return some arbitrary number, expecting to get the same on Unregister() + return reinterpret_cast<void*>(4); + } + + static inline ULONG Unregister(void* handle) { + EXPECT_EQ(handle, reinterpret_cast<void*>(4)); + return 1; + } + + static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip, + DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) { + EXPECT_EQ(2, FramesToSkip); + EXPECT_LE(FramesToSkip + FramesToCapture, + VectoredHandlerBase::max_back_trace); + memcpy(BackTrace, g_stack, g_stack_entries * sizeof(BackTrace[0])); + return g_stack_entries; + } + + static inline EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() { + return g_seh_chain; + } + + // Test helpers + + // Create fake SEH chain of random filters - with and without our module. + static void SetHaveSEHFilter() { + SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000, + reinterpret_cast<const char*>(g_module_start) + 0x1000, + reinterpret_cast<const char*>(g_module_end) + 0x7127, + NULL); + } + + static void SetNoSEHFilter() { + SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000, + reinterpret_cast<const char*>(g_module_end) + 0x7127, + NULL); + } + + // Create fake stack - with and without our module. + static void SetOnStack() { + SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283, + reinterpret_cast<const char*>(g_module_start) - 0x278361, + reinterpret_cast<const char*>(g_module_start) + 0x9171, + reinterpret_cast<const char*>(g_module_end) + 1231, + NULL); + } + + static void SetNotOnStack() { + SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283, + reinterpret_cast<const char*>(g_module_start) - 0x278361, + reinterpret_cast<const char*>(g_module_end) + 1231, + NULL); + } + + // Populate stack array + static void SetStack(const void* p, ...) { + va_list vl; + va_start(vl, p); + g_stack_entries = 0; + for (; p; ++g_stack_entries) { + CHECK(g_stack_entries < arraysize(g_stack)); + g_stack[g_stack_entries] = p; + p = va_arg(vl, const void*); + } + } + + static void SetSEHChain(const void* p, ...) { + va_list vl; + va_start(vl, p); + int i = 0; + for (; p; ++i) { + CHECK(i + 1 < arraysize(g_seh_chain)); + g_seh_chain[i].Handler = const_cast<void*>(p); + g_seh_chain[i].Next = &g_seh_chain[i + 1]; + p = va_arg(vl, const void*); + } + + g_seh_chain[i].Next = EXCEPTION_CHAIN_END; + } + + static EXCEPTION_REGISTRATION_RECORD g_seh_chain[25]; + static const void* g_stack[VectoredHandlerBase::max_back_trace]; + static WORD g_stack_entries; + static bool g_dump_made; +}; + +EXCEPTION_REGISTRATION_RECORD EMock::g_seh_chain[25]; +const void* EMock::g_stack[VectoredHandlerBase::max_back_trace]; +WORD EMock::g_stack_entries; +bool EMock::g_dump_made; + +typedef VectoredHandlerT<EMock> VectoredHandlerMock; + +class ExPtrsHelper : public _EXCEPTION_POINTERS { + public: + ExPtrsHelper() { + ExceptionRecord = &er_; + ContextRecord = &ctx_; + ZeroMemory(&er_, sizeof(er_)); + ZeroMemory(&ctx_, sizeof(ctx_)); + } + + void Set(DWORD code, void* address, DWORD flags) { + er_.ExceptionCode = code; + er_.ExceptionAddress = address; + er_.ExceptionFlags = flags; + } + + EXCEPTION_RECORD er_; + CONTEXT ctx_; +}; + + +TEST(ChromeFrame, ExceptionReport) { + char* s = reinterpret_cast<char*>(0x30000000); + char* e = s + 0x10000; + void* handler = VectoredHandlerMock::Register(s, e); + char* our_code = s + 0x1111; + char* not_our_code = s - 0x5555; + char* not_our_code2 = e + 0x5555; + + ExPtrsHelper ex; + // Exception in our code, but we have SEH filter + ex.Set(STATUS_ACCESS_VIOLATION, our_code, 0); + EMock::SetHaveSEHFilter(); + EMock::SetOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen); + EXPECT_FALSE(EMock::g_dump_made); + + // RPC_E_DISCONNECTED (0x80010108) is "The object invoked has disconnected + // from its clients", shall not be caught since it's a warning only. + ex.Set(RPC_E_DISCONNECTED, our_code, 0); + EMock::SetHaveSEHFilter(); + EMock::SetOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen); + EXPECT_FALSE(EMock::g_dump_made); + + + // Exception, not in our code, we do not have SEH and we are not in stack. + ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code, 0); + EMock::SetNoSEHFilter(); + EMock::SetNotOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(2, VectoredHandlerMock::g_exceptions_seen); + EXPECT_FALSE(EMock::g_dump_made); + + // Exception, not in our code, no SEH, but we are on the stack. + ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code2, 0); + EMock::SetNoSEHFilter(); + EMock::SetOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(3, VectoredHandlerMock::g_exceptions_seen); + EXPECT_TRUE(EMock::g_dump_made); + EMock::g_dump_made = false; + + + // Exception, in our code, no SEH, not on stack (assume FPO screwed us) + ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, our_code, 0); + EMock::SetNoSEHFilter(); + EMock::SetNotOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(4, VectoredHandlerMock::g_exceptions_seen); + EXPECT_TRUE(EMock::g_dump_made); + EMock::g_dump_made = false; + + VectoredHandlerMock::Unregister(); +} + +const wchar_t kInitializeHiddenPage[] = L"files/initialize_hidden.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_InitializeHidden) { + SimpleBrowserTest(IE, kInitializeHiddenPage, L"InitializeHidden"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_InitializeHidden) { + SimpleBrowserTest(FIREFOX, kInitializeHiddenPage, L"InitializeHidden"); +} + +// Disabled due to a problem with Opera. +// http://b/issue?id=1708275 +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_InitializeHidden) { + OptionalBrowserTest(OPERA, kInitializeHiddenPage, L"InitializeHidden"); +} + +const wchar_t kInHeadPage[] = L"files/in_head.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_InHead) { + SimpleBrowserTest(FIREFOX, kInHeadPage, L"InHead"); +} + +const wchar_t kVersionPage[] = L"files/version.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Version) { + VersionTest(IE, kVersionPage, L"version"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_Version) { + VersionTest(FIREFOX, kVersionPage, L"version"); +} + +const wchar_t kEventListenerPage[] = L"files/event_listener.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_EventListener) { + SimpleBrowserTest(IE, kEventListenerPage, L"EventListener"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_EventListener) { + SimpleBrowserTest(FIREFOX, kEventListenerPage, L"EventListener"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_EventListener) { + OptionalBrowserTest(OPERA, kEventListenerPage, L"EventListener"); +} + +const wchar_t kPrivilegedApisPage[] = L"files/privileged_apis_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PrivilegedApis) { + SimpleBrowserTest(IE, kPrivilegedApisPage, L"PrivilegedApis"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_PrivilegedApis) { + SimpleBrowserTest(FIREFOX, kPrivilegedApisPage, L"PrivilegedApis"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_PrivilegedApis) { + OptionalBrowserTest(OPERA, kPrivilegedApisPage, L"PrivilegedApis"); +} + +class ChromeFrameTestEnvironment: public testing::Environment { + public: + ~ChromeFrameTestEnvironment() { + } + + void SetUp() { + ScopedChromeFrameRegistrar::RegisterDefaults(); + } + + void TearDown() { + } +}; + +::testing::Environment* const chrome_frame_env =
+ ::testing::AddGlobalTestEnvironment(new ChromeFrameTestEnvironment); + +// TODO(stoyan): - Move everything below in separate file(s). +struct LaunchDelegateMock : public ProxyFactory::LaunchDelegate { + MOCK_METHOD2(LaunchComplete, void(ChromeFrameAutomationProxy*, + AutomationLaunchResult)); +}; + +TEST(ProxyFactoryTest, CreateDestroy) { + ProxyFactory f; + LaunchDelegateMock d; + EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(1); + void* id = f.GetAutomationServer(0, L"Adam.N.Epilinter", L"", false, &d); + f.ReleaseAutomationServer(id); +} + +TEST(ProxyFactoryTest, CreateSameProfile) { + ProxyFactory f; + LaunchDelegateMock d; + EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(2); + void* i1 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d); + void* i2 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d); + EXPECT_EQ(i1, i2); + f.ReleaseAutomationServer(i2); + f.ReleaseAutomationServer(i1); +} + +TEST(ProxyFactoryTest, CreateDifferentProfiles) { + ProxyFactory f; + LaunchDelegateMock d; + EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(2); + void* i1 = f.GetAutomationServer(0, L"Adam.N.Epilinter", L"", false, &d); + void* i2 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d); + EXPECT_NE(i1, i2); + f.ReleaseAutomationServer(i2); + f.ReleaseAutomationServer(i1); +} + +// ChromeFrameAutomationClient [CFAC] tests. +struct MockCFDelegate : public ChromeFrameDelegateImpl { + MOCK_CONST_METHOD0(GetWindow, WindowType()); + MOCK_METHOD1(GetBounds, void(RECT* bounds)); + MOCK_METHOD0(GetDocumentUrl, std::string()); + MOCK_METHOD2(ExecuteScript, bool(const std::string& script, + std::string* result)); + MOCK_METHOD0(OnAutomationServerReady, void()); + MOCK_METHOD2(OnAutomationServerLaunchFailed, void( + AutomationLaunchResult reason, const std::string& server_version)); + // This remains in interface since we call it if Navigate() + // returns immediate error. + MOCK_METHOD2(OnLoadFailed, void(int error_code, const std::string& url)); + + // Do not mock this method. :) Use it as message demuxer and dispatcher + // to the following methods (which we mock) + // MOCK_METHOD1(OnMessageReceived, void(const IPC::Message&)); + + + MOCK_METHOD2(OnNavigationStateChanged, void(int tab_handle, int flags)); + MOCK_METHOD2(OnUpdateTargetUrl, void(int tab_handle, + const std::wstring& new_target_url)); + MOCK_METHOD2(OnAcceleratorPressed, void(int tab_handle, + const MSG& accel_message)); + MOCK_METHOD2(OnTabbedOut, void(int tab_handle, bool reverse)); + MOCK_METHOD3(OnOpenURL, void(int tab_handle, const GURL& url, + int open_disposition)); + MOCK_METHOD2(OnDidNavigate, void(int tab_handle, + const IPC::NavigationInfo& navigation_info)); + MOCK_METHOD3(OnNavigationFailed, void(int tab_handle, int error_code, + const GURL& gurl)); + MOCK_METHOD2(OnLoad, void(int tab_handle, const GURL& url)); + MOCK_METHOD4(OnMessageFromChromeFrame, void(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target)); + MOCK_METHOD5(OnHandleContextMenu, void(int tab_handle, HANDLE menu_handle, + int x_pos, int y_pos, int align_flags)); + MOCK_METHOD3(OnRequestStart, void(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request)); + MOCK_METHOD3(OnRequestRead, void(int tab_handle, int request_id, + int bytes_to_read)); + MOCK_METHOD3(OnRequestEnd, void(int tab_handle, int request_id, + const URLRequestStatus& status)); + MOCK_METHOD3(OnSetCookieAsync, void(int tab_handle, const GURL& url, + const std::string& cookie)); + + // Use for sending network responses + void SetAutomationSender(IPC::Message::Sender* automation) { + automation_ = automation; + } + + // Set-expectation helpers + void SetOnNavigationStateChanged(int tab_handle) { + EXPECT_CALL(*this, + OnNavigationStateChanged(testing::Eq(tab_handle), testing::_)) + .Times(testing::AnyNumber()); + } + + // Response sender helpers + void ReplyStarted(const IPC::AutomationURLResponse* response, + int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + automation_->Send(new AutomationMsg_RequestStarted(0, tab_handle, + request_id, *response)); + } + + void ReplyData(const std::string* data, int tab_handle, int request_id, + int bytes_to_read) { + automation_->Send(new AutomationMsg_RequestData(0, tab_handle, + request_id, *data)); + } + + void ReplyEOF(int tab_handle, int request_id) { + automation_->Send(new AutomationMsg_RequestEnd(0, tab_handle, + request_id, URLRequestStatus())); + } + + void Reply404(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + const IPC::AutomationURLResponse notfound = {"", "HTTP/1.1 404\r\n\r\n"}; + automation_->Send(new AutomationMsg_RequestStarted(0, tab_handle, + request_id, notfound)); + automation_->Send(new AutomationMsg_RequestEnd(0, tab_handle, + request_id, URLRequestStatus())); + } + + IPC::Message::Sender* automation_; +}; + +class MockProxyFactory : public ProxyFactory { + public: + MOCK_METHOD5(GetAutomationServer, void*(int, const std::wstring&, + const std::wstring& extra_argument, bool, ProxyFactory::LaunchDelegate*)); + MOCK_METHOD1(ReleaseAutomationServer, bool(void* id)); + + MockProxyFactory() : thread_("mock factory worker") { + thread_.Start(); + loop_ = thread_.message_loop(); + } + + // Fake implementation + void GetServerImpl(ChromeFrameAutomationProxy* pxy, + AutomationLaunchResult result, + int timeout, + ProxyFactory::LaunchDelegate* d) { + Task* task = NewRunnableMethod(d, + &ProxyFactory::LaunchDelegate::LaunchComplete, pxy, result); + loop_->PostDelayedTask(FROM_HERE, task, timeout/2); + } + + base::Thread thread_; + MessageLoop* loop_; +}; + +class MockAutomationProxy : public ChromeFrameAutomationProxy { + public: + MOCK_METHOD1(Send, bool(IPC::Message*)); + MOCK_METHOD3(SendAsAsync, void(IPC::SyncMessage* msg, void* callback, + void* key)); + MOCK_METHOD1(CancelAsync, void(void* key)); + MOCK_METHOD1(CreateTabProxy, scoped_refptr<TabProxy>(int handle)); + MOCK_METHOD0(server_version, std::string(void)); + MOCK_METHOD1(SendProxyConfig, void(const std::string&)); + MOCK_METHOD1(SetEnableExtensionAutomation, void(bool enable)); + + ~MockAutomationProxy() {} +}; + +struct MockAutomationMessageSender : public AutomationMessageSender { + MOCK_METHOD1(Send, bool(IPC::Message*)); + MOCK_METHOD3(SendWithTimeout, bool(IPC::Message* , int , bool*)); + + void ForwardTo(MockAutomationProxy *p) { + proxy_ = p; + ON_CALL(*this, Send(testing::_)) + .WillByDefault(testing::Invoke(proxy_, &MockAutomationProxy::Send)); + } + + MockAutomationProxy* proxy_; +}; + +template <> struct RunnableMethodTraits<ProxyFactory::LaunchDelegate> { + static void RetainCallee(ProxyFactory::LaunchDelegate* obj) {} + static void ReleaseCallee(ProxyFactory::LaunchDelegate* obj) {} +}; + +template <> struct RunnableMethodTraits<MockProxyFactory> { + static void RetainCallee(MockProxyFactory* obj) {} + static void ReleaseCallee(MockProxyFactory* obj) {} +}; + +template <> struct RunnableMethodTraits<ChromeFrameAutomationClient> { + static void RetainCallee(ChromeFrameAutomationClient* obj) {} + static void ReleaseCallee(ChromeFrameAutomationClient* obj) {} +}; + +// MessageLoopForUI wrapper that runs only for a limited time. +// We need a UI message loop in the main thread. +struct TimedMsgLoop { + public: + void RunFor(int seconds) { + loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, 1000 * seconds); + loop_.MessageLoop::Run(); + } + + void Quit() { + loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask); + } + + MessageLoopForUI loop_; +}; + +template <> struct RunnableMethodTraits<TimedMsgLoop> { + static void RetainCallee(TimedMsgLoop* obj) {} + static void ReleaseCallee(TimedMsgLoop* obj) {} +}; + +// Saves typing. It's somewhat hard to create a wrapper around +// testing::InvokeWithoutArgs since it returns a +// non-public (testing::internal) type. +#define QUIT_LOOP(loop) testing::InvokeWithoutArgs(TaskHolder(\ + NewRunnableMethod(&loop, &TimedMsgLoop::Quit))) + +// We mock ChromeFrameDelegate only. The rest is with real AutomationProxy +TEST(CFACWithChrome, CreateTooFast) { + MockCFDelegate cfd; + TimedMsgLoop loop; + int timeout = 0; // Chrome cannot send Hello message so fast. + const std::wstring profile = L"Adam.N.Epilinter"; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient()); + + EXPECT_CALL(cfd, OnAutomationServerLaunchFailed(AUTOMATION_TIMEOUT, + testing::_)) + .Times(1) + .WillOnce(QUIT_LOOP(loop)); + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + loop.RunFor(10); + client->Uninitialize(); +} + +// This test may fail if Chrome take more that 10 seconds (timeout var) to +// launch. In this case GMock shall print something like "unexpected call to +// OnAutomationServerLaunchFailed". I'm yet to find out how to specify +// that this is an unexpected call, and still to execute and action. +TEST(CFACWithChrome, CreateNotSoFast) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .Times(1) + .WillOnce(QUIT_LOOP(loop)); + + EXPECT_CALL(cfd, OnAutomationServerLaunchFailed(testing::_, testing::_)) + .Times(0); + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + + loop.RunFor(11); + client->Uninitialize(); + client.reset(NULL); +} + +MATCHER_P(MsgType, msg_type, "IPC::Message::type()") { + const IPC::Message& m = arg; + return (m.type() == msg_type); +} + +MATCHER_P(EqNavigationInfoUrl, url, "IPC::NavigationInfo matcher") { + if (url.is_valid() && url != arg.url) + return false; + // TODO: other members + return true; +} + +TEST(CFACWithChrome, NavigateOk) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + const std::string url = "about:version"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod( + client.get(), &ChromeFrameAutomationClient::InitiateNavigation, + url)))); + +// cfd.SetOnNavigationStateChanged(); + EXPECT_CALL(cfd, + OnNavigationStateChanged(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + { + testing::InSequence s; + + EXPECT_CALL(cfd, OnDidNavigate(testing::_, EqNavigationInfoUrl(GURL()))) + .Times(1); + + EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_)).Times(1); + + EXPECT_CALL(cfd, OnLoad(testing::_, testing::_)) + .Times(1) + .WillOnce(QUIT_LOOP(loop)); + } + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + loop.RunFor(10); + client->Uninitialize(); + client.reset(NULL); +} + +// Bug: http://b/issue?id=2033644 +TEST(CFACWithChrome, DISABLED_NavigateFailed) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + const std::string url = "http://127.0.0.3:65412/"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod( + client.get(), &ChromeFrameAutomationClient::InitiateNavigation, + url)))); + + EXPECT_CALL(cfd, + OnNavigationStateChanged(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + EXPECT_CALL(cfd, OnNavigationFailed(testing::_, testing::_, testing::_)) + .Times(1); + + EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + EXPECT_CALL(cfd, OnLoad(testing::_, testing::_)) + .Times(0); + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + + loop.RunFor(10); + client->Uninitialize(); + client.reset(NULL); +} + +MATCHER_P(EqURLRequest, x, "IPC::AutomationURLRequest matcher") { + if (arg.url != x.url) + return false; + if (arg.method != x.method) + return false; + if (arg.referrer != x.referrer) + return false; + if (arg.extra_request_headers != x.extra_request_headers) + return false; + // TODO: uploaddata member + return true; +} + +MATCHER_P(EqUrlGet, url, "Quick URL matcher for 'HTTP GET' request") { + if (arg.url != url) + return false; + if (arg.method != "GET") + return false; + return true; +} + +TEST(CFACWithChrome, UseHostNetworkStack) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + const std::string url = "http://bongo.com"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + client->set_use_chrome_network(false); + cfd.SetAutomationSender(client.get()); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod( + client.get(), &ChromeFrameAutomationClient::InitiateNavigation, + url)))); + + EXPECT_CALL(cfd, OnNavigationStateChanged(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + EXPECT_CALL(cfd, GetBounds(testing::_)) + .Times(testing::AtMost(1)); + + EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + // Note slash appending to the url string, because of GURL inside chrome + const IPC::AutomationURLResponse found = {"", "HTTP/0.9 200\r\n\r\n\r\n", }; + + // Hard coded tab and request ids + static const int tab_id = 1; + int request_id = 2; + + EXPECT_CALL(cfd, OnRequestStart(tab_id, request_id, EqUrlGet(url + '/'))) + .Times(1) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::ReplyStarted, + &found))); + + // Return some trivial page, that have a link to a "logo.gif" image + const std::string data = "<!DOCTYPE html><title>Hello</title>" + "<img src=\"logo.gif\">"; + EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::Ge(0))) + .Times(2) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::ReplyData, &data))) + .WillOnce(testing::WithArgs<0, 1>(testing::Invoke(CBF(&cfd, + &MockCFDelegate::ReplyEOF)))); + + EXPECT_CALL(cfd, OnDidNavigate(tab_id, EqNavigationInfoUrl(GURL(url)))) + .Times(1); + EXPECT_CALL(cfd, OnLoad(tab_id, GURL(url))) + .Times(1); + + // Expect request for logo.gif + request_id++; + EXPECT_CALL(cfd, + OnRequestStart(tab_id, request_id, EqUrlGet(url + "/logo.gif"))) + .Times(1) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::Reply404))); + + EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::_)) + .Times(testing::AtMost(1)); + + // Chrome makes a brave request for favicon.ico + request_id++; + EXPECT_CALL(cfd, + OnRequestStart(tab_id, request_id, EqUrlGet(url + "/favicon.ico"))) + .Times(1) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::Reply404))); + + EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::_)) + .Times(testing::AtMost(1)); + + bool incognito = true; + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", + incognito)); + + loop.RunFor(10); + client->Uninitialize(); + client.reset(NULL); +} + + +// [CFAC] -- uses a ProxyFactory for creation of ChromeFrameAutomationProxy +// -- uses ChromeFrameAutomationProxy +// -- uses TabProxy obtained from ChromeFrameAutomationProxy +// -- uses ChromeFrameDelegate as outgoing interface +// +// We mock ProxyFactory to return mock object (MockAutomationProxy) implementing +// ChromeFrameAutomationProxy interface. +// Since CFAC uses TabProxy for few calls and TabProxy is not easy mockable, +// we create 'real' TabProxy but with fake AutomationSender (the one responsible +// for sending messages over channel). +// Additionally we have mock implementation ChromeFrameDelagate interface - +// MockCFDelegate. + +// Test fixture, saves typing all of it's members. +class CFACMockTest : public testing::Test { + public: + MockProxyFactory factory_; + MockCFDelegate cfd_; + TimedMsgLoop loop_; + MockAutomationProxy proxy_; + scoped_ptr<AutomationHandleTracker> tracker_; + MockAutomationMessageSender dummy_sender_; + scoped_refptr<TabProxy> tab_; + scoped_ptr<ChromeFrameAutomationClient> client_; // the victim of all tests + + std::wstring profile_; + int timeout_; + void* id_; // Automation server id we are going to return + int tab_handle_; // Tab handle. Any non-zero value is Ok. + + inline ChromeFrameAutomationProxy* get_proxy() { + return static_cast<ChromeFrameAutomationProxy*>(&proxy_); + } + + inline void CreateTab() { + ASSERT_EQ(NULL, tab_.get()); + tab_ = new TabProxy(&dummy_sender_, tracker_.get(), tab_handle_); + } + + // Easy methods to set expectations. + void SetAutomationServerOk() { + EXPECT_CALL(factory_, GetAutomationServer(testing::Eq(timeout_), + testing::StrEq(profile_), + testing::_, + testing::_, + testing::NotNull())) + .Times(1) + .WillOnce(testing::DoAll( + testing::WithArgs<0, 4>( + testing::Invoke(CBF(&factory_, &MockProxyFactory::GetServerImpl, + get_proxy(), AUTOMATION_SUCCESS))), + testing::Return(id_))); + + EXPECT_CALL(factory_, ReleaseAutomationServer(testing::Eq(id_))).Times(1); + } + + void Set_CFD_LaunchFailed(AutomationLaunchResult result) { + EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed( + testing::Eq(result), testing::_)) + .Times(1) + .WillOnce(QUIT_LOOP(loop_)); + } + + protected: + CFACMockTest() : tracker_(NULL), timeout_(500), + profile_(L"Adam.N.Epilinter") { + id_ = reinterpret_cast<void*>(5); + tab_handle_ = 3; + } + + virtual void SetUp() { + dummy_sender_.ForwardTo(&proxy_); + tracker_.reset(new AutomationHandleTracker(&dummy_sender_)); + + client_.reset(new ChromeFrameAutomationClient); + client_->set_proxy_factory(&factory_); + } +}; + +// Could be implemented as MockAutomationProxy member (we have WithArgs<>!) +ACTION_P3(HandleCreateTab, tab_handle, external_tab_container, tab_wnd) { + // arg0 - message + // arg1 - callback + // arg2 - key + CallbackRunner<Tuple3<HWND, HWND, int> >* c = + reinterpret_cast<CallbackRunner<Tuple3<HWND, HWND, int> >*>(arg1); + c->Run(external_tab_container, tab_wnd, tab_handle); + delete c; + delete arg0; +} + +TEST_F(CFACMockTest, MockedCreateTabOk) { + int timeout = 500; + CreateTab(); + SetAutomationServerOk(); + + EXPECT_CALL(proxy_, server_version()).Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return("")); + + // We need some valid HWNDs, when responding to CreateExternalTab + HWND h1 = ::GetDesktopWindow(); + HWND h2 = ::GetDesktopWindow(); + EXPECT_CALL(proxy_, SendAsAsync(testing::Property(&IPC::SyncMessage::type, + AutomationMsg_CreateExternalTab__ID), + testing::NotNull(), testing::_)) + .Times(1) + .WillOnce(HandleCreateTab(tab_handle_, h1, h2)); + + EXPECT_CALL(proxy_, CreateTabProxy(testing::Eq(tab_handle_))) + .WillOnce(testing::Return(tab_)); + + EXPECT_CALL(cfd_, OnAutomationServerReady()) + .WillOnce(QUIT_LOOP(loop_)); + + // Here we go! + EXPECT_TRUE(client_->Initialize(&cfd_, timeout, false, profile_, L"", false)); + loop_.RunFor(10); + client_->Uninitialize(); +} + +TEST_F(CFACMockTest, MockedCreateTabFailed) { + HWND null_wnd = NULL; + SetAutomationServerOk(); + + EXPECT_CALL(proxy_, server_version()).Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return("")); + + EXPECT_CALL(proxy_, SendAsAsync(testing::Property(&IPC::SyncMessage::type, + AutomationMsg_CreateExternalTab__ID), + testing::NotNull(), testing::_)) + .Times(1) + .WillOnce(HandleCreateTab(tab_handle_, null_wnd, null_wnd)); + + EXPECT_CALL(proxy_, CreateTabProxy(testing::_)).Times(0); + + Set_CFD_LaunchFailed(AUTOMATION_CREATE_TAB_FAILED); + + // Here we go! + EXPECT_TRUE(client_->Initialize(&cfd_, timeout_, false, profile_, L"", + false)); + loop_.RunFor(4); + client_->Uninitialize(); +} + +const wchar_t kMetaTagPage[] = L"files/meta_tag.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_MetaTag) { + SimpleBrowserTest(IE, kMetaTagPage, L"meta_tag"); +} + +const wchar_t kCFProtocolPage[] = L"files/chrome_frame_protocol.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_CFProtocol) { + SimpleBrowserTest(IE, kCFProtocolPage, L"chrome_frame_protocol"); +} + +const wchar_t kPersistentCookieTest[] = + L"files/persistent_cookie_test_page.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_PersistentCookieTest) { + SimpleBrowserTest(IE, kPersistentCookieTest, L"PersistentCookieTest"); +} + +const wchar_t kNavigateOutPage[] = L"files/navigate_out.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_NavigateOut) { + SimpleBrowserTest(IE, kNavigateOutPage, L"navigate_out"); +} + +HRESULT LaunchIEAsComServer(IWebBrowser2** web_browser) { + if (!web_browser) + return E_INVALIDARG; + + ScopedComPtr<IWebBrowser2> web_browser2; + HRESULT hr = CoCreateInstance( + CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, + reinterpret_cast<void**>(web_browser2.Receive())); + + if (SUCCEEDED(hr)) { + *web_browser = web_browser2.Detach(); + } + + return hr; +} + +TEST(ChromeFrameTest, FullTabModeIE_DisallowedUrls) { + int major_version = 0; + int minor_version = 0; + int bugfix_version = 0; + + base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version, + &bugfix_version); + if (major_version > 5) { + DLOG(INFO) << __FUNCTION__ << " Not running test on Windows version: " + << major_version; + return; + } + + IEVersion ie_version = GetIEVersion(); + if (ie_version == IE_8) { + DLOG(INFO) << __FUNCTION__ << " Not running test on IE8"; + return; + } + + HRESULT hr = CoInitialize(NULL); + bool should_uninit = SUCCEEDED(hr); + + ScopedComPtr<IWebBrowser2> web_browser2; + EXPECT_TRUE(S_OK == LaunchIEAsComServer(web_browser2.Receive())); + web_browser2->put_Visible(VARIANT_TRUE); + + CComObject<WebBrowserEventSink>* web_browser_sink = NULL; + CComObject<WebBrowserEventSink>::CreateInstance(&web_browser_sink); + + // Pass the main thread id to the browser sink so that it can notify + // us about test completion. + web_browser_sink->set_main_thread_id(GetCurrentThreadId()); + + hr = web_browser_sink->DispEventAdvise(web_browser2, + &DIID_DWebBrowserEvents2); + EXPECT_TRUE(hr == S_OK); + + VARIANT empty = ScopedVariant::kEmptyVariant; + ScopedVariant url; + url.Set(L"cf:file:///C:/"); + + TimedMsgLoop loop; + + hr = web_browser2->Navigate2(url.AsInput(), &empty, &empty, &empty, &empty); + EXPECT_TRUE(hr == S_OK); + + loop.RunFor(10); + + EXPECT_TRUE(web_browser_sink->navigation_failed()); + + hr = web_browser_sink->DispEventUnadvise(web_browser2); + EXPECT_TRUE(hr == S_OK); + + web_browser2.Release(); + chrome_frame_test::CloseAllIEWindows(); + + if (should_uninit) { + CoUninitialize(); + } +} + diff --git a/chrome_frame/test/chrome_frame_unittests.h b/chrome_frame/test/chrome_frame_unittests.h new file mode 100644 index 0000000..98b5985 --- /dev/null +++ b/chrome_frame/test/chrome_frame_unittests.h @@ -0,0 +1,164 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_ +#define CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <string> +#include <exdisp.h> +#include <exdispid.h> + +#include "base/ref_counted.h" +#include "base/scoped_handle_win.h" +#include "googleurl/src/gurl.h" +#include "chrome_frame/test/http_server.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Class that: +// 1) Starts the local webserver, +// 2) Supports launching browsers - Internet Explorer and Firefox with local url +// 3) Wait the webserver to finish. It is supposed the test webpage to shutdown +// the server by navigating to "kill" page +// 4) Supports read the posted results from the test webpage to the "dump" +// webserver directory +class ChromeFrameTestWithWebServer: public testing::Test { + protected: + enum BrowserKind { INVALID, IE, FIREFOX, OPERA, SAFARI, CHROME }; + + bool LaunchBrowser(BrowserKind browser, const wchar_t* url); + bool WaitForTestToComplete(int milliseconds); + // Waits for the page to notify us of the window.onload event firing. + // Note that the milliseconds value is only approximate. + bool WaitForOnLoad(int milliseconds); + bool ReadResultFile(const std::wstring& file_name, std::string* data); + + // Launches the specified browser and waits for the test to complete + // (see WaitForTestToComplete). Then checks that the outcome is OK. + // This function uses EXPECT_TRUE and ASSERT_TRUE for all steps performed + // hence no return value. + void SimpleBrowserTest(BrowserKind browser, const wchar_t* page, + const wchar_t* result_file_to_check); + + // Same as SimpleBrowserTest but if the browser isn't installed (LaunchBrowser + // fails), the function will print out a warning but not treat the test + // as failed. + // Currently this is how we run Opera tests. + void OptionalBrowserTest(BrowserKind browser, const wchar_t* page, + const wchar_t* result_file_to_check); + + // Test if chrome frame correctly reports its version. + void VersionTest(BrowserKind browser, const wchar_t* page, + const wchar_t* result_file_to_check); + + void CloseBrowser(); + + // Ensures (well, at least tries to ensure) that the browser window has focus. + bool BringBrowserToTop(); + + // Returns true iff the specified result file contains 'expected result'. + bool CheckResultFile(const std::wstring& file_name, + const std::string& expected_result); + + virtual void SetUp(); + virtual void TearDown(); + + // Important: kind means "sheep" in Icelandic. ?:-o + const char* ToString(BrowserKind kind) { + switch (kind) { + case IE: + return "IE"; + case FIREFOX: + return "Firefox"; + case OPERA: + return "Opera"; + case CHROME: + return "Chrome"; + case SAFARI: + return "Safari"; + default: + NOTREACHED(); + break; + } + return ""; + } + + BrowserKind browser_; + std::wstring results_dir_; + ScopedHandle browser_handle_; + ChromeFrameHTTPServer server_; +}; + +// This class sets up event sinks to the IWebBrowser interface. Currently it +// subscribes to the following events:- +// 1. DISPID_BEFORENAVIGATE2 +// 2. DISPID_NAVIGATEERROR +// 3. DISPID_NAVIGATECOMPLETE2 +// Other events can be subscribed to on an if needed basis. +class WebBrowserEventSink + : public CComObjectRootEx<CComSingleThreadModel>, + public IDispEventSimpleImpl<0, WebBrowserEventSink, + &DIID_DWebBrowserEvents2> { + public: + WebBrowserEventSink() + : navigation_failed_(false), + main_thread_id_(0) { + } + +BEGIN_COM_MAP(WebBrowserEventSink) +END_COM_MAP() + +BEGIN_SINK_MAP(WebBrowserEventSink) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, + OnBeforeNavigate2, &kBeforeNavigate2Info) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, + OnNavigateComplete2, &kNavigateComplete2Info) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR, + OnNavigateError, &kNavigateErrorInfo) +END_SINK_MAP() + + STDMETHOD_(void, OnNavigateError)(IDispatch* dispatch, VARIANT* url, + VARIANT* frame_name, VARIANT* status_code, + VARIANT* cancel) { + navigation_failed_ = true; + } + + STDMETHOD(OnBeforeNavigate2)(IDispatch* dispatch, VARIANT* url, VARIANT* + flags, VARIANT* target_frame_name, + VARIANT* post_data, VARIANT* headers, + VARIANT_BOOL* cancel) { + DLOG(INFO) << __FUNCTION__; + // If a navigation fails then IE issues a navigation to an interstitial + // page. Catch this to track navigation errors as the NavigateError + // notification does not seem to fire reliably. + GURL crack_url(url->bstrVal); + if (crack_url.scheme() == "res") { + navigation_failed_ = true; + } + return S_OK; + } + + STDMETHOD_(void, OnNavigateComplete2)(IDispatch* dispatch, VARIANT* url) { + DLOG(INFO) << __FUNCTION__; + } + + bool navigation_failed() const { + return navigation_failed_; + } + + void set_main_thread_id(DWORD thread_id) { + main_thread_id_ = thread_id; + } + + protected: + bool navigation_failed_; + + static _ATL_FUNC_INFO kBeforeNavigate2Info; + static _ATL_FUNC_INFO kNavigateComplete2Info; + static _ATL_FUNC_INFO kNavigateErrorInfo; + DWORD main_thread_id_; +}; + +#endif // CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_ + diff --git a/chrome_frame/test/chrometab_unittests.vsprops b/chrome_frame/test/chrometab_unittests.vsprops new file mode 100644 index 0000000..d5375c9 --- /dev/null +++ b/chrome_frame/test/chrometab_unittests.vsprops @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometab_unittests"
+ InheritedPropertySheets="$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="_ATL_APARTMENT_THREADED;_ATL_CSTRING_EXPLICIT_CONSTRUCTORS"
+ AdditionalIncludeDirectories=""
+ />
+
+</VisualStudioPropertySheet>
diff --git a/chrome_frame/test/com_message_event_unittest.cc b/chrome_frame/test/com_message_event_unittest.cc new file mode 100644 index 0000000..f850c20 --- /dev/null +++ b/chrome_frame/test/com_message_event_unittest.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome_frame/com_message_event.h"
+
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+// To allow the unit test read-only access to check protected member variables.
+class FriendlyComMessageEvent : public ComMessageEvent {
+ public:
+ inline IHTMLEventObj* basic_event() { return basic_event_; }
+};
+
+class ATL_NO_VTABLE MockDumbContainer :
+ public CComObjectRoot,
+ public IOleContainer {
+ public:
+ DECLARE_NOT_AGGREGATABLE(MockDumbContainer)
+ BEGIN_COM_MAP(MockDumbContainer)
+ COM_INTERFACE_ENTRY(IParseDisplayName)
+ COM_INTERFACE_ENTRY(IOleContainer)
+ END_COM_MAP()
+
+ STDMETHOD(ParseDisplayName)(IBindCtx*, LPOLESTR, ULONG*, IMoniker**) {
+ NOTREACHED();
+ return E_NOTIMPL;
+ }
+ STDMETHOD(EnumObjects)(DWORD, IEnumUnknown**) {
+ NOTREACHED();
+ return E_NOTIMPL;
+ }
+ STDMETHOD(LockContainer)(BOOL) {
+ NOTREACHED();
+ return E_NOTIMPL;
+ }
+};
+
+TEST(ComMessageEvent, WithDumbContainer) {
+ CComObject<MockDumbContainer>* container_obj = NULL;
+ CComObject<MockDumbContainer>::CreateInstance(&container_obj);
+ ScopedComPtr<IOleContainer> container(container_obj);
+ EXPECT_FALSE(!container);
+
+ CComObject<FriendlyComMessageEvent>* event_obj = NULL;
+ CComObject<FriendlyComMessageEvent>::CreateInstance(&event_obj);
+ ScopedComPtr<IUnknown> event_ref(event_obj);
+
+ bool result = event_obj->Initialize(container, "hi", "http://www.foo.com/",
+ "message");
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(!event_obj->basic_event());
+}
+
+// Mock object to mimic a "smart" container, e.g. IE, that will
+// be able to return an IHTMLDocument2 and 4, and from which you
+// can get an IHTMLEventObj implementation. Doubles as a mock
+// IHTMLEventObj implementation.
+class ATL_NO_VTABLE MockSmartContainer :
+ public CComObjectRoot,
+ public IOleContainer,
+ public IHTMLDocument2,
+ public IHTMLDocument4,
+ public IHTMLEventObj {
+ public:
+ DECLARE_NOT_AGGREGATABLE(MockSmartContainer)
+ BEGIN_COM_MAP(MockSmartContainer)
+ COM_INTERFACE_ENTRY_IID(IID_IDispatch, IHTMLEventObj)
+ COM_INTERFACE_ENTRY(IParseDisplayName)
+ COM_INTERFACE_ENTRY(IOleContainer)
+ COM_INTERFACE_ENTRY(IHTMLDocument)
+ COM_INTERFACE_ENTRY(IHTMLDocument2)
+ COM_INTERFACE_ENTRY(IHTMLDocument4)
+ COM_INTERFACE_ENTRY(IHTMLEventObj)
+ END_COM_MAP()
+
+ static const DISPID kDispId = 424242;
+ static const long kResultValue = 42;
+
+ // Only method we actually implement from IHTMLDocument4, to give
+ // out the mock IHTMLEventObj.
+ STDMETHOD(createEventObject)(VARIANT*, IHTMLEventObj** event_obj) {
+ return GetUnknown()->QueryInterface(event_obj);
+ }
+
+ // Dummy IDispatch implementation for unit testing, to validate
+ // passthrough semantics.
+ STDMETHOD(GetIDsOfNames)(REFIID iid, LPOLESTR* names, UINT num_names,
+ LCID lcid, DISPID* disp_ids) {
+ DCHECK(num_names == 1);
+ disp_ids[0] = kDispId;
+ return S_OK;
+ }
+
+ STDMETHOD(Invoke)(DISPID id, REFIID iid, LCID lcid, WORD flags,
+ DISPPARAMS* disp_params, VARIANT* var_result,
+ EXCEPINFO* excep_info, UINT* arg_error) {
+ var_result->vt = VT_I4;
+ var_result->lVal = kResultValue;
+ return S_OK;
+ }
+
+
+ // Do-nothing implementation of the rest of the interface methods.
+ // To make this less verbose, define a macro here and undefine it
+ // at the end of the list.
+#define STDMETHODNOTIMP(method, parameters) \
+ STDMETHOD(method) parameters { \
+ NOTREACHED(); \
+ return E_NOTIMPL; \
+ }
+
+ // IDispatch
+ STDMETHODNOTIMP(GetTypeInfoCount, (UINT*));
+ STDMETHODNOTIMP(GetTypeInfo, (UINT, LCID, ITypeInfo**));
+
+ // IParseDisplayName
+ STDMETHODNOTIMP(ParseDisplayName, (IBindCtx*, LPOLESTR, ULONG*, IMoniker**));
+ // IOleContainer
+ STDMETHODNOTIMP(EnumObjects, (DWORD, IEnumUnknown**));
+ STDMETHODNOTIMP(LockContainer, (BOOL));
+ // IHTMLDocument
+ STDMETHODNOTIMP(get_Script, (IDispatch**));
+ // IHTMLDocument2
+ STDMETHODNOTIMP(get_all, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_body, (IHTMLElement**));
+ STDMETHODNOTIMP(get_activeElement, (IHTMLElement**));
+ STDMETHODNOTIMP(get_images, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_applets, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_links, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_forms, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_anchors, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(put_title, (BSTR));
+ STDMETHODNOTIMP(get_title, (BSTR*));
+ STDMETHODNOTIMP(get_scripts, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(put_designMode, (BSTR));
+ STDMETHODNOTIMP(get_designMode, (BSTR*));
+ STDMETHODNOTIMP(get_selection, (IHTMLSelectionObject**));
+ STDMETHODNOTIMP(get_readyState, (BSTR*));
+ STDMETHODNOTIMP(get_frames, (IHTMLFramesCollection2**));
+ STDMETHODNOTIMP(get_embeds, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_plugins, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(put_alinkColor, (VARIANT));
+ STDMETHODNOTIMP(get_alinkColor, (VARIANT*));
+ STDMETHODNOTIMP(put_bgColor, (VARIANT));
+ STDMETHODNOTIMP(get_bgColor, (VARIANT*));
+ STDMETHODNOTIMP(put_fgColor, (VARIANT));
+ STDMETHODNOTIMP(get_fgColor, (VARIANT*));
+ STDMETHODNOTIMP(put_linkColor, (VARIANT));
+ STDMETHODNOTIMP(get_linkColor, (VARIANT*));
+ STDMETHODNOTIMP(put_vlinkColor, (VARIANT));
+ STDMETHODNOTIMP(get_vlinkColor, (VARIANT*));
+ STDMETHODNOTIMP(get_referrer, (BSTR*));
+ STDMETHODNOTIMP(get_location, (IHTMLLocation**));
+ STDMETHODNOTIMP(get_lastModified, (BSTR*));
+ STDMETHODNOTIMP(put_URL, (BSTR));
+ STDMETHODNOTIMP(get_URL, (BSTR*));
+ STDMETHODNOTIMP(put_domain, (BSTR));
+ STDMETHODNOTIMP(get_domain, (BSTR*));
+ STDMETHODNOTIMP(put_cookie, (BSTR));
+ STDMETHODNOTIMP(get_cookie, (BSTR*));
+ STDMETHODNOTIMP(put_expando, (VARIANT_BOOL));
+ STDMETHODNOTIMP(get_expando, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(put_charset, (BSTR));
+ STDMETHODNOTIMP(get_charset, (BSTR*));
+ STDMETHODNOTIMP(put_defaultCharset, (BSTR));
+ STDMETHODNOTIMP(get_defaultCharset, (BSTR*));
+ STDMETHODNOTIMP(get_mimeType, (BSTR*));
+ STDMETHODNOTIMP(get_fileSize, (BSTR*));
+ STDMETHODNOTIMP(get_fileCreatedDate, (BSTR*));
+ STDMETHODNOTIMP(get_fileModifiedDate, (BSTR*));
+ STDMETHODNOTIMP(get_fileUpdatedDate, (BSTR*));
+ STDMETHODNOTIMP(get_security, (BSTR*));
+ STDMETHODNOTIMP(get_protocol, (BSTR*));
+ STDMETHODNOTIMP(get_nameProp, (BSTR*));
+ STDMETHODNOTIMP(write, (SAFEARRAY*));
+ STDMETHODNOTIMP(writeln, (SAFEARRAY*));
+ STDMETHODNOTIMP(open, (BSTR, VARIANT, VARIANT, VARIANT, IDispatch**));
+ STDMETHODNOTIMP(close, ());
+ STDMETHODNOTIMP(clear, ());
+ STDMETHODNOTIMP(queryCommandSupported, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandEnabled, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandState, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandIndeterm, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandText, (BSTR, BSTR*));
+ STDMETHODNOTIMP(queryCommandValue, (BSTR, VARIANT*));
+ STDMETHODNOTIMP(execCommand, (BSTR, VARIANT_BOOL, VARIANT, VARIANT_BOOL*));
+ STDMETHODNOTIMP(execCommandShowHelp, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(createElement, (BSTR, IHTMLElement**));
+ STDMETHODNOTIMP(put_onhelp, (VARIANT));
+ STDMETHODNOTIMP(get_onhelp, (VARIANT*));
+ STDMETHODNOTIMP(put_onclick, (VARIANT));
+ STDMETHODNOTIMP(get_onclick, (VARIANT*));
+ STDMETHODNOTIMP(put_ondblclick, (VARIANT));
+ STDMETHODNOTIMP(get_ondblclick, (VARIANT*));
+ STDMETHODNOTIMP(put_onkeyup, (VARIANT));
+ STDMETHODNOTIMP(get_onkeyup, (VARIANT*));
+ STDMETHODNOTIMP(put_onkeydown, (VARIANT));
+ STDMETHODNOTIMP(get_onkeydown, (VARIANT*));
+ STDMETHODNOTIMP(put_onkeypress, (VARIANT));
+ STDMETHODNOTIMP(get_onkeypress, (VARIANT*));
+ STDMETHODNOTIMP(put_onmouseup, (VARIANT));
+ STDMETHODNOTIMP(get_onmouseup, (VARIANT*));
+ STDMETHODNOTIMP(put_onmousedown, (VARIANT));
+ STDMETHODNOTIMP(get_onmousedown, (VARIANT*));
+ STDMETHODNOTIMP(put_onmousemove, (VARIANT));
+ STDMETHODNOTIMP(get_onmousemove, (VARIANT*));
+ STDMETHODNOTIMP(put_onmouseout, (VARIANT));
+ STDMETHODNOTIMP(get_onmouseout, (VARIANT*));
+ STDMETHODNOTIMP(put_onmouseover, (VARIANT));
+ STDMETHODNOTIMP(get_onmouseover, (VARIANT*));
+ STDMETHODNOTIMP(put_onreadystatechange, (VARIANT));
+ STDMETHODNOTIMP(get_onreadystatechange, (VARIANT*));
+ STDMETHODNOTIMP(put_onafterupdate, (VARIANT));
+ STDMETHODNOTIMP(get_onafterupdate, (VARIANT*));
+ STDMETHODNOTIMP(put_onrowexit, (VARIANT));
+ STDMETHODNOTIMP(get_onrowexit, (VARIANT*));
+ STDMETHODNOTIMP(put_onrowenter, (VARIANT));
+ STDMETHODNOTIMP(get_onrowenter, (VARIANT*));
+ STDMETHODNOTIMP(put_ondragstart, (VARIANT));
+ STDMETHODNOTIMP(get_ondragstart, (VARIANT*));
+ STDMETHODNOTIMP(put_onselectstart, (VARIANT));
+ STDMETHODNOTIMP(get_onselectstart, (VARIANT*));
+ STDMETHODNOTIMP(elementFromPoint, (long, long, IHTMLElement**));
+ STDMETHODNOTIMP(get_parentWindow, (IHTMLWindow2**));
+ STDMETHODNOTIMP(get_styleSheets, (IHTMLStyleSheetsCollection**));
+ STDMETHODNOTIMP(put_onbeforeupdate, (VARIANT));
+ STDMETHODNOTIMP(get_onbeforeupdate, (VARIANT*));
+ STDMETHODNOTIMP(put_onerrorupdate, (VARIANT));
+ STDMETHODNOTIMP(get_onerrorupdate, (VARIANT*));
+ STDMETHODNOTIMP(toString, (BSTR*));
+ STDMETHODNOTIMP(createStyleSheet, (BSTR, long, IHTMLStyleSheet**));
+ // IHTMLDocument4
+ STDMETHODNOTIMP(focus, ());
+ STDMETHODNOTIMP(hasFocus, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(put_onselectionchange, (VARIANT));
+ STDMETHODNOTIMP(get_onselectionchange, (VARIANT*));
+ STDMETHODNOTIMP(get_namespaces, (IDispatch**));
+ STDMETHODNOTIMP(createDocumentFromUrl, (BSTR, BSTR, IHTMLDocument2**));
+ STDMETHODNOTIMP(put_media, (BSTR));
+ STDMETHODNOTIMP(get_media, (BSTR*));
+ STDMETHODNOTIMP(fireEvent, (BSTR, VARIANT*, VARIANT_BOOL*));
+ STDMETHODNOTIMP(createRenderStyle, (BSTR, IHTMLRenderStyle**));
+ STDMETHODNOTIMP(put_oncontrolselect, (VARIANT));
+ STDMETHODNOTIMP(get_oncontrolselect, (VARIANT*));
+ STDMETHODNOTIMP(get_URLUnencoded, (BSTR*));
+ // IHTMLEventObj
+ STDMETHODNOTIMP(get_srcElement, (IHTMLElement**))
+ STDMETHODNOTIMP(get_altKey, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(get_ctrlKey, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(get_shiftKey, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(put_returnValue, (VARIANT));
+ STDMETHODNOTIMP(get_returnValue, (VARIANT*));
+ STDMETHODNOTIMP(put_cancelBubble, (VARIANT_BOOL));
+ STDMETHODNOTIMP(get_cancelBubble, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(get_fromElement, (IHTMLElement**));
+ STDMETHODNOTIMP(get_toElement, (IHTMLElement**));
+ STDMETHODNOTIMP(put_keyCode, (long));
+ STDMETHODNOTIMP(get_keyCode, (long*));
+ STDMETHODNOTIMP(get_button, (long*));
+ STDMETHODNOTIMP(get_type, (BSTR*));
+ STDMETHODNOTIMP(get_qualifier, (BSTR*));
+ STDMETHODNOTIMP(get_reason, (long*));
+ STDMETHODNOTIMP(get_x, (long*));
+ STDMETHODNOTIMP(get_y, (long*));
+ STDMETHODNOTIMP(get_clientX, (long*));
+ STDMETHODNOTIMP(get_clientY, (long*));
+ STDMETHODNOTIMP(get_offsetX, (long*));
+ STDMETHODNOTIMP(get_offsetY, (long*));
+ STDMETHODNOTIMP(get_screenX, (long*));
+ STDMETHODNOTIMP(get_screenY, (long*));
+ STDMETHODNOTIMP(get_srcFilter, (IDispatch**));
+#undef STDMETHODNOTIMP
+};
+
+TEST(ComMessageEvent, WithSmartContainer) {
+ CComObject<MockSmartContainer>* container_obj = NULL;
+ CComObject<MockSmartContainer>::CreateInstance(&container_obj);
+ ScopedComPtr<IOleContainer> container(container_obj);
+ EXPECT_FALSE(!container);
+
+ CComObject<FriendlyComMessageEvent>* event_obj = NULL;
+ CComObject<FriendlyComMessageEvent>::CreateInstance(&event_obj);
+ ScopedComPtr<IUnknown> event_ref(event_obj);
+
+ bool succeeded = event_obj->Initialize(container, "hi",
+ "http://www.foo.com/", "message");
+ EXPECT_TRUE(succeeded);
+ EXPECT_FALSE(!event_obj->basic_event());
+
+ // Name handled natively by CF's ComMessageEvent.
+ DISPID dispid = -1;
+ LPOLESTR name = L"data";
+ HRESULT hr = event_obj->GetIDsOfNames(IID_IDispatch, &name, 1,
+ LOCALE_USER_DEFAULT, &dispid);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(dispid, ComMessageEvent::DISPID_MESSAGE_EVENT_DATA);
+
+ // Name not handled by CF's ComMessageEvent.
+ dispid = -1;
+ name = L"nothandledatallbyanyone";
+ hr = event_obj->GetIDsOfNames(IID_IDispatch, &name, 1,
+ LOCALE_USER_DEFAULT, &dispid);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(dispid, MockSmartContainer::kDispId);
+
+ // Invoke function handled by ComMessageEvent.
+ CComDispatchDriver dispatcher(event_obj);
+ CComVariant result;
+ hr = dispatcher.GetProperty(ComMessageEvent::DISPID_MESSAGE_EVENT_DATA,
+ &result);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(result.vt, VT_BSTR);
+ EXPECT_EQ(wcscmp(result.bstrVal, L"hi"), 0);
+
+ // And now check passthrough.
+ result.Clear();
+ hr = dispatcher.GetProperty(MockSmartContainer::kDispId, &result);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(result.vt, VT_I4);
+ EXPECT_EQ(result.lVal, MockSmartContainer::kResultValue);
+}
diff --git a/chrome_frame/test/data/CFInstance_basic_frame.html b/chrome_frame/test/data/CFInstance_basic_frame.html new file mode 100644 index 0000000..11938ee --- /dev/null +++ b/chrome_frame/test/data/CFInstance_basic_frame.html @@ -0,0 +1,8 @@ +<html> + <head> + <title></title> + </head> + <body> + <h1>do nothing</h1> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_basic_host.html b/chrome_frame/test/data/CFInstance_basic_host.html new file mode 100644 index 0000000..6bad61c --- /dev/null +++ b/chrome_frame/test/data/CFInstance_basic_host.html @@ -0,0 +1,55 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceBasic"; + (function(){ + try{ + var cf = new CFInstance({ + src: "CFInstance_basic_frame.html", + node: "toBeReplaced" + }); + + if (document.getElementById("parent") != cf.parentNode ) { + onFailure(testName, 1, "parent node mismatch"); + return; + } + + if (document.getElementById("prev").nextSibling != cf) { + onFailure(testName, 1, "sibling node mismatch"); + return; + } + + if (document.getElementById("after").previousSibling != cf) { + onFailure(testName, 1, "sibling node mismatch"); + return; + } + + onSuccess(testName, 1); + + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame Navigation</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_default_ctor_host.html b/chrome_frame/test/data/CFInstance_default_ctor_host.html new file mode 100644 index 0000000..744c6de --- /dev/null +++ b/chrome_frame/test/data/CFInstance_default_ctor_host.html @@ -0,0 +1,45 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceDefaultCtor"; + (function(){ + try{ + var cf = new CFInstance(); + cf.src = "CFInstance_basic_frame.html"; + var node = document.getElementById("toBeReplaced"); + node.parentNode.replaceChild(cf, node); + var timer = setTimeout(function() { + onFailure(testName, 1, "CFInstance navigation timeout"); + }, 15000); + cf.listen("load", function() { + clearTimeout(timer); + onSuccess(testName, 1); + }); + cf.src = "CFInstance_basic_frame.html"; + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests Chrome Frame constructor without arguments</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_delay_host.html b/chrome_frame/test/data/CFInstance_delay_host.html new file mode 100644 index 0000000..f6d9e02 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_delay_host.html @@ -0,0 +1,47 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceDelay"; + (function(){ + try{ + var cf = new CFInstance({ + onload: function() { + onSuccess(testName, 1); + }, + src: "CFInstance_basic_frame.html" + }); + + setTimeout(function() { + var replNode = document.getElementById("toBeReplaced"); + // impedence matching between new and old CFInstance.js + var node = cf["plugin"] ? cf.plugin : cf; + replNode.parentNode.replaceChild(node, replNode); + }, 100); + + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame Navigation when placed in the document on a delay</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_fallback_host.html b/chrome_frame/test/data/CFInstance_fallback_host.html new file mode 100644 index 0000000..5939180 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_fallback_host.html @@ -0,0 +1,44 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceFallback"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_basic_frame.html", + requirements: [] + }); + + if (cf.tagName.toLowerCase() == "iframe") { + onSuccess(testName, 1); + } else { + onFailure(testName, 1, "expected tagName mismatch"); + } + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame fallback</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_iframe_onload_host.html b/chrome_frame/test/data/CFInstance_iframe_onload_host.html new file mode 100644 index 0000000..913c462 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_iframe_onload_host.html @@ -0,0 +1,41 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceIfrOnload"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_basic_frame.html", + onload: function() { + onSuccess(testName, 1); + }, + requirements: [] // always use an iframe + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_iframe_post_host.html b/chrome_frame/test/data/CFInstance_iframe_post_host.html new file mode 100644 index 0000000..f503522 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_iframe_post_host.html @@ -0,0 +1,50 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceIfrPost"; + (function() { + if (!CFInstance.contentTests.postMessage()) { + onSuccess(testName, 1); + } else { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_post_frame.html", + onload: function() { + cf.postMessage("howdy!"); + }, + onmessage: function(evt) { + if (evt.data == 'hola!') { + onSuccess(testName, 1); + } + }, + requirements: [] // always use an iframe + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_no_src_host.html b/chrome_frame/test/data/CFInstance_no_src_host.html new file mode 100644 index 0000000..379e26d --- /dev/null +++ b/chrome_frame/test/data/CFInstance_no_src_host.html @@ -0,0 +1,43 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceNoSrc"; + (function() { + try { + var cf = new CFInstance({ + src: "", + node: "toBeReplaced" + }); + + // check that we got "src" set to "about:blank" by CFInstance + if (cf.src == "about:blank") { + onSuccess(testName, 1); + } else { + onFailure(testName, 1, "blank URL mismatch"); + } + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame with blank src</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_post_frame.html b/chrome_frame/test/data/CFInstance_post_frame.html new file mode 100644 index 0000000..8055cb2 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_post_frame.html @@ -0,0 +1,26 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + <script> + var cf = CFInstance; + + cf.listen("load", function() { + cf.postMessage("hola!"); + }) + </script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame + CFInstance PostMessage Test + <br>Test for PostMessage from the host browser to iframe and back</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_post_host.html b/chrome_frame/test/data/CFInstance_post_host.html new file mode 100644 index 0000000..09402ac --- /dev/null +++ b/chrome_frame/test/data/CFInstance_post_host.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> + +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstancePost"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_post_frame.html", + onload: function() { + cf.postMessage("howdy!"); + }, + onmessage: function(evt) { + if (evt.data == "hola!") { + onSuccess(testName, 1); + } + } + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_rpc_frame.html b/chrome_frame/test/data/CFInstance_rpc_frame.html new file mode 100644 index 0000000..a7dbfd7 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_frame.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> + +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + <script> + var cf = CFInstance; + cf.rpc.expose("rpcHandler", function(arg) { + cf.rpc.callRemote("handleCallback", ["hola!"]); + }); + cf.rpc.init(); + </script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame + CFInstance PostMessage Test + <br>Test for PostMessage from the host browser to iframe and back</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_rpc_host.html b/chrome_frame/test/data/CFInstance_rpc_host.html new file mode 100644 index 0000000..60680cf --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_host.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> + +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceRPC"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_rpc_frame.html" + }); + + var handleCallback = function(arg) { + // alert(arg); + if (arg == "hola!") { + onSuccess(testName, 1); + } + }; + + cf.rpc.expose("handleCallback", handleCallback); + cf.rpc.init(); + + cf.rpc.callRemote("rpcHandler", ["whatcho talkin 'bout, willis!?"]); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_rpc_internal_frame.html b/chrome_frame/test/data/CFInstance_rpc_internal_frame.html new file mode 100644 index 0000000..8208269 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_internal_frame.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> + +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + <script> + setTimeout(rpcCall, 10000); + function rpcCall() { + var cf = CFInstance; + cf.rpc.callRemote("callback"); + cf.rpc.init(); + } + </script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame + CFInstance PostMessage Test + <br>Test for PostMessage from the host browser to iframe and back</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_rpc_internal_host.html b/chrome_frame/test/data/CFInstance_rpc_internal_host.html new file mode 100644 index 0000000..f350871 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_internal_host.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> + +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceRPCInternal"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_rpc_internal_frame.html" + }); + + + cf.rpc.expose("callback", function(arg) { + onSuccess(testName, 1); + }); + cf.rpc.init(); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_singleton_frame.html b/chrome_frame/test/data/CFInstance_singleton_frame.html new file mode 100644 index 0000000..1ab0302 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_singleton_frame.html @@ -0,0 +1,20 @@ +<html> + <head> + <title>talk to me...</title> + <script type="text/javascript" src="CFInstance.js"></script> + <script type="text/javascript"> + CFInstance.listen("load", function() { + document.body.innerHTML = "sending 'foo'"; + CFInstance.postMessage("foo"); + document.body.innerHTML = "...waiting..."; + }); + CFInstance.listen("message", function(evt) { + document.body.innerHTML = "sending 'baz'"; + CFInstance.postMessage("baz"); + }); + </script> + </head> + <body> + <h1>sends a message to the parent</h1> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_singleton_host.html b/chrome_frame/test/data/CFInstance_singleton_host.html new file mode 100644 index 0000000..3eda108 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_singleton_host.html @@ -0,0 +1,44 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript" src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="toBeReplaced"> + fallback content goes here + </div> + <script type="text/javascript"> + var testName = "CFInstanceSingleton"; + (function() { + try{ + var cf = new CFInstance({ + src: "CFInstance_singleton_frame.html", + node: "toBeReplaced" + }); + + // test a call/response set of actions driven by the CF content + cf.listen("message", function(evt) { + if (evt.data == "foo") { + cf.postMessage("bar"); + } else if(evt.data == "baz") { + onSuccess(testName, 1); + } + }); + + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests ChromeFrame Navigation</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_zero_size_host.html b/chrome_frame/test/data/CFInstance_zero_size_host.html new file mode 100644 index 0000000..94b35c0 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_zero_size_host.html @@ -0,0 +1,41 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceZeroSize"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_basic_frame.html", + cssText: "width: 0px; height: 0px;", + onload: function() { + onSuccess(testName, 1); + } + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/back_to_ie.html b/chrome_frame/test/data/back_to_ie.html new file mode 100644 index 0000000..ad4b61d --- /dev/null +++ b/chrome_frame/test/data/back_to_ie.html @@ -0,0 +1,21 @@ +<html>
+ <head><title>Back to IE</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ var test_name = 'navigate_out';
+ if (isRunningInMSIE()) {
+ onSuccess(test_name, 1);
+ } else {
+ onFailure(test_name, 1, 'Failed');
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="test();">
+ <h2>Redirected!</h2>
+ <p>This page should have loaded in IE</p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/cf_protocol.html b/chrome_frame/test/data/cf_protocol.html new file mode 100644 index 0000000..0036555 --- /dev/null +++ b/chrome_frame/test/data/cf_protocol.html @@ -0,0 +1,20 @@ +<html>
+ <head><title>cf: protocol test</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ if (isRunningInMSIE()) {
+ reloadUsingCFProtocol();
+ } else {
+ onSuccess("chrome_frame_protocol", 1);
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(test, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Redirects the same page to its 'cf:' version</p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/chrome_frame_mime_filter_test.html b/chrome_frame/test/data/chrome_frame_mime_filter_test.html new file mode 100644 index 0000000..7520758 --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_mime_filter_test.html @@ -0,0 +1,32 @@ +<!-- saved from url=(0014)about:internet -->
+<!-- Note that for the above Mark of the Web to work, the comment must
+ be followed by a CR/LF ending, so please do not change the line endings. -->
+<html>
+<!-- This page is meant to load inside a host browser like IE/FF -->
+<head>
+<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+<script type="text/javascript">
+
+function TestIfRunningInChrome() {
+ var is_chrome = /chrome/.test(navigator.userAgent.toLowerCase());
+ if (is_chrome) {
+ onSuccess("MIMEFilter", "MIME filter worked!");
+ } else {
+ onFailure("MIMEFilter", "MIME filter failed :-(",
+ "User agent = " + navigator.userAgent.toLowerCase());
+ }
+}
+
+</script>
+</head>
+
+<body onload="TestIfRunningInChrome();">
+<div id="statusPanel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+
+<p>
+
+</body>
+</html>
\ No newline at end of file diff --git a/chrome_frame/test/data/chrome_frame_resize.html b/chrome_frame/test/data/chrome_frame_resize.html new file mode 100644 index 0000000..afba53b --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_resize.html @@ -0,0 +1,138 @@ +<!-- saved from url=(0014)about:internet -->
+<!-- Please preserve the CR/LF at the end of the previous line. -->
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+<head>
+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+<script type="text/javascript">
+function onLoad() {
+ var chromeFrame = GetChromeFrame();
+ chromeFrame.onmessage = OnChromeFrameResize;
+ setTimeout(NavigateToURL, 100);
+}
+
+function NavigateToURL() {
+ var chromeFrame = GetChromeFrame();
+ chromeFrame.src = "chrome_frame_resize_hosted.html";
+ setTimeout(CheckIfNavigationFailed, 15000);
+}
+
+var navigation_success = false;
+
+function CheckIfNavigationFailed() {
+ if (!navigation_success) {
+ onFailure("Resize", 1, "ChromeFrame Navigation failed");
+ }
+}
+
+function OnNavigationSucceeded() {
+ navigation_success = true;
+ appendStatus("ChromeFrame hosted page loaded, beginning test...");
+ setTimeout(ResizeChromeFrame, 100);
+}
+
+var resize_step = 0;
+
+function ResizeChromeFrame() {
+ var chromeFrame = GetChromeFrame();
+
+ if (resize_step == 0) {
+ appendStatus("Setting chromeFrame to 100x100");
+ resize_step = 1;
+ chromeFrame.width = 100;
+ setTimeout("OnResizeFailure(0)", 2000);
+ } else if (resize_step == 1) {
+ resize_step = 2;
+ chromeFrame.height = 100;
+ setTimeout("OnResizeFailure(1)", 2000);
+ } else if (resize_step == 2) {
+ appendStatus("Setting chromeFrame to 10x10");
+ resize_step = 3;
+ chromeFrame.width = 10;
+ setTimeout("OnResizeFailure(0)", 2000);
+ } else if (resize_step == 3) {
+ resize_step = 4;
+ chromeFrame.height = 10;
+ setTimeout("OnResizeFailure(1)", 2000);
+ }
+
+ // Note that setting the ChromeFrame window to 0x0 (or < 2x2 if we have the
+ // WS_BORDER style defined on our window) currently results
+ // in a check failure from the child chrome.exe process.
+ // TODO(robertshield): Figure out why and fix it.
+}
+
+var resize_step_received = 0;
+
+function OnChromeFrameResize(evt) {
+ resize_step_received++;
+ appendStatus("ChromeFrame resized: " + evt.data + "step=" +
+ resize_step_received);
+
+ if (resize_step == 4) {
+ onSuccess("Resize", 1);
+ } else {
+ setTimeout(ResizeChromeFrame, 100);
+ }
+}
+
+function OnResizeFailure(step) {
+ // It turns out that the hosted page gets two calls to onresize()
+ // every time a single size parameter (i.e. width OR height) is changed.
+ // As such this check doesn't quite guarantee success, but if it fails,
+ // then we should fail the unit test.
+ if (step >= resize_step_received) {
+ onFailure("Resize", 1, "Did not receive resize reply back from frame.");
+ }
+}
+
+function GetChromeFrame() {
+ return window.document.ChromeFrame;
+}
+
+var debug_counter = 0;
+
+function DebugResizeChromeFrame(delta) {
+ var chromeFrame = GetChromeFrame();
+ var newWidth = chromeFrame.clientWidth + delta;
+ var newHeight = chromeFrame.clientHeight + delta;
+
+ appendStatus(debug_counter + ". DEBUG resizing CF to (" + newWidth + "," +
+ newHeight + ")");
+
+ debug_counter++;
+
+ chromeFrame.width = newWidth;
+ chromeFrame.height = newHeight;
+}
+
+</script>
+</head>
+
+<body onload="onLoad();">
+<div id="description" style="border: 2px solid black; width: 100%">
+ Test for resizing the chrome frame control.
+</div>
+<div id="statusPanel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+
+<object id="ChromeFrame" codebase="http://www.google.com"
+ classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"
+ style="border: 1px solid blue">
+ <param name="onload" value="return OnNavigationSucceeded();" />
+ <embed id="ChromeFramePlugin" name="ChromeFrame"
+ onload="return OnNavigationSucceeded();"
+ type="application/chromeframe"
+ style="border: 1px solid green">
+ </embed>
+</object>
+<br />
+<br />
+
+<button onclick="javascript:DebugResizeChromeFrame(20);">Bigger</button>
+<button onclick="javascript:DebugResizeChromeFrame(-20);">Smaller</button>
+
+</body>
+</html>
+
diff --git a/chrome_frame/test/data/chrome_frame_resize_hosted.html b/chrome_frame/test/data/chrome_frame_resize_hosted.html new file mode 100644 index 0000000..95528ec --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_resize_hosted.html @@ -0,0 +1,48 @@ +<html>
+<!-- This page is meant to load inside ChromeFrame -->
+<head>
+
+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+<script type="text/javascript">
+
+function onLoad() {
+ var host = window.externalHost;
+ if (host) {
+ host.postMessage(
+ "ChromeFrame navigated to: " + window.location);
+ } else {
+ appendStatus("Running in non-hosted mode");
+ }
+}
+
+var resize_event_counter = 0;
+
+function OnResizeEvent() {
+ width = window.innerWidth;
+ height = window.innerHeight;
+
+ appendStatus(resize_event_counter + ". Resized to (" + width +
+ "," + height + ")");
+
+ var host = window.externalHost;
+ if (host) {
+ host.postMessage(
+ resize_event_counter + ":(" + width + "," + height + ")");
+ } else {
+ appendStatus("window.externalHost is null!");
+ }
+}
+</script>
+</head>
+
+<body onload="onLoad();" bgcolor="#999999" onresize="OnResizeEvent();">
+<div id="description" style="border: 2px solid black; width: 100%">
+ Hosted resize test component.
+</div>
+
+<div id="statusPanel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+
+</body>
+</html>
diff --git a/chrome_frame/test/data/chrome_frame_tester_helpers.js b/chrome_frame/test/data/chrome_frame_tester_helpers.js new file mode 100644 index 0000000..1c914ee --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_tester_helpers.js @@ -0,0 +1,142 @@ +// +// This script provides some mechanics for testing ChromeFrame +// +function onSuccess(name, id) { + onFinished(name, id, "OK"); +} + +function onFailure(name, id, status) { + onFinished(name, id, status); +} + +function getXHRObject(){ + var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', + 'Msxml2.XMLHTTP.4.0']; + var http = null; + try { + http = new XMLHttpRequest(); + } catch(e) { + } + + if (http) + return http; + + for (var i = 0; i < 3; ++i) { + var progid = XMLHTTP_PROGIDS[i]; + try { + http = new ActiveXObject(progid); + } catch(e) { + } + + if (http) + break; + } + return http; +} + +var reportURL = "/writefile/"; + +function shutdownServer() { + var xhr = getXHRObject(); + if(!xhr) + return; + + xhr.open("POST", "/kill", false); + try { + xhr.send(null); + } catch(e) { + appendStatus("XHR send failed. Error: " + e.description); + } +} + +// Optionally send the server a notification that onload was fired. +// To be called from within window.onload. +function sendOnLoadEvent() { + writeToServer("OnLoadEvent", "loaded"); +} + +function writeToServer(name, result) { + var xhr = getXHRObject(); + if(!xhr) + return; + + // synchronously POST the results + xhr.open("POST", reportURL + name, false); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + try { + xhr.send(result); + } catch(e) { + appendStatus("XHR send failed. Error: " + e.description); + } +} + +function postResult(name, result) { + writeToServer(name, result); + // NOTE: + // not watching for failure or return status issues. What should we do here? + shutdownServer(); +} + +// Finish running a test by setting the status +// and the cookie. +function onFinished(name, id, result) { + appendStatus(result); + + // set a cookie to report the results... + var cookie = name + "." + id + ".status=" + result + "; path=/"; + document.cookie = cookie; + + // ...and POST the status back to the server + postResult(name, result); +} + +function appendStatus(message) { + var statusPanel = document.getElementById("statusPanel"); + if (statusPanel) { + statusPanel.innerHTML += '<BR>' + message; + } +} + +function readCookie(name) { + var cookie_name = name + "="; + var ca = document.cookie.split(';'); + + for(var i = 0 ; i < ca.length ; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1,c.length); + } + if (c.indexOf(cookie_name) == 0) { + return c.substring(cookie_name.length, c.length); + } + } + return null; +} + +function createCookie(name,value,days) { + var expires = ""; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toGMTString(); + } + document.cookie = name+"="+value+expires+"; path=/"; +} + +function eraseCookie(name) { + createCookie(name, "", -1); +} + +function isRunningInMSIE() { + if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) + return true; + + return false; +} + +function reloadUsingCFProtocol() { + var redirect_location = "cf:"; + redirect_location += window.location; + window.location = redirect_location; +} + diff --git a/chrome_frame/test/data/event_listener.html b/chrome_frame/test/data/event_listener.html new file mode 100644 index 0000000..6fbd158 --- /dev/null +++ b/chrome_frame/test/data/event_listener.html @@ -0,0 +1,42 @@ +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> +<head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"></script> + <script language="javascript"> + var g_test_name = 'EventListener'; + + function onChromeFrameLoaded() { + appendStatus('Chrome frame loaded.'); + onSuccess(g_test_name, 1); + } + + function onEventNotFired() { + onFailure(g_test_name, 1, 'Did not receive onload event'); + } + + function onDocumentLoad() { + appendStatus('document loaded'); + var cf = getCf(); + cf.addEventListener("load", onChromeFrameLoaded, false); + setTimeout(onEventNotFired, 10000) + cf.src = "CFInstance_basic_frame.html"; + } + + function getCf() { + return window.document.ChromeFrame; + } + </script> +</head> +<body onload="onDocumentLoad();"> + <object id="ChromeFrame" width="500" height ="300" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <embed id="ChromeFramePlugin" name="ChromeFrame" width="500" + height="500" type="application/chromeframe"> + </embed> + </object> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> +</body> +</html> diff --git a/chrome_frame/test/data/iframe_basic_host.html b/chrome_frame/test/data/iframe_basic_host.html new file mode 100644 index 0000000..f9a4c0c --- /dev/null +++ b/chrome_frame/test/data/iframe_basic_host.html @@ -0,0 +1,13 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> +<head></head> +<body> + <iframe src ="postmessage_basic_host.html" width="50%" height="600">
+ </iframe> + + <br> + <br> + Test for ChromeFrame inside iframe +</body> +</html> diff --git a/chrome_frame/test/data/in_head.html b/chrome_frame/test/data/in_head.html new file mode 100644 index 0000000..a093c71 --- /dev/null +++ b/chrome_frame/test/data/in_head.html @@ -0,0 +1,62 @@ +<html> + <!-- This page is meant to load inside the host browser like IE/FF --> + <head><title>Initialize hidden chrome frame</title> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + var g_failure_timeout = null; + var g_test_id = 1; + var g_test_name = 'InHead'; + var g_cf3_loaded = false; + + function OnNavigationFailed() { + onFailure(g_test_name, g_test_id, 'ChromeFrame Navigation failed'); + } + + function OnObjectFocusFailed() { + appendStatus('chrome frame focus failed'); + onFailure(g_test_name, g_test_id, 'Embed in head test failed'); + } + + function OnFrameMessage(evt) { + appendStatus('Chrome frame visible and focused'); + if (evt.data == 'btnOnFocus') { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + appendStatus('Chrome frame visible and focused'); + + onSuccess(g_test_name, g_test_id); + } + } + + function OnFrameLoad() { + document.ChromeFrame.focus(); + } + + function OnLoad() { + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } + </script> + <object id="ChromeFrame" width="300" height="80" tabindex="0" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="simple_object_focus_cf.html"> + <param name="onload" value="OnFrameLoad();"> + <param name="onloaderror" value="OnNavigationFailed();"> + <param name="onmessage" value="OnFrameMessage(arguments[0]);"> + <embed width="300" height="80" name="ChromeFrame" + type="application/chromeframe" + src="simple_object_focus_cf.html" + onload="OnFrameLoad();" + onloaderror="OnNavigationFailed();" + onmessage="OnFrameMessage(arguments[0]);"> + </embed> + </object> + </head> + <body onload = "OnLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + <div id = "dummy"> </div> + </body> +</html> diff --git a/chrome_frame/test/data/initialize_hidden.html b/chrome_frame/test/data/initialize_hidden.html new file mode 100644 index 0000000..2da0917 --- /dev/null +++ b/chrome_frame/test/data/initialize_hidden.html @@ -0,0 +1,120 @@ +<html> + <!-- This page is meant to load inside the host browser like IE/FF --> + <head><title>Initialize hidden chrome frame</title> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + var g_failure_timeout = null; + var g_test_id = 1; + var g_test_name = 'InitializeHidden'; + var g_cf3_loaded = false; + + function OnNavigationFailed() { + onFailure(g_test_name, g_test_id, 'ChromeFrame Navigation failed'); + } + + function OnObjectFocusFailed() { + appendStatus('chrome frame focus failed'); + onFailure(g_test_name, g_test_id, 'Visibility test failed'); + } + + function OnCF1Loaded() { + appendStatus('Chrome frame 1 loaded, not visible yet.'); + try { + // Make chrome frame visible + var cf1 = document.getElementById('CFDiv1'); + cf1.style.visibility = 'visible'; + appendStatus('Chrome frame 1 visibility - ' + cf1.style.visibility); + // Set focus to chrome frame. This should set focus to the + // first element inside the frame, which a script inside the + // page will detect and notify us back by sending us a message. + document.ChromeFrame1.focus(); + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } catch(e) { + appendStatus('Error setting focus to CF1. Error: ' + e.description); + onFailure(g_test_name, g_test_id, 'CF1 focus() error'); + } + } + + function OnCF1Message(evt) { + if (evt.data == 'btnOnFocus') { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + appendStatus('CF1 visible and focused'); + + // Now make second chrome frame instance visible + document.getElementById('CFDiv2').style.display = 'block'; + appendStatus('Chrome frame 2 visible, should start loading now'); + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } + } + + function OnCF2Loaded() { + appendStatus('Chrome frame 2 loaded'); + try { + // Set focus to chrome frame. This should set focus to the + // first element inside the frame, which a script inside the + // page will detect and notify us back by sending us a message. + // We set focus on a timeout as on IE it takes a while for the window + // to become visible. + setTimeout(SetFocusToCF2, 100); + } catch(e) { + appendStatus('Error setting focus to CF2. Error: ' + e.description); + onFailure(g_test_name, g_test_id, 'CF2 focus() error'); + } + } + + function SetFocusToCF2() { + document.ChromeFrame2.focus(); + } + + function OnCF2Message(evt) { + if (evt.data == 'btnOnFocus') { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + appendStatus('CF2 visible and focused'); + onSuccess(g_test_name, g_test_id); + } + } + </script> + </head> + <body> + <div id="CFDiv1" style="visibility: hidden;"> + <object id="ChromeFrame1" width="300" height="80" tabindex="0" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="simple_object_focus_cf.html"> + <param name="onload" value="OnCF1Loaded();"> + <param name="onloaderror" value="OnNavigationFailed();"> + <param name="onmessage" value="OnCF1Message(arguments[0]);"> + <embed width="300" height="80" name="ChromeFrame1" + type="application/chromeframe" + src="simple_object_focus_cf.html" + onload="OnCF1Loaded();" + onloaderror="OnNavigationFailed();" + onmessage="OnCF1Message(arguments[0]);"> + </embed> + </object> + </div> + <div id="CFDiv2" style="display: none;"> + <object id="ChromeFrame2" width="300" height="80" tabindex="1" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="simple_object_focus_cf.html"> + <param name="onload" value="OnCF2Loaded();"> + <param name="onloaderror" value="OnNavigationFailed();"> + <param name="onmessage" value="OnCF2Message(arguments[0]);"> + <embed width="300" height="80" name="ChromeFrame2" + type="application/chromeframe" + src="simple_object_focus_cf.html" + onload="OnCF2Loaded();" + onloaderror="OnNavigationFailed();" + onmessage="OnCF2Message(arguments[0]);"> + </embed> + </object> + </div> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + </body> +</html> diff --git a/chrome_frame/test/data/meta_tag.html b/chrome_frame/test/data/meta_tag.html new file mode 100644 index 0000000..9b17406 --- /dev/null +++ b/chrome_frame/test/data/meta_tag.html @@ -0,0 +1,21 @@ +<html>
+ <head>
+ <meta http-equiv="x-ua-compatible" content="chrome=1" /> + <title>Load chrome frame using meta tag</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ if (isRunningInMSIE()) {
+ onFailure("meta_tag", 1, "Failed");
+ } else {
+ onSuccess("meta_tag", 1);
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(test, 100);"> + chrome trame in tab mode + </body> +</html> diff --git a/chrome_frame/test/data/navigate_out.html b/chrome_frame/test/data/navigate_out.html new file mode 100644 index 0000000..7b910b4 --- /dev/null +++ b/chrome_frame/test/data/navigate_out.html @@ -0,0 +1,20 @@ +<html>
+ <head><title>Test to make sure that navigations sent back to IE</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ if (isRunningInMSIE()) {
+ reloadUsingCFProtocol();
+ } else {
+ window.location = "back_to_ie.html";
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(test, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Redirects the same page to its 'cf:' version and </p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/navigateurl_absolute_host.html b/chrome_frame/test/data/navigateurl_absolute_host.html new file mode 100644 index 0000000..03e1de2 --- /dev/null +++ b/chrome_frame/test/data/navigateurl_absolute_host.html @@ -0,0 +1,64 @@ +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function onLoad() { + var chromeFrame = GetChromeFrame(); + chromeFrame.onloaderror = OnNavigationFailed; + setTimeout(NavigateToURL, 100); + } + + function NavigateToURL() { + var frame_location = new String(window.location); + frame_location = frame_location.replace( + /navigateurl_absolute_host.html/, "navigateurl_basic_frame.html"); + var chromeFrame = GetChromeFrame(); + chromeFrame.src = frame_location; + setTimeout(OnNavigationTimeout, 10000); + } + + var navigation_success = 0; + + function OnNavigationFailed(msg) { + if (!navigation_success) { + onFailure("NavigateURL", 1, 'ChromeFrame Navigation failed: ' + msg); + } + } + + function OnNavigationTimeout() { + OnNavigationFailed('TIMEOUT'); + } + + function OnChromeFrameLoaded() { + navigation_success = 1; + onSuccess("NavigateURL", 1); + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + + <body onload="onLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + <object id="ChromeFrame" width="500" height="500" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="onload" value="return OnChromeFrameLoaded();"> + <embed id="ChromeFramePlugin" width="500" height="500" + name="ChromeFrame" onload="return OnChromeFrameLoaded();" + type="application/chromeframe"> + </embed> + </OBJECT> + <br /> + <br /> + + <p>Tests ChromeFrame Navigation</p> + + </body> +</html> diff --git a/chrome_frame/test/data/navigateurl_basic_frame.html b/chrome_frame/test/data/navigateurl_basic_frame.html new file mode 100644 index 0000000..4d99b43 --- /dev/null +++ b/chrome_frame/test/data/navigateurl_basic_frame.html @@ -0,0 +1,12 @@ +<html> +<!-- This page is meant to load inside ChromeFrame --> + +<body> +<div id="statusPanel" style="border: 1px solid red; width: 100%"> +Test running.... +</div> + +<p>ChromeFrame NavigateURL Test<br> +Tests ChromeFrame Navigation</p> +</body> +</html> diff --git a/chrome_frame/test/data/navigateurl_relative_host.html b/chrome_frame/test/data/navigateurl_relative_host.html new file mode 100644 index 0000000..06ec63e --- /dev/null +++ b/chrome_frame/test/data/navigateurl_relative_host.html @@ -0,0 +1,60 @@ +<html> + <!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function onLoad() { + var chromeFrame = GetChromeFrame(); + chromeFrame.onloaderror = OnNavigationFailed; + setTimeout(NavigateToURL, 100); + } + + function NavigateToURL() { + var chromeFrame = GetChromeFrame(); + chromeFrame.src = "navigateurl_basic_frame.html"; + setTimeout(OnNavigationTimeout, 10000); + } + + var navigation_complete = 0; + + function OnNavigationFailed(msg) { + if (!navigation_complete) { + onFailure("NavigateURL", 1, 'ChromeFrame Navigation failed: ' + msg); + } + } + + function OnNavigationTimeout() { + OnNavigationFailed('TIMEOUT'); + } + + function OnChromeFrameLoaded() { + navigation_success = 1; + onSuccess("NavigateURL", 1); + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + + <body onload="onLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + <object id="ChromeFrame" width="500" height="500" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="onload" value="return OnChromeFrameLoaded();"> + <embed id="ChromeFramePlugin" width="500" height="500" + name="ChromeFrame" onload="return OnChromeFrameLoaded();" + type="application/chromeframe"> + </embed> + </OBJECT> + <br /> + <br /> + + <p>Tests ChromeFrame Navigation</p> + </body> +</html> diff --git a/chrome_frame/test/data/persistent_cookie_test_page.html b/chrome_frame/test/data/persistent_cookie_test_page.html new file mode 100644 index 0000000..ea56262 --- /dev/null +++ b/chrome_frame/test/data/persistent_cookie_test_page.html @@ -0,0 +1,36 @@ +<html>
+ <head><title>Persistent host browser chrome frame cookie test</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function validatePersistentCookie() {
+ if (readCookie("PersistentCookie1") != "Cookie1" ||
+ readCookie("PersistentCookie2") != "Cookie2") {
+ onFailure("PersistentCookieTest", 1, "Failed");
+ } else {
+ onSuccess("PersistentCookieTest", 1);
+ }
+ eraseCookie("PersistentCookie1");
+ eraseCookie("PersistentCookie2");
+ }
+
+ function setPersistentCookieAndRedirect() {
+ if (isRunningInMSIE()) {
+ eraseCookie("PersistentCookie1");
+ eraseCookie("PersistentCookie2");
+ createCookie("PersistentCookie1", "Cookie1", 365);
+ createCookie("PersistentCookie2", "Cookie2", 365);
+ reloadUsingCFProtocol();
+ } else {
+ validatePersistentCookie();
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(setPersistentCookieAndRedirect, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Sets two persistent cookies in the host and redirects ChromeFrame <br />
+ to the same page </p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/postmessage_basic_frame.html b/chrome_frame/test/data/postmessage_basic_frame.html new file mode 100644 index 0000000..76f8cb3 --- /dev/null +++ b/chrome_frame/test/data/postmessage_basic_frame.html @@ -0,0 +1,27 @@ +<!-- saved from url=(0014)about:internet -->
+<html>
+<!-- This page is meant to load inside ChromeFrame -->
+ <head>
+ <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript">
+ function OnLoad() { + externalHost.onmessage = OnHostMessage; + }
+
+ function OnHostMessage(evt) {
+ appendStatus('Host message: ' + evt.data); + externalHost.postMessage("Hello from ChromeFrame");
+ }
+ </script>
+ </head>
+
+ <body onload="OnLoad();">
+ <div id="statusPanel" style="border: 1px solid red; width: 100%">
+ Test running....
+ </div>
+
+ <p>ChromeFrame PostMessage Test
+ <br>Test for PostMessage from the host browser to ChromeFrame and back</p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/postmessage_basic_host.html b/chrome_frame/test/data/postmessage_basic_host.html new file mode 100644 index 0000000..e5ecef9 --- /dev/null +++ b/chrome_frame/test/data/postmessage_basic_host.html @@ -0,0 +1,69 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + var post_message_reply_received = 0; + + function onChromeFrameLoaded() { + appendStatus('Chrome frame loaded...'); + document.ChromeFrame.postMessage('Hello from host'); + setTimeout(onPostMessageFailure, 10000); + } + + function onNavigationFailed(msg) { + onFailure('PostMessage', 1, 'ChromeFrame Navigation failed: ' + msg); + } + + function onChromeFrameMessage(evt) { + try { + var d = new String(evt.data); + appendStatus('Message: ' + d); + if (d == 'Hello from ChromeFrame') { + post_message_reply_received = 1; + onSuccess('PostMessage', 1); + } else { + onFailure('PostMessage', 1, 'unexpected data'); + } + } catch (e) { + onFailure('PostMessage', 1, 'exception in onChromeFrameMessage'); + } + } + + function onPostMessageFailure() { + if (!post_message_reply_received) { + onFailure('PostMessage', 1, 'Did not receive reply back from frame'); + } + } + </script> + </head> + + <body> + <object id="ChromeFrame" width="500" height ="300" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="postmessage_basic_frame.html"> + <param name="onload" value="onChromeFrameLoaded();"> + <param name="onloaderror" value="onNavigationFailed();"> + <param name="onmessage" value="onChromeFrameMessage(arguments[0]);"> + <embed id="ChromeFramePlugin" name="ChromeFrame" + width="500" height="500" + src="postmessage_basic_frame.html" + type="application/chromeframe" + onload="onChromeFrameLoaded();" + onloaderror="onNavigationFailed();" + onmessage="onChromeFrameMessage(arguments[0]);"> + </embed> + </object> + <br> + <br> + <p>Test for PostMessage from the host browser to ChromeFrame and back</p> + <button onclick="document.ChromeFrame.postMessage('Message from button');"> + Send message to frame</button> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + </body> +</html> diff --git a/chrome_frame/test/data/privileged_apis_frame.html b/chrome_frame/test/data/privileged_apis_frame.html new file mode 100644 index 0000000..9e51152 --- /dev/null +++ b/chrome_frame/test/data/privileged_apis_frame.html @@ -0,0 +1,33 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function OnLoad() { + externalHost.onmessage = OnHostMessage; + } + + function OnHostMessage(evt) { + // Any time we receive a message, we reflect it back both + // with a nonsensical target, and with "*". + appendStatus('Host message: ' + evt.data); + externalHost.postMessage(evt.data, + "privileged_target"); + appendStatus('After postMessage(' + evt.data + ', "privileged_target)"'); + externalHost.postMessage(evt.data); + appendStatus('After postMessage(' + evt.data + '")'); + } + </script> + </head> + + <body onload="OnLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame PrivilegeApis Test + <br>Tests that private messaging is not available to regular web pages</p> + </body> +</html> diff --git a/chrome_frame/test/data/privileged_apis_host.html b/chrome_frame/test/data/privileged_apis_host.html new file mode 100644 index 0000000..7261704 --- /dev/null +++ b/chrome_frame/test/data/privileged_apis_host.html @@ -0,0 +1,85 @@ +<html> + <head><title>Privileged Apis test</title> + <script type='text/javascript' src='chrome_frame_tester_helpers.js'> + </script> + <script type='text/javascript'> + var testName = 'PrivilegedApis'; + function OnNavigationFailed(msg) { + onFailure(testName, 1, 'ChromeFrame Navigation failed: ' + msg); + } + + function OnPrivateMessage() { + onFailure(testName, 1, 'OnPrivateMessage should not execute'); + } + + function OnChromeFrameMessage(evt) { + try { + var d = new String(evt.data); + appendStatus('Message: ' + d); + if (d == 'succeed') { + onSuccess(testName, 1); + } else { + onFailure(testName, 1, 'unexpected data'); + } + } catch (e) { + onFailure(testName, 1, 'exception in OnChromeFrameMessage'); + } + } + + function OnChromeFrameLoaded(url) { + var cf = GetChromeFrame(); + + try { + // Any message received by this listener is a failure. + // This succeeds in FF, but throws an exception in IE. + cf.addEventListener('onprivatemessage', OnPrivateMessage, false); + } catch(e) { + cf.onprivatemessage = + appendStatus('addEventListener onprivatemessage threw exception') + } + + // If this invocation succeeds, then 'fail' is reflected by the frame + // and we fail in the OnChromeFrameMessage handler above. + try { + cf.postPrivateMessage('fail', String(document.location), '*'); + onFailure(testName, 1, 'postPrivateMessage should throw'); + } catch(e) { + } + appendStatus('After postPrivateMessage') + // The frame reflects this twice, first to a bogus target + // and again to the default target '*'. We succeed if we + // get the reflected message to OnChromeFrameMessage and not to + // OnPrivateMessage. + cf.postMessage('succeed'); + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + <body> + <div id='statusPanel' style='border: 1px solid red; width: 100%'> + Test running.... + </div> + + <!-- TODO(siggi): Test setting onprivatemessage in these params --> + <object id='ChromeFrame' width='500' height='500' + codebase='http://www.google.com' + classid='CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A'> + <param name='src' value='privileged_apis_frame.html'> + <param name='onload' value='OnChromeFrameLoaded(arguments[0]);'> + <param name='onloaderror' value='OnNavigationFailed();'> + <param name='onmessage' value='OnChromeFrameMessage(arguments[0]);'> + <embed id='ChromeFramePlugin' width='500' height='500' name='ChromeFrame' + src='privileged_apis_frame.html' + type='application/chromeframe' + onload='OnChromeFrameLoaded(arguments[0]);' + onloaderror='OnNavigationFailed();' + onmessage='return OnChromeFrameMessage(arguments[0]);' + privileged_mode='1' + </embed> + </object> + <p>Tests that privileged apis are unavailable from regular pages</p> + </body> +</html> diff --git a/chrome_frame/test/data/simple_object_focus.html b/chrome_frame/test/data/simple_object_focus.html new file mode 100644 index 0000000..138ffa5 --- /dev/null +++ b/chrome_frame/test/data/simple_object_focus.html @@ -0,0 +1,95 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> +<head> +<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script> +<script type="text/javascript"> +var g_failure_timeout = null; +var g_test_id = 1; +var g_test_name = "ObjectFocus"; + +function onLoad() { + status("onload"); + + try { + var cf = getCf(); + cf.onmessage = OnChromeFrameMessage; + window.setTimeout(NavigateToURL, 100); + } catch(e) { + status("error: onload"); + onFailure(g_test_name, g_test_id, "error in onload"); + } + + sendOnLoadEvent(); +} + +function NavigateToURL() { + try { + status("Navigate to URL"); + var cf = getCf(); + cf.src = "simple_object_focus_cf.html"; + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } catch(e) { + status("error: NavigateToURL"); + onFailure(g_test_name, g_test_id, "NavigateToURL error"); + } +} + +function OnObjectFocusFailed() { + status("OnNavigationFailed"); + onFailure(g_test_name, g_test_id, "focus test failed"); +} + +function OnChromeFrameLoaded() { + status("OnChromeFrameLoaded"); + try { + // Set focus to chrome frame. This should set focus to the first element + // inside the frame, which a script inside the page will detect and notify + // us back by sending us a message. + getCf().focus(); + } catch(e) { + status("error: can't set focus"); + onFailure(g_test_name, g_test_id, "focus() error"); + } +} + +function OnChromeFrameMessage(evt) { + if (evt.data != "btnOnFocus") { + status("unexpected message: " + evt.data + " from " + evt.origin); + } else { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + status("success"); + } + onSuccess(g_test_name, g_test_id); +} + +function getCf() { + // Fetching chrome frame with getElementById doesn't work in Firefox. + // Most likely due to object vs embed. + return document.ChromeFrame; +} + +// Useful while writing and debugging the unit test. +function status(s) { + var panel = document.getElementById("status_panel"); + panel.innerHTML = s; +} + +</script> +</head> +<body onload="onLoad();"> +<div id="status_panel" style="border: 1px solid red; width: 100%"> +Test running.... +</div> +<object id="ChromeFrame" width="300" height="60" tabindex="0" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="onload" value="return OnChromeFrameLoaded();">
+ <embed width="300" height="60" name="ChromeFrame" + onload="return OnChromeFrameLoaded();" + type="application/chromeframe"> + </embed> +</object> +</body> +</html> diff --git a/chrome_frame/test/data/simple_object_focus_cf.html b/chrome_frame/test/data/simple_object_focus_cf.html new file mode 100644 index 0000000..9b06711 --- /dev/null +++ b/chrome_frame/test/data/simple_object_focus_cf.html @@ -0,0 +1,10 @@ +<html> +<!-- This page is meant to load inside Chrome Frame --> + <body> + <button onfocus="externalHost.postMessage('btnOnFocus');"> + hello world</button> + <div id="statusPanel" style="border: 1px solid green; width: 100%"> + Inside Chrome Frame.... + </div> + </body> +</html> diff --git a/chrome_frame/test/data/src_property_frame1.html b/chrome_frame/test/data/src_property_frame1.html new file mode 100644 index 0000000..1eaa3cf --- /dev/null +++ b/chrome_frame/test/data/src_property_frame1.html @@ -0,0 +1,13 @@ +<html>
+ <head><title>src property test - page 1</title>
+ <script type="text/javascript">
+ function redirect(){
+ window.location = "src_property_frame2.html";
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(redirect, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Redirecting to a new page within frame...</p>
+ </body>
+</html>
\ No newline at end of file diff --git a/chrome_frame/test/data/src_property_frame2.html b/chrome_frame/test/data/src_property_frame2.html new file mode 100644 index 0000000..c5c0364 --- /dev/null +++ b/chrome_frame/test/data/src_property_frame2.html @@ -0,0 +1,8 @@ +<html>
+ <head><title>src property test - page 2</title>
+ </head>
+ <body>
+ <h2>Redirected!</h2>
+ <p>All finished.</p>
+ </body>
+</html>
\ No newline at end of file diff --git a/chrome_frame/test/data/src_property_host.html b/chrome_frame/test/data/src_property_host.html new file mode 100644 index 0000000..7b7b358 --- /dev/null +++ b/chrome_frame/test/data/src_property_host.html @@ -0,0 +1,65 @@ +<html>
+ <head><title>src property test</title> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function OnNavigationFailed() { + onFailure("ChromeFrame_SrcTest", 1, "ChromeFrame Navigation failed"); + } + + var load_count = 2; + + function OnChromeFrameLoaded(url) { + url = url.data; + + var chromeFrame = GetChromeFrame(); + var frame_url = chromeFrame.src; + + appendStatus("Loaded URL: " + url + " Frame url: " + frame_url); + load_count--; + + if (load_count) { + // For the first load, the URLs should match. + if (frame_url != url) { + onFailure("SrcProperty", 1, "Url: " + url); + } + } else { + // Previous versions changed the frame URL when internal navigation + // was performed. This does not match how iframes behave, and so we + // report success only in the case that they continue to match, even + // though the "internal" URL is different (and not visible) to the + // external host. + if (frame_url == url) { + onFailure("SrcProperty", 1, "Url: " + url); + } else { + onSuccess("SrcProperty", 1); + } + } + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <object id="ChromeFrame" width="500" height="500" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="src_property_frame1.html"> + <param name="onload" value="return OnChromeFrameLoaded(arguments[0]);"> + <param name="onloaderror" value="return OnNavigationFailed(arguments[0]);"> + <embed id="ChromeFramePlugin" width="500" height="500" name="ChromeFrame" + src="src_property_frame1.html" + type="application/chromeframe" + onload="return OnChromeFrameLoaded(arguments[0]);" + onloaderror="return OnNavigationFailed(arguments[0]);"> + </embed> + </object> + <p>Tests ChromeFrame Navigation</p> + </body> +</html> diff --git a/chrome_frame/test/data/version.html b/chrome_frame/test/data/version.html new file mode 100644 index 0000000..b585a6d --- /dev/null +++ b/chrome_frame/test/data/version.html @@ -0,0 +1,29 @@ +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function onLoad() { + appendStatus('Chrome frame version: ' + document.ChromeFrame.version); + onFinished('version', 1, document.ChromeFrame.version); + } + </script> + </head> + + <body onload="onLoad();"> + <object id="ChromeFrame" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <embed id="ChromeFramePlugin" name="ChromeFrame" + type="application/chromeframe" + </embed> + </object> + <br> + <br> + <p>Test for Chrome frame version</p> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + </body> +</html> diff --git a/chrome_frame/test/function_stub_unittest.cc b/chrome_frame/test/function_stub_unittest.cc new file mode 100644 index 0000000..6ef6f36 --- /dev/null +++ b/chrome_frame/test/function_stub_unittest.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/function_stub.h" + +#include "chrome_frame/test/chrome_frame_unittests.h" + +#define NO_INLINE __declspec(noinline) + +namespace { + +typedef int (__stdcall* FooPrototype)(); + +NO_INLINE int __stdcall Foo() { + return 1; +} + +NO_INLINE int __stdcall PatchedFoo(FooPrototype original) { + return original() + 1; +} + +} // end namespace + +TEST(PatchTests, FunctionStub) { + EXPECT_EQ(Foo(), 1); + // Create a function stub that calls PatchedFoo and supplies it with + // a pointer to Foo. + FunctionStub* stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), + &PatchedFoo); + EXPECT_TRUE(stub != NULL); + // Call the stub as it were Foo(). The call should get forwarded to Foo(). + FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code()); + EXPECT_EQ(patch(), 2); + // Now neutralize the stub so that it calls Foo() directly without touching + // PatchedFoo(). + // stub->BypassStub(&Foo); + stub->BypassStub(reinterpret_cast<void*>(stub->argument())); + EXPECT_EQ(patch(), 1); + // We're done with the stub. + FunctionStub::Destroy(stub); +} + +// Basic tests to check the validity of a stub. +TEST(PatchTests, FunctionStubCompare) { + EXPECT_EQ(Foo(), 1); + + // Detect the absence of a stub + FunctionStub* stub = reinterpret_cast<FunctionStub*>(&Foo); + EXPECT_FALSE(stub->is_valid()); + + stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), &PatchedFoo); + EXPECT_TRUE(stub != NULL); + EXPECT_TRUE(stub->is_valid()); + + FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code()); + EXPECT_EQ(patch(), 2); + + // See if we can get the correct absolute pointer to the hook function + // back from the stub. + EXPECT_EQ(stub->absolute_target(), reinterpret_cast<uintptr_t>(&PatchedFoo)); + + // Also verify that the argument being passed to the hook function is indeed + // the pointer to the original function (again, absolute not relative). + EXPECT_EQ(stub->argument(), reinterpret_cast<uintptr_t>(&Foo)); +} diff --git a/chrome_frame/test/helper_gmock.h b/chrome_frame/test/helper_gmock.h new file mode 100644 index 0000000..7f6d0a7 --- /dev/null +++ b/chrome_frame/test/helper_gmock.h @@ -0,0 +1,597 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_HELPER_GMOCK_H_ +#define CHROME_FRAME_TEST_HELPER_GMOCK_H_ +// This intention of this file is to make possible gmock WithArgs<> in +// Chromium code base. +// MutantImpl is like CallbackImpl, but also has prebound arguments (like Task) +// There is also functor wrapper around it that should be used with +// testing::Invoke, for example: +// testing::WithArgs<0, 2>( +// testing::Invoke(CBF(&mock_object, &MockObject::Something, &tmp_obj, 12))); +// This will invoke MockObject::Something(tmp_obj, 12, arg_0, arg_2) + +// DispatchToMethod supporting two sets of arguments - +// prebound (P) and calltime (C) +// 1 - 1 +template <class ObjT, class Method, class P1, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, c.a); +} +// 2 - 1 +template <class ObjT, class Method, class P1, class P2, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, p.b, c.a); +} +// 3 - 1 +template <class ObjT, class Method, class P1, class P2, class P3, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, p.b, p.c, c.a); +} +// 4 - 1 +template <class ObjT, class Method, class P1, class P2, class P3, + class P4, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a); +} + +// 1 - 2 +template <class ObjT, class Method, class P1, class C1, class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, c.a, c.b); +} + +// 2 - 2 +template <class ObjT, class Method, class P1, class P2, class C1, class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, p.b, c.a, c.b); +} + +// 3 - 2 +template <class ObjT, class Method, class P1, class P2, class P3, class C1, + class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, p.b, p.c, c.a, c.b); +} + +// 4 - 2 +template <class ObjT, class Method, class P1, class P2, class P3, class P4, + class C1, class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b); +} + +// 1 - 3 +template <class ObjT, class Method, class P1, class C1, class C2, class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, c.a, c.b, c.c); +} + +// 2 - 3 +template <class ObjT, class Method, class P1, class P2, class C1, class C2, + class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, p.b, c.a, c.b, c.c); +} + +// 3 - 3 +template <class ObjT, class Method, class P1, class P2, class P3, class C1, + class C2, class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c); +} + +// 4 - 3 +template <class ObjT, class Method, class P1, class P2, class P3, class P4, + class C1, class C2, class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c); +} + +// 1 - 4 +template <class ObjT, class Method, class P1, class C1, class C2, class C3, + class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, c.a, c.b, c.c, c.d); +} + +// 2 - 4 +template <class ObjT, class Method, class P1, class P2, class C1, class C2, + class C3, class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, p.b, c.a, c.b, c.c, c.d); +} + +// 3 - 4 +template <class ObjT, class Method, class P1, class P2, class P3, + class C1, class C2, class C3, class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c, c.d); +} + +// 4 - 4 +template <class ObjT, class Method, class P1, class P2, class P3, class P4, + class C1, class C2, class C3, class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d); +} + + +//////////////////////////////////////////////////////////////////////////////// + +// Like CallbackImpl but has prebound arguments (like Task) +template <class T, typename Method, typename PreBound, typename Params> +class MutantImpl : public CallbackStorage<T, Method>, + public CallbackRunner<Params> { + public: + MutantImpl(T* obj, Method meth, const PreBound& pb) + : CallbackStorage<T, Method>(obj, meth), + pb_(pb) { + } + + virtual void RunWithParams(const Params& params) { + // use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + DispatchToMethod(this->obj_, this->meth_, pb_, params); + } + + PreBound pb_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Mutant creation simplification +// 1 - 1 +template <class T, typename P1, typename A1> +inline typename Callback1<A1>::Type* NewMutant(T* obj, + void (T::*method)(P1, A1), + P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1), P1, A1>(obj, method, + MakeTuple(p1)); +} + +// 1 - 2 +template <class T, typename P1, typename A1, typename A2> +inline typename Callback2<A1, A2>::Type* NewMutant(T* obj, + void (T::*method)(P1, A1, A2), + P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1, A2), Tuple1<P1>, Tuple2<A1, A2> > + (obj, method, MakeTuple(p1)); +} + +// 1 - 3 +template <class T, typename P1, typename A1, typename A2, typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, A1, A2, A3), P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1, A2, A3), Tuple1<P1>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1)); +} + +// 1 - 4 +template <class T, typename P1, typename A1, typename A2, typename A3, + typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, A1, A2, A3, A4), P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1, A2, A3, A4), Tuple1<P1>, + Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1)); +} + + +// 2 - 1 +template <class T, typename P1, typename P2, typename A1> +inline typename Callback1<A1>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1), Tuple2<P1, P2>, + Tuple1<A1> >(obj, method, MakeTuple(p1, p2)); +} + +// 2 - 2 +template <class T, typename P1, typename P2, typename A1, typename A2> +inline typename Callback2<A1, A2>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1, A2), Tuple2<P1, P2>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2)); +} + +// 2 - 3 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2, A3), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3), Tuple2<P1, P2>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2)); +} + +// 2 - 4 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3, typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2, A3, A4), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3, A4), Tuple2<P1, P2>, + Tuple3<A1, A2, A3, A4> >(obj, method, MakeTuple(p1, p2)); +} + +// 3 - 1 +template <class T, typename P1, typename P2, typename P3, typename A1> +inline typename Callback1<A1>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1), P1 p1, P2 p2, P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1), Tuple3<P1, P2, P3>, + Tuple1<A1> >(obj, method, MakeTuple(p1, p2, p3)); +} + +// 3 - 2 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2> +inline typename Callback2<A1, A2>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2), P1 p1, P2 p2, P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2), Tuple3<P1, P2, P3>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3)); +} + +// 3 - 3 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3), P1 p1, P2 p2, + P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3), + Tuple3<P1, P2, P3>, Tuple3<A1, A2, A3> >(obj, method, + MakeTuple(p1, p2, p3)); +} + +// 3 - 4 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3, typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3, A4), P1 p1, P2 p2, + P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3, A4), + Tuple3<P1, P2, P3>, Tuple3<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3)); +} + +// 4 - 1 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1> +inline typename Callback1<A1>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1), P1 p1, P2 p2, P3 p3, + P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1), + Tuple4<P1, P2, P3, P4>, Tuple1<A1> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +// 4 - 2 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2> +inline typename Callback2<A1, A2>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2), P1 p1, P2 p2, + P3 p3, P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2), + Tuple4<P1, P2, P3, P4>, Tuple2<A1, A2> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +// 4 - 3 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3), P1 p1, P2 p2, + P3 p3, P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3), + Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +// 4 - 4 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3, typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3, A4), + P1 p1, P2 p2, P3 p3, P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3, A4), + Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Simple callback wrapper acting as a functor. +// Redirects operator() to CallbackRunner<Params>::Run +// We cannot delete the inner impl_ in object's destructor because +// this object is copied few times inside from GMock machinery. +template <typename Params> +struct CallbackFunctor { + explicit CallbackFunctor(CallbackRunner<Params>* cb) : impl_(cb) {} + + template <typename Arg1> + inline void operator()(const Arg1& a) { + impl_->Run(a); + delete impl_; + impl_ = NULL; + } + + template <typename Arg1, typename Arg2> + inline void operator()(const Arg1& a, const Arg2& b) { + impl_->Run(a, b); + delete impl_; + impl_ = NULL; + } + + template <typename Arg1, typename Arg2, typename Arg3> + inline void operator()(const Arg1& a, const Arg2& b, const Arg3& c) { + impl_->Run(a, b, c); + delete impl_; + impl_ = NULL; + } + + template <typename Arg1, typename Arg2, typename Arg3, typename Arg4> + inline void operator()(const Arg1& a, const Arg2& b, const Arg3& c, + const Arg4& d) { + impl_->Run(a, b, c, d); + delete impl_; + impl_ = NULL; + } + + private: + CallbackFunctor(); + CallbackRunner<Params>* impl_; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// CallbackFunctors creation + +// 0 - 1 +template <class T, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(A1)) { + return CallbackFunctor<Tuple1<A1> >(NewCallback(obj, method)); +} + +// 0 - 2 +template <class T, typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(A1, A2)) { + return CallbackFunctor<Tuple2<A1, A2> >(NewCallback(obj, method)); +} + +// 0 - 3 +template <class T, typename A1, typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(A1, A2, A3)) { + return CallbackFunctor<Tuple3<A1, A2, A3> >(NewCallback(obj, method)); +} + +// 0 - 4 +template <class T, typename A1, typename A2, typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(A1, A2, A3, A4)) { + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(NewCallback(obj, method)); +} + +// 1 - 1 +template <class T, typename P1, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, A1), P1 p1) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, A1), Tuple1<P1>, + Tuple1<A1> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple1<A1> >(t); +} + +// 1 - 2 +template <class T, typename P1, typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, A1, A2), P1 p1) { + Callback2<A1, A2>::Type* t = new MutantImpl<T, void (T::*)(P1, A1, A2), + Tuple1<P1>, Tuple2<A1, A2> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 1 - 3 +template <class T, typename P1, typename A1, typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, A1, A2, A3), P1 p1) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, A1, A2, A3), Tuple1<P1>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 1 - 4 +template <class T, typename P1, typename A1, typename A2, typename A3, + typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, A1, A2, A3, A4), P1 p1) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, A1, A2, A3, A4), Tuple1<P1>, + Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + +// 2 - 1 +template <class T, typename P1, typename P2, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, P2, A1), P1 p1, P2 p2) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, A1), + Tuple2<P1, P2>, Tuple1<A1> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple1<A1> >(t); +} + +// 2 - 2 +template <class T, typename P1, typename P2, typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, P2, A1, A2), P1 p1, P2 p2) { + Callback2<A1, A2>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, A1, A2), + Tuple2<P1, P2>, Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 2 - 3 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3> inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, P2, A1, A2, A3), P1 p1, P2 p2) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3), Tuple2<P1, P2>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 2 - 4 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, P2, A1, A2, A3, A4), P1 p1, P2 p2) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3, A4), Tuple2<P1, P2>, + Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + + +// 3 - 1 +template <class T, typename P1, typename P2, typename P3, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1), P1 p1, P2 p2, P3 p3) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, P3, A1), + Tuple3<P1, P2, P3>, Tuple1<A1> >(obj, method, MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple1<A1> >(t); +} + + +// 3 - 2 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2> inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2), P1 p1, P2 p2, P3 p3) { + Callback2<A1, A2>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2), Tuple3<P1, P2, P3>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 3 - 3 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3), P1 p1, P2 p2, P3 p3) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3), Tuple3<P1, P2, P3>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 3 - 4 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3, A4), + P1 p1, P2 p2, P3 p3) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3, A4), + Tuple3<P1, P2, P3>, Tuple4<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + + + +// 4 - 1 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1), P1 p1, P2 p2, P3 p3, P4 p4) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1), + Tuple4<P1, P2, P3, P4>, Tuple1<A1> > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple1<A1> >(t); +} + + +// 4 - 2 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2), + P1 p1, P2 p2, P3 p3, P4 p4) { + Callback2<A1, A2>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2), + Tuple4<P1, P2, P3, P4>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 4 - 3 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3), + P1 p1, P2 p2, P3 p3, P4 p4) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3), + Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3> > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 4 - 4 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3, A4), + P1 p1, P2 p2, P3 p3, P4 p4) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3, A4), + Tuple4<P1, P2, P3, P4>, Tuple4<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + + +// Simple task wrapper acting as a functor. +// Redirects operator() to Task::Run. We cannot delete the inner impl_ object +// in object's destructor because this object is copied few times inside +// from GMock machinery. +struct TaskHolder { + explicit TaskHolder(Task* impl) : impl_(impl) {} + void operator()() { + impl_->Run(); + delete impl_; + impl_ = NULL; + } + private: + TaskHolder(); + Task* impl_; +}; + +#endif // CHROME_FRAME_TEST_HELPER_GMOCK_H_ diff --git a/chrome_frame/test/html_util_test_data/basic_test.html b/chrome_frame/test/html_util_test_data/basic_test.html new file mode 100644 index 0000000..f0cd17a --- /dev/null +++ b/chrome_frame/test/html_util_test_data/basic_test.html @@ -0,0 +1,11 @@ +<HTML>
+
+ <HEAD>
+ <!-- Note the capitalization in CONtent to test the
+ case-insensitiveness -->
+ <META http-equiv="X-UA-Compatible" CONtent="chrome=1" />
+ </HEAD>
+ <BODY>
+
+ Wooo!
+ </BODY></HTML>
diff --git a/chrome_frame/test/html_util_test_data/degenerate_cases_test.html b/chrome_frame/test/html_util_test_data/degenerate_cases_test.html new file mode 100644 index 0000000..d527496a --- /dev/null +++ b/chrome_frame/test/html_util_test_data/degenerate_cases_test.html @@ -0,0 +1,7 @@ +<><foo ">
+</head>
+<"foo">
+<!-- Note that the meta tag shouldn't be picked up since we are still
+ inside a quote block. -->
+<META http-equiv="X-UA-Compatible" CONtent="chrome=1" />
+<fee>
diff --git a/chrome_frame/test/html_util_test_data/multiple_tags.html b/chrome_frame/test/html_util_test_data/multiple_tags.html new file mode 100644 index 0000000..9bd5cea --- /dev/null +++ b/chrome_frame/test/html_util_test_data/multiple_tags.html @@ -0,0 +1,17 @@ +<HTML><HEAD>
+
+ <META http-equiv="X-UA-Compatible" CONtent="chrome=1" />
+ <META http-equiv="X-UA-Compatible" content="chrome=1" />
+ <METAman http-equiv="X-UA-Compatible" CONtent="notchrome=1" />
+ <transMETA http-equiv="X-UA-Compatible" CONtent="notchrome=1" />
+ <IMETAGIRL http-equiv="X-UA-Compatible" CONtent="notchrome=1" />
+ <metA http-equiv="X-UA-Compatible" content="chrome=1" />
+ <!-- shouldn't pick up commented meta tags! -->
+ <!-- <metA http-equiv="X-UA-Compatible" content="chrome=1" /> -->
+
+ <!-- The following cases should also not be matched -->
+ <meta http-equiv="X-UA-Compatibleeee" content="chrome=1" />
+ <meta http-equiv="X-UA-Compatible!" content="chrome=1" />
+ <meta http-equiv="!X-UA-Compatible" content="chrome=1" />
+ <meta http-equiv="\"X-UA-Compatible\"" content="chrome=1" />
+<fee>
\ No newline at end of file diff --git a/chrome_frame/test/html_util_test_data/quotes_test.html b/chrome_frame/test/html_util_test_data/quotes_test.html new file mode 100644 index 0000000..03ce96d --- /dev/null +++ b/chrome_frame/test/html_util_test_data/quotes_test.html @@ -0,0 +1,10 @@ +<HTML>
+
+ <HEAD>
+ <DANGER red="herring>'" testing = "do'><><>quotes\"\\'work?">
+ <META http-equiv=X-UA-Compatible CONtent="chrome=1" />
+ </HEAD>
+ <BODY>
+
+ Wooo!
+ </BODY></HTML>
diff --git a/chrome_frame/test/html_util_unittests.cc b/chrome_frame/test/html_util_unittests.cc new file mode 100644 index 0000000..131b185 --- /dev/null +++ b/chrome_frame/test/html_util_unittests.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <windows.h>
+#include <atlsecurity.h>
+#include <shellapi.h>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/ref_counted.h"
+#include "base/scoped_handle.h"
+#include "base/task.h"
+#include "base/win_util.h"
+#include "net/base/net_util.h"
+
+#include "chrome_frame/test/chrome_frame_unittests.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/chrome_frame_delegate.h"
+#include "chrome_frame/html_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class HtmlUtilUnittest : public testing::Test {
+ protected:
+ // Constructor
+ HtmlUtilUnittest() {}
+
+ // Returns the test path given a test case.
+ virtual bool GetTestPath(const std::wstring& test_case, std::wstring* path) {
+ if (!path) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::wstring test_path;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &test_path)) {
+ NOTREACHED();
+ return false;
+ }
+
+ file_util::AppendToPath(&test_path, L"chrome_frame");
+ file_util::AppendToPath(&test_path, L"test");
+ file_util::AppendToPath(&test_path, L"html_util_test_data");
+ file_util::AppendToPath(&test_path, test_case);
+
+ *path = test_path;
+ return true;
+ }
+
+ virtual bool GetTestData(const std::wstring& test_case, std::wstring* data) {
+ if (!data) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::wstring path;
+ if (!GetTestPath(test_case, &path)) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::string raw_data;
+ file_util::ReadFileToString(path, &raw_data);
+
+ // Convert to wide using the "best effort" assurance described in
+ // string_util.h
+ data->assign(UTF8ToWide(raw_data));
+ return true;
+ }
+};
+
+TEST_F(HtmlUtilUnittest, BasicTest) {
+ std::wstring test_data;
+ GetTestData(L"basic_test.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_EQ(1, tag_list.size());
+
+ // Pull out the http-equiv attribute and check its value:
+ HTMLScanner::StringRange attribute_value;
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));
+
+ // Pull out the content attribute and check its value:
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+}
+
+TEST_F(HtmlUtilUnittest, QuotesTest) {
+ std::wstring test_data;
+ GetTestData(L"quotes_test.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_EQ(1, tag_list.size());
+
+ // Pull out the http-equiv attribute and check its value:
+ HTMLScanner::StringRange attribute_value;
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));
+
+ // Pull out the content attribute and check its value:
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+}
+
+TEST_F(HtmlUtilUnittest, DegenerateCasesTest) {
+ std::wstring test_data;
+ GetTestData(L"degenerate_cases_test.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Scan for meta tags in the document. We expect not to pick up the one
+ // that appears to be there since it is technically inside a quote block.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_TRUE(tag_list.empty());
+}
+
+TEST_F(HtmlUtilUnittest, MultipleTagsTest) {
+ std::wstring test_data;
+ GetTestData(L"multiple_tags.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly three.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_EQ(7, tag_list.size());
+
+ // Pull out the content attribute for each tag and check its value:
+ HTMLScanner::StringRange attribute_value;
+ HTMLScanner::StringRangeList::const_iterator tag_list_iter(
+ tag_list.begin());
+ int valid_tag_count = 0;
+ for (; tag_list_iter != tag_list.end(); tag_list_iter++) {
+ HTMLScanner::StringRange attribute_value;
+ if (tag_list_iter->GetTagAttribute(L"http-equiv", &attribute_value) &&
+ attribute_value.Equals(L"X-UA-Compatible")) {
+ EXPECT_TRUE(tag_list_iter->GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+ valid_tag_count++;
+ }
+ }
+ EXPECT_EQ(3, valid_tag_count);
+}
+
+TEST_F(HtmlUtilUnittest, ShortDegenerateTest1) {
+ std::wstring test_data(
+ L"<foo><META http-equiv=X-UA-Compatible content='chrome=1'");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Scan for meta tags in the document. We expect not to pick up the one
+ // that is there since it is not properly closed.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_TRUE(tag_list.empty());
+}
+
+TEST_F(HtmlUtilUnittest, ShortDegenerateTest2) {
+ std::wstring test_data(
+ L"<foo <META http-equiv=X-UA-Compatible content='chrome=1'/>");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Scan for meta tags in the document. We expect not to pick up the one
+ // that appears to be there since it is inside a non-closed tag.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_TRUE(tag_list.empty());
+}
+
+TEST_F(HtmlUtilUnittest, QuoteInsideHTMLCommentTest) {
+ std::wstring test_data(
+ L"<!-- comment' --><META http-equiv=X-UA-Compatible content='chrome=1'/>");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_EQ(1, tag_list.size());
+
+ // Pull out the http-equiv attribute and check its value:
+ HTMLScanner::StringRange attribute_value;
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));
+
+ // Pull out the content attribute and check its value:
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+}
+
+TEST_F(HtmlUtilUnittest, CloseTagInsideHTMLCommentTest) {
+ std::wstring test_data(
+ L"<!-- comment> <META http-equiv=X-UA-Compatible content='chrome=1'/>-->");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_TRUE(tag_list.empty());
+}
diff --git a/chrome_frame/test/http_server.cc b/chrome_frame/test/http_server.cc new file mode 100644 index 0000000..f2cc333 --- /dev/null +++ b/chrome_frame/test/http_server.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "chrome_frame/test/http_server.h" + +const wchar_t kDocRoot[] = L"chrome_frame\\test\\data"; + +void ChromeFrameHTTPServer::SetUp() { + std::wstring document_root(kDocRoot); + server_ = HTTPTestServer::CreateServer(document_root, NULL, 30, 1000); + ASSERT_TRUE(server_ != NULL); + + // copy CFInstance.js into the test directory + FilePath cf_source_path; + PathService::Get(base::DIR_SOURCE_ROOT, &cf_source_path); + cf_source_path = cf_source_path.Append(FILE_PATH_LITERAL("chrome_frame")); + + file_util::CopyFile(cf_source_path.Append(FILE_PATH_LITERAL("CFInstance.js")), + cf_source_path.Append( + FILE_PATH_LITERAL("test")).Append( + FILE_PATH_LITERAL("data")).Append( + FILE_PATH_LITERAL("CFInstance.js"))); // NOLINT +} + +void ChromeFrameHTTPServer::TearDown() { + if (server_) { + server_ = NULL; + } + + // clobber CFInstance.js + FilePath cfi_path; + PathService::Get(base::DIR_SOURCE_ROOT, &cfi_path); + cfi_path = cfi_path + .Append(FILE_PATH_LITERAL("chrome_frame")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("CFInstance.js")); + + file_util::Delete(cfi_path, false); +} + +bool ChromeFrameHTTPServer::WaitToFinish(int milliseconds) { + if (!server_) + return true; + + return server_->WaitToFinish(milliseconds); +} + +GURL ChromeFrameHTTPServer::Resolve(const wchar_t* relative_url) { + return server_->TestServerPageW(relative_url); +} + +std::wstring ChromeFrameHTTPServer::GetDataDir() { + return server_->GetDataDirectory().ToWStringHack(); +} + diff --git a/chrome_frame/test/http_server.h b/chrome_frame/test/http_server.h new file mode 100644 index 0000000..acac5b5 --- /dev/null +++ b/chrome_frame/test/http_server.h @@ -0,0 +1,32 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_HTTP_SERVER_H_ +#define CHROME_FRAME_TEST_HTTP_SERVER_H_ + +#include <windows.h> +#include <string> + +#include "googleurl/src/gurl.h" +#include "base/ref_counted.h" +#include "net/url_request/url_request_unittest.h" + +// chrome frame specilization of http server from net. +class ChromeFrameHTTPServer { + public: + void SetUp(); + void TearDown(); + bool WaitToFinish(int milliseconds); + GURL Resolve(const wchar_t* relative_url); + std::wstring GetDataDir(); + + HTTPTestServer* server() { + return server_; + } + + protected: + scoped_refptr<HTTPTestServer> server_; +}; + +#endif // CHROME_FRAME_TEST_HTTP_SERVER_H_ + diff --git a/chrome_frame/test/icu_stubs_unittests.cc b/chrome_frame/test/icu_stubs_unittests.cc new file mode 100644 index 0000000..4da4a40 --- /dev/null +++ b/chrome_frame/test/icu_stubs_unittests.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/chrome_frame_unittests.h" + +// Need to include these first since they're included +#include "base/logging.h" +#include "base/string_util.h" +#include "googleurl/src/url_canon.h" + +// Include the implementation of our stubs into a special namespace so that +// we can separate them from Chrome's implementation. +namespace icu_stubs { + // This struct is only to avoid build problems for the two googleurl stubs + // that currently are noops. + struct CanonOutputW { }; + + #include "chrome_frame/icu_stubs.cc" +} // namespace icu_stubs + +// anonymous namespace for test data. +namespace { + + // Test strings borrowed from base/string_util_unittest.cc + static const wchar_t* const kConvertRoundtripCases[] = { + L"", + L"Google Vid¯ôfY »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // " ±³ºÌü¹¿Â ÃÄÌÂ" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // ">8A: AB@0=8F =0 @CAA:><" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "È´ÌÁD¾¤Â" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. + #if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", + #elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", + #endif + }; + +} // namespace + +TEST(IcuStubsTests, UTF8AndWideStubTest) { + // Test code borrowed from ConvertUTF8AndWide in base/string_util_unittest.cc. + + // The difference is that we want to make sure that our stubs work the same + // way as chrome's implementation of WideToUTF8 and UTF8ToWide. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::ostringstream utf8_base, utf8_stub; + utf8_base << WideToUTF8(kConvertRoundtripCases[i]); + utf8_stub << icu_stubs::WideToUTF8(kConvertRoundtripCases[i]); + + EXPECT_EQ(utf8_base.str(), utf8_stub.str()); + + std::wostringstream wide_base, wide_stub; + wide_base << UTF8ToWide(utf8_base.str()); + wide_stub << icu_stubs::UTF8ToWide(utf8_base.str()); + + EXPECT_EQ(wide_base.str(), wide_stub.str()); + } +} diff --git a/chrome_frame/test/net/dialog_watchdog.cc b/chrome_frame/test/net/dialog_watchdog.cc new file mode 100644 index 0000000..27a01a0 --- /dev/null +++ b/chrome_frame/test/net/dialog_watchdog.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <oleacc.h> + +#include "chrome_frame/test/net/dialog_watchdog.h" + +#include "base/logging.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" + +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/function_stub.h" + +namespace { +// Uses the IAccessible interface for the window to set the focus. +// This can be useful when you don't have control over the thread that +// owns the window. +// NOTE: this depends on oleacc.lib which the net tests already depend on +// but other unit tests don't depend on oleacc so we can't just add the method +// directly into chrome_frame_test_utils.cc (without adding a +// #pragma comment(lib, "oleacc.lib")). +bool SetFocusToAccessibleWindow(HWND hwnd) { + bool ret = false; + ScopedComPtr<IAccessible> acc; + AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, + reinterpret_cast<void**>(acc.Receive())); + if (acc) { + VARIANT self = { VT_I4 }; + self.lVal = CHILDID_SELF; + ret = SUCCEEDED(acc->accSelect(SELFLAG_TAKEFOCUS, self)); + } + return ret; +} + +} // namespace + +SupplyProxyCredentials::SupplyProxyCredentials(const char* username, + const char* password) + : username_(username), password_(password) { +} + +bool SupplyProxyCredentials::OnDialogDetected(HWND hwnd, + const std::string& caption) { + // IE's dialog caption (en-US). + if (caption.compare("Windows Security") != 0) + return false; + + DialogProps props = {0}; + ::EnumChildWindows(hwnd, EnumChildren, reinterpret_cast<LPARAM>(&props)); + DCHECK(::IsWindow(props.username_)); + DCHECK(::IsWindow(props.password_)); + + // We can't use SetWindowText to set the username/password, so simulate + // keyboard input instead. + chrome_frame_test::ForceSetForegroundWindow(hwnd); + CHECK(SetFocusToAccessibleWindow(props.username_)); + chrome_frame_test::SendString(username_.c_str()); + Sleep(100); + + chrome_frame_test::SendVirtualKey(VK_TAB); + Sleep(100); + chrome_frame_test::SendString(password_.c_str()); + + Sleep(100); + chrome_frame_test::SendVirtualKey(VK_RETURN); + + return true; +} + +// static +BOOL SupplyProxyCredentials::EnumChildren(HWND hwnd, LPARAM param) { + if (!::IsWindowVisible(hwnd)) + return TRUE; // Ignore but continue to enumerate. + + DialogProps* props = reinterpret_cast<DialogProps*>(param); + + char class_name[MAX_PATH] = {0}; + ::GetClassNameA(hwnd, class_name, arraysize(class_name)); + if (lstrcmpiA(class_name, "Edit") == 0) { + if (props->username_ == NULL || props->username_ == hwnd) { + props->username_ = hwnd; + } else if (props->password_ == NULL) { + props->password_ = hwnd; + } + } else { + EnumChildWindows(hwnd, EnumChildren, param); + } + + return TRUE; +} + +DialogWatchdog::DialogWatchdog() : hook_(NULL), hook_stub_(NULL) { + Initialize(); +} + +DialogWatchdog::~DialogWatchdog() { + Uninitialize(); +} + +bool DialogWatchdog::Initialize() { + DCHECK(hook_ == NULL); + DCHECK(hook_stub_ == NULL); + hook_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), + WinEventHook); + hook_ = SetWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_SHOW, NULL, + reinterpret_cast<WINEVENTPROC>(hook_stub_->code()), 0, + 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + + return hook_ != NULL; +} + +void DialogWatchdog::Uninitialize() { + if (hook_) { + ::UnhookWinEvent(hook_); + hook_ = NULL; + FunctionStub::Destroy(hook_stub_); + hook_stub_ = NULL; + } +} + +// static +void DialogWatchdog::WinEventHook(DialogWatchdog* me, HWINEVENTHOOK hook, + DWORD event, HWND hwnd, LONG object_id, + LONG child_id, DWORD event_thread_id, + DWORD event_time) { + // Check for a dialog class ("#32770") and notify observers if we find one. + char class_name[MAX_PATH] = {0}; + ::GetClassNameA(hwnd, class_name, arraysize(class_name)); + if (lstrcmpA(class_name, "#32770") == 0) { + int len = ::GetWindowTextLength(hwnd); + std::string text; + ::GetWindowTextA(hwnd, WriteInto(&text, len + 1), len + 1); + me->OnDialogFound(hwnd, text); + } +} + +void DialogWatchdog::OnDialogFound(HWND hwnd, const std::string& caption) { + std::vector<DialogWatchdogObserver*>::iterator it = observers_.begin(); + while (it != observers_.end()) { + if ((*it)->OnDialogDetected(hwnd, caption)) + break; + it++; + } +} diff --git a/chrome_frame/test/net/dialog_watchdog.h b/chrome_frame/test/net/dialog_watchdog.h new file mode 100644 index 0000000..dfb8989 --- /dev/null +++ b/chrome_frame/test/net/dialog_watchdog.h @@ -0,0 +1,64 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_ +#define CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_ + +#include <windows.h> + +#include <string> +#include <vector> + +struct FunctionStub; + +class DialogWatchdogObserver { // NOLINT + public: + // returns true if this observer handled the dialog. + virtual bool OnDialogDetected(HWND hwnd, const std::string& caption) = 0; +}; + +class SupplyProxyCredentials : public DialogWatchdogObserver { + public: + SupplyProxyCredentials(const char* username, const char* password); + + protected: + struct DialogProps { + HWND username_; + HWND password_; + }; + + virtual bool OnDialogDetected(HWND hwnd, const std::string& caption); + static BOOL CALLBACK EnumChildren(HWND hwnd, LPARAM param); + + protected: + std::string username_; + std::string password_; +}; + +class DialogWatchdog { + public: + DialogWatchdog(); + ~DialogWatchdog(); + + inline void AddObserver(DialogWatchdogObserver* observer) { + observers_.push_back(observer); + } + + bool Initialize(); + void Uninitialize(); + + protected: + static void CALLBACK WinEventHook(DialogWatchdog* me, HWINEVENTHOOK hook, + DWORD event, HWND hwnd, LONG object_id, LONG child_id, + DWORD event_thread_id, DWORD event_time); + + void OnDialogFound(HWND hwnd, const std::string& caption); + + protected: + HWINEVENTHOOK hook_; + std::vector<DialogWatchdogObserver*> observers_; + FunctionStub* hook_stub_; +}; + +#endif // CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_ diff --git a/chrome_frame/test/net/fake_external_tab.cc b/chrome_frame/test/net/fake_external_tab.cc new file mode 100644 index 0000000..eebd2d9b --- /dev/null +++ b/chrome_frame/test/net/fake_external_tab.cc @@ -0,0 +1,391 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/net/fake_external_tab.h" + +#include <exdisp.h> + +#include "app/app_paths.h" +#include "app/resource_bundle.h" +#include "app/win_util.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/icu_util.h" +#include "base/path_service.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" + +#include "chrome/browser/browser_prefs.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/process_singleton.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/browser/renderer_host/render_process_host.h" + +#include "chrome_frame/utils.h" +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/net/dialog_watchdog.h" +#include "chrome_frame/test/net/test_automation_resource_message_filter.h" + +namespace { + +// A special command line switch to allow developers to manually launch the +// browser and debug CF inside the browser. +const wchar_t kManualBrowserLaunch[] = L"manual-browser"; + +// Pops up a message box after the test environment has been set up +// and before tearing it down. Useful for when debugging tests and not +// the test environment that's been set up. +const wchar_t kPromptAfterSetup[] = L"prompt-after-setup"; + +const int kTestServerPort = 4666; +// The test HTML we use to initialize Chrome Frame. +// Note that there's a little trick in there to avoid an extra URL request +// that the browser will otherwise make for the site's favicon. +// If we don't do this the browser will create a new URL request after +// the CF page has been initialized and that URL request will confuse the +// global URL instance counter in the unit tests and subsequently trip +// some DCHECKs. +const char kChromeFrameHtml[] = "<html><head>" + "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />" + "<link rel=\"shortcut icon\" href=\"file://c:\\favicon.ico\"/>" + "</head><body>Chrome Frame should now be loaded</body></html>"; + +bool ShouldLaunchBrowser() { + return !CommandLine::ForCurrentProcess()->HasSwitch(kManualBrowserLaunch); +} + +bool PromptAfterSetup() { + return CommandLine::ForCurrentProcess()->HasSwitch(kPromptAfterSetup); +} + +} // end namespace + +FakeExternalTab::FakeExternalTab() { + PathService::Get(chrome::DIR_USER_DATA, &overridden_user_dir_); + user_data_dir_ = FilePath::FromWStringHack(GetProfilePath()); + PathService::Override(chrome::DIR_USER_DATA, user_data_dir_); + process_singleton_.reset(new ProcessSingleton(user_data_dir_)); +} + +FakeExternalTab::~FakeExternalTab() { + if (!overridden_user_dir_.empty()) { + PathService::Override(chrome::DIR_USER_DATA, overridden_user_dir_); + } +} + +std::wstring FakeExternalTab::GetProfileName() { + return L"iexplore"; +} + +std::wstring FakeExternalTab::GetProfilePath() { + std::wstring path; + GetUserProfileBaseDirectory(&path); + file_util::AppendToPath(&path, GetProfileName()); + return path; +} + +void FakeExternalTab::Initialize() { + DCHECK(g_browser_process == NULL); + + // The gears plugin causes the PluginRequestInterceptor to kick in and it + // will cause problems when it tries to intercept URL requests. + PathService::Override(chrome::FILE_GEARS_PLUGIN, FilePath()); + + icu_util::Initialize(); + + chrome::RegisterPathProvider(); + app::RegisterPathProvider(); + + ResourceBundle::InitSharedInstance(L"en-US"); + ResourceBundle::GetSharedInstance().LoadThemeResources(); + + const CommandLine* cmd = CommandLine::ForCurrentProcess(); + browser_process_.reset(new BrowserProcessImpl(*cmd)); + RenderProcessHost::set_run_renderer_in_process(true); + // BrowserProcessImpl's constructor should set g_browser_process. + DCHECK(g_browser_process); + + Profile* profile = g_browser_process->profile_manager()-> + GetDefaultProfile(FilePath(user_data())); + PrefService* prefs = profile->GetPrefs(); + PrefService* local_state = browser_process_->local_state(); + local_state->RegisterStringPref(prefs::kApplicationLocale, L""); + local_state->RegisterBooleanPref(prefs::kMetricsReportingEnabled, false); + + browser::RegisterAllPrefs(prefs, local_state); + + // Override some settings to avoid hitting some preferences that have not + // been registered. + prefs->SetBoolean(prefs::kPasswordManagerEnabled, false); + prefs->SetBoolean(prefs::kAlternateErrorPagesEnabled, false); + prefs->SetBoolean(prefs::kSafeBrowsingEnabled, false); + + profile->InitExtensions(); +} + +void FakeExternalTab::Shutdown() { + browser_process_.reset(); + g_browser_process = NULL; + process_singleton_.reset(); + + ResourceBundle::CleanupSharedInstance(); +} + +CFUrlRequestUnittestRunner::CFUrlRequestUnittestRunner(int argc, char** argv) + : NetTestSuite(argc, argv), + chrome_frame_html_("/chrome_frame", kChromeFrameHtml) { + fake_chrome_.Initialize(); + pss_subclass_.reset(new ProcessSingletonSubclass(this)); + EXPECT_TRUE(pss_subclass_->Subclass(fake_chrome_.user_data())); + StartChromeFrameInHostBrowser(); +} + +CFUrlRequestUnittestRunner::~CFUrlRequestUnittestRunner() { + fake_chrome_.Shutdown(); +} + +DWORD WINAPI NavigateIE(void* param) { + return 0; + win_util::ScopedCOMInitializer com; + BSTR url = reinterpret_cast<BSTR>(param); + + bool found = false; + int retries = 0; + const int kMaxRetries = 20; + while (!found && retries < kMaxRetries) { + ScopedComPtr<IShellWindows> windows; + HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL, + IID_IShellWindows, reinterpret_cast<void**>(windows.Receive())); + DCHECK(SUCCEEDED(hr)) << "CoCreateInstance"; + + if (SUCCEEDED(hr)) { + hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + long count = 0; // NOLINT + windows->get_Count(&count); + VARIANT i = { VT_I4 }; + for (i.lVal = 0; i.lVal < count; ++i.lVal) { + ScopedComPtr<IDispatch> folder; + windows->Item(i, folder.Receive()); + if (folder != NULL) { + ScopedComPtr<IWebBrowser2> browser; + if (SUCCEEDED(browser.QueryFrom(folder))) { + found = true; + browser->Stop(); + Sleep(1000); + VARIANT empty = ScopedVariant::kEmptyVariant; + hr = browser->Navigate(url, &empty, &empty, &empty, &empty); + DCHECK(SUCCEEDED(hr)) << "Failed to navigate"; + break; + } + } + } + } + if (!found) { + DLOG(INFO) << "Waiting for browser to initialize..."; + ::Sleep(100); + retries++; + } + } + + DCHECK(retries < kMaxRetries); + DCHECK(found); + + ::SysFreeString(url); + + return 0; +} + +void CFUrlRequestUnittestRunner::StartChromeFrameInHostBrowser() { + if (!ShouldLaunchBrowser()) + return; + + win_util::ScopedCOMInitializer com; + chrome_frame_test::CloseAllIEWindows(); + + test_http_server_.reset(new test_server::SimpleWebServer(kTestServerPort)); + test_http_server_->AddResponse(&chrome_frame_html_); + std::wstring url(StringPrintf(L"http://localhost:%i/chrome_frame", + kTestServerPort).c_str()); + + // Launch IE. This launches IE correctly on Vista too. + ScopedHandle ie_process(chrome_frame_test::LaunchIE(url)); + EXPECT_TRUE(ie_process.IsValid()); + + // NOTE: If you're running IE8 and CF is not being loaded, you need to + // disable IE8's prebinding until CF properly handles that situation. + // + // HKCU\Software\Microsoft\Internet Explorer\Main + // Value name: EnablePreBinding (REG_DWORD) + // Value: 0 +} + +void CFUrlRequestUnittestRunner::ShutDownHostBrowser() { + if (ShouldLaunchBrowser()) { + win_util::ScopedCOMInitializer com; + chrome_frame_test::CloseAllIEWindows(); + } +} + +// Override virtual void Initialize to not call icu initialize +void CFUrlRequestUnittestRunner::Initialize() { + DCHECK(::GetCurrentThreadId() == test_thread_id_); + + // Start by replicating some of the steps that would otherwise be + // done by TestSuite::Initialize. We can't call the base class + // directly because it will attempt to initialize some things such as + // ICU that have already been initialized for this process. + InitializeLogging(); + base::Time::EnableHiResClockForTests(); + +#if !defined(PURIFY) && defined(OS_WIN) + logging::SetLogAssertHandler(UnitTestAssertHandler); +#endif // !defined(PURIFY) + + // Next, do some initialization for NetTestSuite. + NetTestSuite::InitializeTestThread(); +} + +void CFUrlRequestUnittestRunner::Shutdown() { + DCHECK(::GetCurrentThreadId() == test_thread_id_); + NetTestSuite::Shutdown(); +} + +void CFUrlRequestUnittestRunner::OnConnectAutomationProviderToChannel( + const std::string& channel_id) { + Profile* profile = g_browser_process->profile_manager()-> + GetDefaultProfile(fake_chrome_.user_data()); + + AutomationProviderList* list = + g_browser_process->InitAutomationProviderList(); + DCHECK(list); + list->AddProvider(TestAutomationProvider::NewAutomationProvider(profile, + channel_id, this)); +} + +void CFUrlRequestUnittestRunner::OnInitialTabLoaded() { + test_http_server_.reset(); + StartTests(); +} + +void CFUrlRequestUnittestRunner::RunMainUIThread() { + DCHECK(MessageLoop::current()); + DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); + + // Register the main thread by instantiating it, but don't call any methods. + ChromeThread main_thread; + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + MessageLoop::current()->Run(); +} + +void CFUrlRequestUnittestRunner::StartTests() { + if (PromptAfterSetup()) + MessageBoxA(NULL, "click ok to run", "", MB_OK); + + DCHECK_EQ(test_thread_.IsValid(), false); + test_thread_.Set(::CreateThread(NULL, 0, RunAllUnittests, this, 0, + &test_thread_id_)); + DCHECK(test_thread_.IsValid()); +} + +// static +DWORD CFUrlRequestUnittestRunner::RunAllUnittests(void* param) { + PlatformThread::SetName("CFUrlRequestUnittestRunner"); + CFUrlRequestUnittestRunner* me = + reinterpret_cast<CFUrlRequestUnittestRunner*>(param); + me->Run(); + me->fake_chrome_.ui_loop()->PostTask(FROM_HERE, + NewRunnableFunction(TakeDownBrowser, me)); + return 0; +} + +// static +void CFUrlRequestUnittestRunner::TakeDownBrowser( + CFUrlRequestUnittestRunner* me) { + if (PromptAfterSetup()) + MessageBoxA(NULL, "click ok to exit", "", MB_OK); + + me->ShutDownHostBrowser(); +} + +void CFUrlRequestUnittestRunner::InitializeLogging() { + FilePath exe; + PathService::Get(base::FILE_EXE, &exe); + FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log")); + logging::InitLogging(log_filename.value().c_str(), + logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG, + logging::LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE); + // We want process and thread IDs because we may have multiple processes. + // Note: temporarily enabled timestamps in an effort to catch bug 6361. + logging::SetLogItems(true, true, true, true); +} + +void FilterDisabledTests() { + if (::testing::FLAGS_gtest_filter.GetLength() && + ::testing::FLAGS_gtest_filter.Compare("*") != 0) { + // Don't override user specified filters. + return; + } + + const char* disabled_tests[] = { + // Tests disabled since they're testing the same functionality used + // by the TestAutomationProvider. + "URLRequestTest.InterceptNetworkError",
+ "URLRequestTest.InterceptRestartRequired",
+ "URLRequestTest.InterceptRespectsCancelMain",
+ "URLRequestTest.InterceptRespectsCancelRedirect",
+ "URLRequestTest.InterceptRespectsCancelFinal",
+ "URLRequestTest.InterceptRespectsCancelInRestart", + "URLRequestTest.InterceptRedirect", + "URLRequestTest.InterceptServerError", + "URLRequestTestFTP.*", + + // Tests that are currently not working: + + // Temporarily disabled because they needs user input (login dialog). + "URLRequestTestHTTP.BasicAuth", + "URLRequestTestHTTP.BasicAuthWithCookies", + + // HTTPS tests temporarily disabled due to the certificate error dialog. + // TODO(tommi): The tests currently fail though, so need to fix. + "HTTPSRequestTest.HTTPSMismatchedTest", + "HTTPSRequestTest.HTTPSExpiredTest", + + // Tests chrome's network stack's cache (might not apply to CF). + "URLRequestTestHTTP.VaryHeader", + + // I suspect we can only get this one to work (if at all) on IE8 and + // later by using the new INTERNET_OPTION_SUPPRESS_BEHAVIOR flags + // See http://msdn.microsoft.com/en-us/library/aa385328(VS.85).aspx + "URLRequestTest.DoNotSaveCookies", + }; + + std::string filter("-"); // All following filters will be negative. + for (int i = 0; i < arraysize(disabled_tests); ++i) { + if (i > 0) + filter += ":"; + filter += disabled_tests[i]; + } + + ::testing::FLAGS_gtest_filter = filter; +} + +int main(int argc, char** argv) { + DialogWatchdog watchdog; + // See url_request_unittest.cc for these credentials. + SupplyProxyCredentials credentials("user", "secret"); + watchdog.AddObserver(&credentials); + testing::InitGoogleTest(&argc, argv); + FilterDisabledTests(); + CFUrlRequestUnittestRunner test_suite(argc, argv); + test_suite.RunMainUIThread(); + return 0; +} diff --git a/chrome_frame/test/net/fake_external_tab.h b/chrome_frame/test/net/fake_external_tab.h new file mode 100644 index 0000000..6ce4f93 --- /dev/null +++ b/chrome_frame/test/net/fake_external_tab.h @@ -0,0 +1,106 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_ +#define CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/message_loop.h" + +#include "chrome/app/scoped_ole_initializer.h" +#include "chrome/browser/browser_process_impl.h" + +#include "chrome_frame/test/test_server.h" +#include "chrome_frame/test/net/test_automation_provider.h" +#include "chrome_frame/test/net/process_singleton_subclass.h" + +#include "net/base/net_test_suite.h" + +class ProcessSingleton; + +class FakeExternalTab { + public: + FakeExternalTab(); + ~FakeExternalTab(); + + virtual std::wstring GetProfileName(); + + virtual std::wstring GetProfilePath(); + virtual void Initialize(); + virtual void Shutdown(); + + const FilePath& user_data() const { + return user_data_dir_; + } + + MessageLoopForUI* ui_loop() { + return &loop_; + } + + protected: + MessageLoopForUI loop_; + scoped_ptr<BrowserProcess> browser_process_; + FilePath overridden_user_dir_; + FilePath user_data_dir_; + ScopedOleInitializer ole_initializer_; // For RegisterDropTarget etc to work. + scoped_ptr<ProcessSingleton> process_singleton_; +}; + +// The "master class" that spins the UI and test threads. +class CFUrlRequestUnittestRunner + : public NetTestSuite, + public ProcessSingletonSubclassDelegate, + public TestAutomationProviderDelegate { + public: + CFUrlRequestUnittestRunner(int argc, char** argv); + ~CFUrlRequestUnittestRunner(); + + virtual void StartChromeFrameInHostBrowser(); + + virtual void ShutDownHostBrowser(); + + // Overrides to not call icu initialize + virtual void Initialize(); + virtual void Shutdown(); + + // ProcessSingletonSubclassDelegate. + virtual void OnConnectAutomationProviderToChannel( + const std::string& channel_id); + + // TestAutomationProviderDelegate. + virtual void OnInitialTabLoaded(); + + void RunMainUIThread(); + + void StartTests(); + + protected: + // This is the thread that runs all the UrlRequest tests. + // Within its context, the Initialize() and Shutdown() routines above + // will be called. + static DWORD WINAPI RunAllUnittests(void* param); + + static void TakeDownBrowser(CFUrlRequestUnittestRunner* me); + + protected: + // Borrowed from TestSuite::Initialize(). + void InitializeLogging(); + + protected: + ScopedHandle test_thread_; + DWORD test_thread_id_; + scoped_ptr<MessageLoop> test_thread_message_loop_; + + scoped_ptr<test_server::SimpleWebServer> test_http_server_; + test_server::SimpleResponse chrome_frame_html_; + + // The fake chrome instance. This instance owns the UI message loop + // on the main thread. + FakeExternalTab fake_chrome_; + scoped_ptr<ProcessSingletonSubclass> pss_subclass_; +}; + +#endif // CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_ diff --git a/chrome_frame/test/net/process_singleton_subclass.cc b/chrome_frame/test/net/process_singleton_subclass.cc new file mode 100644 index 0000000..2206a74 --- /dev/null +++ b/chrome_frame/test/net/process_singleton_subclass.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/net/process_singleton_subclass.h" + +#include "base/command_line.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "chrome/browser/browser_process_impl.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome_frame/test/net/test_automation_provider.h" +#include "chrome_frame/function_stub.h" +#include "testing/gtest/include/gtest/gtest.h" + +ProcessSingletonSubclass::ProcessSingletonSubclass( + ProcessSingletonSubclassDelegate* delegate) + : stub_(NULL), delegate_(delegate), original_wndproc_(NULL) { +} + +ProcessSingletonSubclass::~ProcessSingletonSubclass() { + if (stub_) { + stub_->BypassStub(reinterpret_cast<void*>(original_wndproc_)); + } +} + +bool ProcessSingletonSubclass::Subclass(const FilePath& user_data_dir) { + DCHECK(stub_ == NULL); + DCHECK(original_wndproc_ == NULL); + HWND hwnd = FindWindowEx(HWND_MESSAGE, NULL, chrome::kMessageWindowClass, + user_data_dir.ToWStringHack().c_str()); + if (!::IsWindow(hwnd)) + return false; + + // The window must be in this process for us to be able to subclass it. + DWORD pid = 0; + ::GetWindowThreadProcessId(hwnd, &pid); + EXPECT_EQ(pid, ::GetCurrentProcessId()); + + original_wndproc_ = reinterpret_cast<WNDPROC>(::GetWindowLongPtr(hwnd, + GWLP_WNDPROC)); + stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), + &SubclassWndProc); + DCHECK(stub_); + ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(stub_->code())); + return true; +} + +// static +LRESULT ProcessSingletonSubclass::SubclassWndProc(ProcessSingletonSubclass* me, + HWND hwnd, UINT msg, + WPARAM wp, LPARAM lp) { + switch (msg) { + case WM_COPYDATA: + return me->OnCopyData(hwnd, reinterpret_cast<HWND>(wp), + reinterpret_cast<COPYDATASTRUCT*>(lp)); + default: + break; + } + + return me->original_wndproc_(hwnd, msg, wp, lp); +} + +// static +LRESULT ProcessSingletonSubclass::OnCopyData(HWND hwnd, HWND from_hwnd, + const COPYDATASTRUCT* cds) { + // We should have enough room for the shortest command (min_message_size) + // and also be a multiple of wchar_t bytes. The shortest command + // possible is L"START\0\0" (empty current directory and command line). + static const int kMinMessageSize = sizeof(L"START\0"); + EXPECT_TRUE(kMinMessageSize <= cds->cbData); + + if (kMinMessageSize > cds->cbData) + return TRUE; + + // We split the string into 4 parts on NULLs. + const wchar_t* begin = reinterpret_cast<const wchar_t*>(cds->lpData); + const wchar_t* end = begin + (cds->cbData / sizeof(wchar_t)); + const wchar_t kNull = L'\0'; + const wchar_t* eos = wmemchr(begin, kNull, end - begin); + EXPECT_NE(eos, end); + if (lstrcmpW(begin, L"START") == 0) { + begin = eos + 1; + EXPECT_TRUE(begin <= end); + eos = wmemchr(begin, kNull, end - begin); + EXPECT_NE(eos, end); + + // Get current directory. + const wchar_t* cur_dir = begin; + begin = eos + 1; + EXPECT_TRUE(begin <= end); + eos = wmemchr(begin, kNull, end - begin); + // eos might be equal to end at this point. + + // Get command line. + std::wstring cmd_line(begin, static_cast<size_t>(end - begin)); + + CommandLine parsed_command_line(L""); + parsed_command_line.ParseFromString(cmd_line); + std::string channel_id(WideToASCII(parsed_command_line.GetSwitchValue( + switches::kAutomationClientChannelID))); + EXPECT_FALSE(channel_id.empty()); + + delegate_->OnConnectAutomationProviderToChannel(channel_id); + } + return TRUE; +} diff --git a/chrome_frame/test/net/process_singleton_subclass.h b/chrome_frame/test/net/process_singleton_subclass.h new file mode 100644 index 0000000..dd9ce36 --- /dev/null +++ b/chrome_frame/test/net/process_singleton_subclass.h @@ -0,0 +1,36 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_ +#define CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_ + +#include <windows.h> +#include "base/file_path.h" + +struct FunctionStub; + +class ProcessSingletonSubclassDelegate { + public: + virtual void OnConnectAutomationProviderToChannel( + const std::string& channel_id) = 0; +}; + +class ProcessSingletonSubclass { + public: + ProcessSingletonSubclass(ProcessSingletonSubclassDelegate* delegate); + ~ProcessSingletonSubclass(); + + bool Subclass(const FilePath& user_data_dir); + + protected: + static LRESULT CALLBACK SubclassWndProc(ProcessSingletonSubclass* me, + HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); + LRESULT OnCopyData(HWND hwnd, HWND from_hwnd, const COPYDATASTRUCT* cds); + protected: + FunctionStub* stub_; + ProcessSingletonSubclassDelegate* delegate_; + WNDPROC original_wndproc_; +}; + +#endif // CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_ + diff --git a/chrome_frame/test/net/test_automation_provider.cc b/chrome_frame/test/net/test_automation_provider.cc new file mode 100644 index 0000000..3a56aa4 --- /dev/null +++ b/chrome_frame/test/net/test_automation_provider.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/net/test_automation_provider.h" + +#include "base/command_line.h" +#include "chrome/test/automation/automation_messages.h" + +#include "chrome_frame/test/net/test_automation_resource_message_filter.h" + +namespace { + +// A special command line switch to just run the unit tests without CF in +// the picture. Can be useful when the harness itself needs to be debugged. +const wchar_t kNoCfTestRun[] = L"no-cf-test-run"; + +bool CFTestsDisabled() { + static bool switch_present = CommandLine::ForCurrentProcess()-> + HasSwitch(kNoCfTestRun); + return switch_present; +} + +} // end namespace + +TestAutomationProvider::TestAutomationProvider( + Profile* profile, + TestAutomationProviderDelegate* delegate) + : AutomationProvider(profile), tab_handle_(-1), delegate_(delegate) { + filter_ = new TestAutomationResourceMessageFilter(this); + URLRequest::RegisterRequestInterceptor(this); +} + +TestAutomationProvider::~TestAutomationProvider() { + URLRequest::UnregisterRequestInterceptor(this); +} + +void TestAutomationProvider::OnMessageReceived(const IPC::Message& msg) { + if (filter_->OnMessageReceived(msg)) + return; // Message handled by the filter. + + __super::OnMessageReceived(msg); +} + +// IPC override to grab the tab handle. +bool TestAutomationProvider::Send(IPC::Message* msg) { + if (msg->type() == AutomationMsg_TabLoaded::ID) { + DCHECK(tab_handle_ == -1) << "Currently only support one tab"; + void* iter = NULL; + CHECK(msg->ReadInt(&iter, &tab_handle_)); + DLOG(INFO) << "Got tab handle: " << tab_handle_; + DCHECK(tab_handle_ != -1 && tab_handle_ != 0); + delegate_->OnInitialTabLoaded(); + } + + return AutomationProvider::Send(msg); +} + +URLRequestJob* TestAutomationProvider::MaybeIntercept(URLRequest* request) { + if (CFTestsDisabled()) + return NULL; + + if (request->url().SchemeIs("http") || request->url().SchemeIs("https")) { + // Only look at requests that don't have any user data. + // ResourceDispatcherHost uses the user data for requests that it manages. + // We don't want to mess with those. + + // We could also check if the current thread is our TestUrlRequest thread + // and only intercept requests that belong to that thread. + if (request->GetUserData(NULL) == NULL) { + DCHECK(tab_handle_ != -1); + URLRequestAutomationJob* job = new URLRequestAutomationJob(request, + tab_handle_, filter_); + return job; + } + } + + return NULL; +} + +// static +TestAutomationProvider* TestAutomationProvider::NewAutomationProvider( + Profile* p, const std::string& channel, + TestAutomationProviderDelegate* delegate) { + TestAutomationProvider* automation = new TestAutomationProvider(p, delegate); + automation->ConnectToChannel(channel); + automation->SetExpectedTabCount(1); + return automation; +} diff --git a/chrome_frame/test/net/test_automation_provider.h b/chrome_frame/test/net/test_automation_provider.h new file mode 100644 index 0000000..75830ba --- /dev/null +++ b/chrome_frame/test/net/test_automation_provider.h @@ -0,0 +1,52 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_ +#define CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_ + +#include "chrome/browser/automation/automation_provider.h" + +class TestAutomationResourceMessageFilter; + +// Callback interface for TestAutomationProvider. +class TestAutomationProviderDelegate { + public: + virtual void OnInitialTabLoaded() = 0; +}; + +// A slightly customized version of AutomationProvider. +// We override AutomationProvider to be able to filter received messages +// (see TestAutomationResourceMessageFilter) and know when the initial +// ExternalTab has been loaded. +// In order to intercept UrlRequests and make the URLRequestAutomationJob class +// handle requests from unit tests, we also implement URLRequest::Interceptor. +class TestAutomationProvider + : public AutomationProvider, + public URLRequest::Interceptor { + public: + explicit TestAutomationProvider(Profile* profile, + TestAutomationProviderDelegate* delegate); + + virtual ~TestAutomationProvider(); + + // AutomationProvider overrides. + virtual void OnMessageReceived(const IPC::Message& msg); + virtual bool Send(IPC::Message* msg); + + // URLRequest::Interceptor. + virtual URLRequestJob* MaybeIntercept(URLRequest* request); + + // Call to instantiate and initialize a new instance of + // TestAutomationProvider. + static TestAutomationProvider* NewAutomationProvider( + Profile* p, + const std::string& channel, + TestAutomationProviderDelegate* delegate); + + protected: + scoped_refptr<TestAutomationResourceMessageFilter> filter_; + int tab_handle_; + TestAutomationProviderDelegate* delegate_; +}; + +#endif CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_ diff --git a/chrome_frame/test/net/test_automation_resource_message_filter.cc b/chrome_frame/test/net/test_automation_resource_message_filter.cc new file mode 100644 index 0000000..32ef532 --- /dev/null +++ b/chrome_frame/test/net/test_automation_resource_message_filter.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/net/test_automation_resource_message_filter.h" + +TestAutomationResourceMessageFilter::TestAutomationResourceMessageFilter( + AutomationProvider* automation) : automation_(automation) { +} + +bool TestAutomationResourceMessageFilter::Send(IPC::Message* message) { + return automation_->Send(message); +} + +// static +void TestAutomationResourceMessageFilter::OnRequestMessage( + URLRequestAutomationJob* job, IPC::Message* msg) { + job->OnMessage(*msg); + delete msg; +} + +bool TestAutomationResourceMessageFilter::OnMessageReceived( + const IPC::Message& message) { + // See if we want to process this message... call the base class + // for filter messages, send the message to the correct thread + // for URL requests. + bool handled = false; + int request_id = URLRequestAutomationJob::MayFilterMessage(message); + if (request_id) { + RequestMap::iterator it = requests_.find(request_id); + if (it != requests_.end()) { + handled = true; + IPC::Message* msg = new IPC::Message(message); + RequestJob& job = it->second; + job.loop_->PostTask(FROM_HERE, NewRunnableFunction(OnRequestMessage, + job.job_, msg)); + } + } else { + handled = AutomationResourceMessageFilter::OnMessageReceived(message); + } + return handled; +} + +// Add request to the list of outstanding requests. +bool TestAutomationResourceMessageFilter::RegisterRequest( + URLRequestAutomationJob* job) { + // Store the request in an internal map like the parent class + // does, but also store the current loop pointer so we can send + // request messages to that loop. + DCHECK(requests_.end() == requests_.find(job->id())); + RequestJob request_job = { MessageLoop::current(), job }; + requests_[job->id()] = request_job; + return true; +} + +// Remove request from the list of outstanding requests. +void TestAutomationResourceMessageFilter::UnRegisterRequest( + URLRequestAutomationJob* job) { + requests_.erase(job->id()); +} diff --git a/chrome_frame/test/net/test_automation_resource_message_filter.h b/chrome_frame/test/net/test_automation_resource_message_filter.h new file mode 100644 index 0000000..310307a --- /dev/null +++ b/chrome_frame/test/net/test_automation_resource_message_filter.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ +#define CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ + +#include "chrome/browser/automation/automation_provider.h" +#include "chrome/browser/automation/url_request_automation_job.h" + +// Performs the same duties as AutomationResourceMessageFilter but with one +// difference. Instead of being tied to an IPC channel running on Chrome's +// IO thread, this instance runs on the unit test's IO thread (all URL request +// tests have their own IO loop) and is tied to an instance of +// AutomationProvider (TestAutomationProvider to be exact). +// +// Messages from the AutomationProvider that are destined to request objects +// owned by this class are marshaled over to the request's IO thread instead +// of using the thread the messages are received on. This way we allow the +// URL request tests to run sequentially as they were written while still +// allowing the automation layer to work as it normally does (i.e. process +// its messages on the receiving thread). +class TestAutomationResourceMessageFilter + : public AutomationResourceMessageFilter { + public: + TestAutomationResourceMessageFilter(AutomationProvider* automation); + + virtual bool Send(IPC::Message* message); + + static void OnRequestMessage(URLRequestAutomationJob* job, + IPC::Message* msg); + + virtual bool OnMessageReceived(const IPC::Message& message); + + // Add request to the list of outstanding requests. + virtual bool RegisterRequest(URLRequestAutomationJob* job); + // Remove request from the list of outstanding requests. + virtual void UnRegisterRequest(URLRequestAutomationJob* job); + + protected: + AutomationProvider* automation_; + // declare the request map. + struct RequestJob { + MessageLoop* loop_; + scoped_refptr<URLRequestAutomationJob> job_; + }; + typedef std::map<int, RequestJob> RequestMap; + RequestMap requests_; +}; + +#endif // CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ diff --git a/chrome_frame/test/perf/chrome_frame_perftest.cc b/chrome_frame/test/perf/chrome_frame_perftest.cc new file mode 100644 index 0000000..79bbd7b --- /dev/null +++ b/chrome_frame/test/perf/chrome_frame_perftest.cc @@ -0,0 +1,1137 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "chrome_frame/test/perf/chrome_frame_perftest.h" + +#include <atlwin.h> +#include <atlhost.h> +#include <map> +#include <vector> +#include <string> + +#include "chrome_tab.h" // Generated from chrome_tab.idl. + +#include "base/file_util.h" +#include "base/registry.h" +#include "base/scoped_ptr.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" +#include "base/time.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/chrome_process_util.h" +#include "chrome/test/perf/mem_usage.h" +#include "chrome/test/ui/ui_test.h" + +#include "chrome_frame/test_utils.h" +#include "chrome_frame/utils.h" + +const wchar_t kSilverlightControlKey[] = + L"CLSID\\{DFEAF541-F3E1-4c24-ACAC-99C30715084A}\\InprocServer32"; + +const wchar_t kFlashControlKey[] = + L"CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32"; + +using base::TimeDelta; +using base::TimeTicks; + +// Callback description for onload, onloaderror, onmessage +static _ATL_FUNC_INFO g_single_param = {CC_STDCALL, VT_EMPTY, 1, {VT_VARIANT}}; +// Simple class that forwards the callbacks. +template <typename T> +class DispCallback + : public IDispEventSimpleImpl<1, DispCallback<T>, &IID_IDispatch> { + public: + typedef HRESULT (T::*Method)(VARIANT* param); + + DispCallback(T* owner, Method method) : owner_(owner), method_(method) { + } + + BEGIN_SINK_MAP(DispCallback) + SINK_ENTRY_INFO(1, IID_IDispatch, DISPID_VALUE, OnCallback, &g_single_param) + END_SINK_MAP() + + virtual ULONG STDMETHODCALLTYPE AddRef() { + return owner_->AddRef(); + } + virtual ULONG STDMETHODCALLTYPE Release() { + return owner_->Release(); + } + + STDMETHOD(OnCallback)(VARIANT param) { + return (owner_->*method_)(¶m); + } + + IDispatch* ToDispatch() { + return reinterpret_cast<IDispatch*>(this); + } + + T* owner_; + Method method_; +}; + +// This class implements an ActiveX container which hosts the ChromeFrame +// ActiveX control. It provides hooks which can be implemented by derived +// classes for implementing performance measurement, etc. +class ChromeFrameActiveXContainer + : public CWindowImpl<ChromeFrameActiveXContainer, CWindow, CWinTraits < + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >, + public CComObjectRootEx<CComSingleThreadModel>, + public IPropertyNotifySink { + public: + ~ChromeFrameActiveXContainer() { + if (m_hWnd) + DestroyWindow(); + } + + DECLARE_WND_CLASS_EX(L"ChromeFrameActiveX_container", 0, 0) + + BEGIN_COM_MAP(ChromeFrameActiveXContainer) + COM_INTERFACE_ENTRY(IPropertyNotifySink) + END_COM_MAP() + + BEGIN_MSG_MAP(ChromeFrameActiveXContainer) + MESSAGE_HANDLER(WM_CREATE, OnCreate) + MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + END_MSG_MAP() + + HRESULT OnMessageCallback(VARIANT* param) { + DLOG(INFO) << __FUNCTION__; + OnMessageCallbackImpl(param); + return S_OK; + } + + HRESULT OnLoadErrorCallback(VARIANT* param) { + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + OnLoadErrorCallbackImpl(param); + return S_OK; + } + + HRESULT OnLoadCallback(VARIANT* param) { + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + OnLoadCallbackImpl(param); + return S_OK; + } + + ChromeFrameActiveXContainer() : + prop_notify_cookie_(0), + onmsg_(this, &ChromeFrameActiveXContainer::OnMessageCallback), + onloaderror_(this, &ChromeFrameActiveXContainer::OnLoadErrorCallback), + onload_(this, &ChromeFrameActiveXContainer::OnLoadCallback) { + } + + LRESULT OnCreate(UINT , WPARAM , LPARAM , BOOL& ) { + chromeview_.Attach(m_hWnd); + return 0; + } + + // This will be called twice. + // Once from CAxHostWindow::OnDestroy (through DefWindowProc) + // and once more from the ATL since CAxHostWindow::OnDestroy claims the + // message is not handled. + LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) { // NOLINT + if (prop_notify_cookie_) { + AtlUnadvise(tab_, IID_IPropertyNotifySink, prop_notify_cookie_); + prop_notify_cookie_ = 0; + } + + tab_.Release(); + return 0; + } + + virtual void OnFinalMessage(HWND /*hWnd*/) { + ::PostQuitMessage(6); + } + + static const wchar_t* GetWndCaption() { + return L"ChromeFrame Container"; + } + + // IPropertyNotifySink + STDMETHOD(OnRequestEdit)(DISPID disp_id) { + OnRequestEditImpl(disp_id); + return S_OK; + } + + STDMETHOD(OnChanged)(DISPID disp_id) { + if (disp_id != DISPID_READYSTATE) + return S_OK; + + long ready_state; + HRESULT hr = tab_->get_readyState(&ready_state); + DCHECK(hr == S_OK); + + OnReadyStateChanged(ready_state); + + if (ready_state == READYSTATE_COMPLETE) { + if(!starting_url_.empty()) { + Navigate(starting_url_.c_str()); + } else { + PostMessage(WM_CLOSE); + } + } else if (ready_state == READYSTATE_UNINITIALIZED) { + DLOG(ERROR) << __FUNCTION__ << " Chrome launch failed."; + } + + return S_OK; + } + + void CreateChromeFrameWindow(const std::string& starting_url) { + starting_url_ = starting_url; + RECT rc = { 0, 0, 800, 600 }; + Create(NULL, rc); + DCHECK(m_hWnd); + ShowWindow(SW_SHOWDEFAULT); + } + + void CreateControl(bool setup_event_sinks) { + HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame"); + EXPECT_HRESULT_SUCCEEDED(hr); + hr = chromeview_.QueryControl(tab_.Receive()); + EXPECT_HRESULT_SUCCEEDED(hr); + + if (setup_event_sinks) + SetupEventSinks(); + } + + void Navigate(const char* url) { + BeforeNavigateImpl(url); + + HRESULT hr = tab_->put_src(ScopedBstr(UTF8ToWide(url).c_str())); + DCHECK(hr == S_OK) << "Chrome frame NavigateToURL(" << url + << StringPrintf(L") failed 0x%08X", hr); + } + + void SetupEventSinks() { + HRESULT hr = AtlAdvise(tab_, this, IID_IPropertyNotifySink, + &prop_notify_cookie_); + DCHECK(hr == S_OK) << "AtlAdvice for IPropertyNotifySink failed " << hr; + + CComVariant onmessage(onmsg_.ToDispatch()); + CComVariant onloaderror(onloaderror_.ToDispatch()); + CComVariant onload(onload_.ToDispatch()); + EXPECT_HRESULT_SUCCEEDED(tab_->put_onmessage(onmessage)); + EXPECT_HRESULT_SUCCEEDED(tab_->put_onloaderror(onloaderror)); + EXPECT_HRESULT_SUCCEEDED(tab_->put_onload(onload)); + } + + protected: + // These functions are implemented by derived classes for special behavior + // like performance measurement, etc. + virtual void OnReadyStateChanged(long ready_state) {} + virtual void OnRequestEditImpl(DISPID disp_id) {} + + virtual void OnMessageCallbackImpl(VARIANT* param) {} + + virtual void OnLoadCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + } + + virtual void OnLoadErrorCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + } + virtual void BeforeNavigateImpl(const char* url) {} + + CAxWindow chromeview_; + ScopedComPtr<IChromeFrame> tab_; + DWORD prop_notify_cookie_; + DispCallback<ChromeFrameActiveXContainer> onmsg_; + DispCallback<ChromeFrameActiveXContainer> onloaderror_; + DispCallback<ChromeFrameActiveXContainer> onload_; + std::string starting_url_; +}; + +// This class overrides the hooks provided by the ChromeFrameActiveXContainer +// class and measures performance at various stages, like initialzation of +// the Chrome frame widget, navigation, etc. +class ChromeFrameActiveXContainerPerf : public ChromeFrameActiveXContainer { + public: + ChromeFrameActiveXContainerPerf() {} + + void CreateControl(bool setup_event_sinks) { + perf_initialize_.reset(new PerfTimeLogger("Fully initialized")); + PerfTimeLogger perf_create("Create Control"); + + HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame"); + EXPECT_HRESULT_SUCCEEDED(hr); + hr = chromeview_.QueryControl(tab_.Receive()); + EXPECT_HRESULT_SUCCEEDED(hr); + + perf_create.Done(); + if (setup_event_sinks) + SetupEventSinks(); + } + + protected: + virtual void OnReadyStateChanged(long ready_state) { + // READYSTATE_COMPLETE is fired when the automation server is ready. + if (ready_state == READYSTATE_COMPLETE) { + perf_initialize_->Done(); + } else if (ready_state == READYSTATE_INTERACTIVE) { + // Window ready. Currently we never receive this notification because it + // is fired before we finish setting up our hosting environment. + // This is because of how ATL is written. Moving forward we might + // have our own hosting classes and then have more control over when we + // set up the prop notify sink. + } else { + DCHECK(ready_state != READYSTATE_UNINITIALIZED) << "failed to initialize"; + } + } + + virtual void OnLoadCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + perf_navigate_->Done(); + } + + virtual void OnLoadErrorCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + perf_navigate_->Done(); + } + + virtual void BeforeNavigateImpl(const char* url ) { + std::string test_name = "Navigate "; + test_name += url; + perf_navigate_.reset(new PerfTimeLogger(test_name.c_str())); + } + + scoped_ptr<PerfTimeLogger> perf_initialize_; + scoped_ptr<PerfTimeLogger> perf_navigate_; +}; + +// This class provides common functionality which can be used for most of the +// ChromeFrame/Tab performance tests. +class ChromeFramePerfTestBase : public UITest { + public: + ChromeFramePerfTestBase() {} + protected: + scoped_ptr<ScopedChromeFrameRegistrar> chrome_frame_registrar_; +}; + +class ChromeFrameStartupTest : public ChromeFramePerfTestBase { + public: + ChromeFrameStartupTest() {} + + virtual void SetUp() { + ASSERT_TRUE(PathService::Get(chrome::DIR_APP, &dir_app_)); + + chrome_dll_ = dir_app_.Append(FILE_PATH_LITERAL("chrome.dll")); + chrome_exe_ = dir_app_.Append( + FilePath::FromWStringHack(chrome::kBrowserProcessExecutableName)); + chrome_frame_dll_ = dir_app_.Append( + FILE_PATH_LITERAL("servers\\npchrome_tab.dll")); + } + virtual void TearDown() {} + + // TODO(iyengar) + // This function is similar to the RunStartupTest function used in chrome + // startup tests. Refactor into a common implementation. + void RunStartupTest(const char* graph, const char* trace, + const char* startup_url, bool test_cold, + int total_binaries, const FilePath binaries_to_evict[], + bool important, bool ignore_cache_error) { + const int kNumCycles = 20; + + startup_url_ = startup_url; + + TimeDelta timings[kNumCycles]; + + for (int i = 0; i < kNumCycles; ++i) { + if (test_cold) { + for (int binary_index = 0; binary_index < total_binaries; + binary_index++) { + bool result = EvictFileFromSystemCacheWrapper( + binaries_to_evict[binary_index]); + if (!ignore_cache_error) { + ASSERT_TRUE(result); + } else if (!result) { + printf("\nFailed to evict file %ls from cache. Not running test\n", + binaries_to_evict[binary_index].value().c_str()); + return; + } + } + } + + TimeTicks start_time, end_time; + + RunStartupTestImpl(&start_time, &end_time); + + timings[i] = end_time - start_time; + + CoFreeUnusedLibraries(); + ASSERT_TRUE(GetModuleHandle(L"npchrome_tab.dll") == NULL); + + // TODO(beng): Can't shut down so quickly. Figure out why, and fix. If we + // do, we crash. + PlatformThread::Sleep(50); + } + + std::string times; + for (int i = 0; i < kNumCycles; ++i) + StringAppendF(×, "%.2f,", timings[i].InMillisecondsF()); + + PrintResultList(graph, "", trace, times, "ms", important); + } + + FilePath dir_app_; + FilePath chrome_dll_; + FilePath chrome_exe_; + FilePath chrome_frame_dll_; + + protected: + // Individual startup tests should implement this function. + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) {} + + // The host is torn down by this function. It should not be used after + // this function returns. + static void ReleaseHostComReferences(CAxWindow& host) { + CComPtr<IAxWinHostWindow> spWinHost; + host.QueryHost(&spWinHost); + ASSERT_TRUE(spWinHost != NULL); + + // Hack to get the host to release all interfaces and thus ensure that + // the COM server can be unloaded. + CAxHostWindow* host_window = static_cast<CAxHostWindow*>(spWinHost.p); + host_window->ReleaseAll(); + host.DestroyWindow(); + } + + std::string startup_url_; +}; + +class ChromeFrameStartupTestActiveX : public ChromeFrameStartupTest { + public: + virtual void SetUp() { + // Register the Chrome Frame DLL in the build directory. + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + + ChromeFrameStartupTest::SetUp(); + } + + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + *start_time = TimeTicks::Now(); + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainer> wnd; + wnd.CreateChromeFrameWindow(startup_url_); + wnd.CreateControl(true); + module.RunMessageLoop(); + *end_time = TimeTicks::Now(); + } +}; + +// This class measures the load time of chrome and chrome frame binaries +class ChromeFrameBinariesLoadTest : public ChromeFrameStartupTestActiveX { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + *start_time = TimeTicks::Now(); + + HMODULE chrome_exe = LoadLibrary(chrome_exe_.ToWStringHack().c_str()); + ASSERT_TRUE(chrome_exe != NULL); + + HMODULE chrome_dll = LoadLibrary(chrome_dll_.ToWStringHack().c_str()); + ASSERT_TRUE(chrome_dll != NULL); + + HMODULE chrome_tab_dll = + LoadLibrary(chrome_frame_dll_.ToWStringHack().c_str()); + ASSERT_TRUE(chrome_tab_dll != NULL); + + *end_time = TimeTicks::Now(); + + FreeLibrary(chrome_exe); + FreeLibrary(chrome_dll); + FreeLibrary(chrome_tab_dll); + } +}; + +// This class provides functionality to run the startup performance test for +// the ChromeFrame ActiveX against a reference build. At this point we only run +// this test in warm mode. +class ChromeFrameStartupTestActiveXReference + : public ChromeFrameStartupTestActiveX { + public: + // override the browser directory to use the reference build instead. + virtual void SetUp() { + // Register the reference build Chrome Frame DLL. + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + chrome_frame_registrar_->RegisterReferenceChromeFrameBuild(); + + ChromeFrameStartupTest::SetUp(); + chrome_frame_dll_ = FilePath::FromWStringHack( + chrome_frame_registrar_->GetChromeFrameDllPath()); + } + + virtual void TearDown() { + // Reregister the Chrome Frame DLL in the build directory. + chrome_frame_registrar_.reset(NULL); + } +}; + +// This class provides base functionality to measure ChromeFrame memory +// usage. +// TODO(iyengar) +// Some of the functionality in this class like printing the results, etc +// is based on the chrome\test\memory_test.cc. We need to factor out +// the common code. +class ChromeFrameMemoryTest : public ChromeFramePerfTestBase { + + // Contains information about the memory consumption of a process. + class ProcessMemoryInfo { + public: + // Default constructor + // Added to enable us to add ProcessMemoryInfo instances to a map. + ProcessMemoryInfo() + : process_id_(0), + peak_virtual_size_(0), + virtual_size_(0), + peak_working_set_size_(0), + working_set_size_(0), + chrome_browser_process_(false), + chrome_frame_memory_test_instance_(NULL) {} + + ProcessMemoryInfo(base::ProcessId process_id, bool chrome_browser_process, + ChromeFrameMemoryTest* memory_test_instance) + : process_id_(process_id), + peak_virtual_size_(0), + virtual_size_(0), + peak_working_set_size_(0), + working_set_size_(0), + chrome_browser_process_(chrome_browser_process), + chrome_frame_memory_test_instance_(memory_test_instance) {} + + bool GetMemoryConsumptionDetails() { + return GetMemoryInfo(process_id_, + &peak_virtual_size_, + &virtual_size_, + &peak_working_set_size_, + &working_set_size_); + } + + void Print(const char* test_name) { + std::string trace_name(test_name); + + ASSERT_TRUE(chrome_frame_memory_test_instance_ != NULL); + + if (chrome_browser_process_) { + chrome_frame_memory_test_instance_->PrintResult( + "vm_final_browser", "", trace_name + "_vm_b", + virtual_size_ / 1024, "KB", false /* not important */); + chrome_frame_memory_test_instance_->PrintResult( + "ws_final_browser", "", trace_name + "_ws_b", + working_set_size_ / 1024, "KB", false /* not important */); + } else if (process_id_ == GetCurrentProcessId()) { + chrome_frame_memory_test_instance_->PrintResult( + "vm_current_process", "", trace_name + "_vm_c", + virtual_size_ / 1024, "KB", false /* not important */); + chrome_frame_memory_test_instance_->PrintResult( + "ws_current_process", "", trace_name + "_ws_c", + working_set_size_ / 1024, "KB", false /* not important */); + } + + printf("\n"); + } + + int process_id_; + size_t peak_virtual_size_; + size_t virtual_size_; + size_t peak_working_set_size_; + size_t working_set_size_; + // Set to true if this is the chrome browser process. + bool chrome_browser_process_; + + // A reference to the ChromeFrameMemoryTest instance. Used to print memory + // consumption information. + ChromeFrameMemoryTest* chrome_frame_memory_test_instance_; + }; + + // This map tracks memory usage for a process. It is keyed on the process + // id. + typedef std::map<DWORD, ProcessMemoryInfo> ProcessMemoryConsumptionMap; + + public: + ChromeFrameMemoryTest() + : current_url_index_(0), + browser_pid_(0) {} + + virtual void SetUp() { + // Register the Chrome Frame DLL in the build directory. + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + } + + void RunTest(const char* test_name, char* urls[], int total_urls) { + ASSERT_TRUE(urls != NULL); + ASSERT_GT(total_urls, 0); + + // Record the initial CommitCharge. This is a system-wide measurement, + // so if other applications are running, they can create variance in this + // test. + start_commit_charge_ = GetSystemCommitCharge(); + + for (int i = 0; i < total_urls; i++) + urls_.push_back(urls[i]); + + std::string url; + GetNextUrl(&url); + ASSERT_TRUE(!url.empty()); + + StartTest(url, test_name); + } + + void OnNavigationSuccess(VARIANT* param) { + ASSERT_TRUE(param != NULL); + ASSERT_EQ(VT_BSTR, param->vt); + + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + InitiateNextNavigation(); + } + + void OnNavigationFailure(VARIANT* param) { + ASSERT_TRUE(param != NULL); + ASSERT_EQ(VT_BSTR, param->vt); + + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + InitiateNextNavigation(); + } + + protected: + bool GetNextUrl(std::string* url) { + if (current_url_index_ >= urls_.size()) + return false; + + *url = urls_[current_url_index_++]; + return true; + } + + // Returns the path of the current chrome.exe being used by this test. + // This could return the regular chrome path or that of the reference + // build. + std::wstring GetChromeExePath() { + std::wstring chrome_exe_path = + chrome_frame_registrar_->GetChromeFrameDllPath(); + EXPECT_FALSE(chrome_exe_path.empty()); + + file_util::UpOneDirectory(&chrome_exe_path); + + std::wstring chrome_exe_test_path = chrome_exe_path; + file_util::AppendToPath(&chrome_exe_test_path, + chrome::kBrowserProcessExecutableName); + + if (!file_util::PathExists(chrome_exe_test_path)) { + file_util::UpOneDirectory(&chrome_exe_path); + + chrome_exe_test_path = chrome_exe_path; + file_util::AppendToPath(&chrome_exe_test_path, + chrome::kBrowserProcessExecutableName); + } + + EXPECT_TRUE(file_util::PathExists(chrome_exe_test_path)); + + return chrome_exe_path; + } + + void InitiateNextNavigation() { + if (browser_pid_ == 0) { + std::wstring profile_directory; + if (GetUserProfileBaseDirectory(&profile_directory)) { + file_util::AppendToPath(&profile_directory, + GetHostProcessName(false)); + } + + user_data_dir_ = FilePath::FromWStringHack(profile_directory); + browser_pid_ = ChromeBrowserProcessId(user_data_dir_); + } + + EXPECT_TRUE(static_cast<int>(browser_pid_) > 0); + + // Get the memory consumption information for the child processes + // of the chrome browser. + ChromeProcessList child_processes = GetBrowserChildren(); + ChromeProcessList::iterator index; + for (index = child_processes.begin(); index != child_processes.end(); + ++index) { + AccountProcessMemoryUsage(*index); + } + + // TODO(iyengar): Bug 2953 + // Need to verify if this is still true. + // The automation crashes periodically if we cycle too quickly. + // To make these tests more reliable, slowing them down a bit. + Sleep(200); + + std::string url; + bool next_url = GetNextUrl(&url); + if (!url.empty()) { + NavigateImpl(url); + } else { + TestCompleted(); + } + } + + void PrintResults(const char* test_name) { + PrintMemoryUsageInfo(test_name); + memory_consumption_map_.clear(); + + // Added to give the OS some time to flush the used pages for the + // chrome processes which would have exited by now. + Sleep(200); + + size_t end_commit_charge = GetSystemCommitCharge(); + size_t commit_size = end_commit_charge - start_commit_charge_; + + std::string trace_name(test_name); + trace_name.append("_cc"); + + PrintResult("commit_charge", "", trace_name, + commit_size / 1024, "KB", true /* important */); + printf("\n"); + } + + ChromeProcessList GetBrowserChildren() { + ChromeProcessList list = GetRunningChromeProcesses(user_data_dir_); + ChromeProcessList::iterator browser = + std::find(list.begin(), list.end(), browser_pid_); + if (browser != list.end()) { + list.erase(browser); + } + return list; + } + + void AccountProcessMemoryUsage(DWORD process_id) { + ProcessMemoryInfo process_memory_info(process_id, + process_id == browser_pid_, this); + + ASSERT_TRUE(process_memory_info.GetMemoryConsumptionDetails()); + + memory_consumption_map_[process_id] = process_memory_info; + } + + void PrintMemoryUsageInfo(const char* test_name) { + printf("\n"); + + std::string trace_name(test_name); + + ProcessMemoryConsumptionMap::iterator index; + size_t total_virtual_size = 0; + size_t total_working_set_size = 0; + + for (index = memory_consumption_map_.begin(); + index != memory_consumption_map_.end(); + ++index) { + ProcessMemoryInfo& memory_info = (*index).second; + memory_info.Print(test_name); + + total_virtual_size += memory_info.virtual_size_; + total_working_set_size += memory_info.working_set_size_; + } + + printf("\n"); + + PrintResult("vm_final_total", "", trace_name + "_vm", + total_virtual_size / 1024, "KB", + false /* not important */); + PrintResult("ws_final_total", "", trace_name + "_ws", + total_working_set_size / 1024, "KB", + true /* important */); + } + + // Should never get called. + virtual void StartTest(const std::string& url, + const std::string& test_name) = 0 { + ASSERT_FALSE(false); + } + + // Should never get called. + virtual void NavigateImpl(const std::string& url) = 0 { + ASSERT_FALSE(false); + } + + virtual void TestCompleted() = 0 { + ASSERT_FALSE(false); + } + + // Holds the commit charge at the start of the memory test run. + size_t start_commit_charge_; + + // The index of the URL being tested. + size_t current_url_index_; + + // The chrome browser pid. + base::ProcessId browser_pid_; + + // Contains the list of urls against which the tests are run. + std::vector<std::string> urls_; + + ProcessMemoryConsumptionMap memory_consumption_map_; +}; + +// This class provides functionality to run the memory test against a reference +// chrome frame build. +class ChromeFrameMemoryTestReference : public ChromeFrameMemoryTest { + public: + virtual void SetUp() { + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + chrome_frame_registrar_->RegisterReferenceChromeFrameBuild(); + } + + virtual void TearDown() { + // Reregisters the chrome frame DLL in the build directory. + chrome_frame_registrar_.reset(NULL); + } +}; + +// This class overrides the hooks provided by the ChromeFrameActiveXContainer +// class and calls back into the ChromeFrameMemoryTest object instance, +// which measures ChromeFrame memory usage. +class ChromeFrameActiveXContainerMemory : public ChromeFrameActiveXContainer { + public: + ChromeFrameActiveXContainerMemory() + : delegate_(NULL) {} + + ~ChromeFrameActiveXContainerMemory() {} + + void Initialize(ChromeFrameMemoryTest* delegate) { + ASSERT_TRUE(delegate != NULL); + delegate_ = delegate; + } + + protected: + virtual void OnLoadCallbackImpl(VARIANT* param) { + delegate_->OnNavigationSuccess(param); + } + + virtual void OnLoadErrorCallbackImpl(VARIANT* param) { + delegate_->OnNavigationFailure(param); + } + + ChromeFrameMemoryTest* delegate_; +}; + +// This class runs memory tests against the ChromeFrame ActiveX. +template<class MemoryTestBase> +class ChromeFrameActiveXMemoryTest : public MemoryTestBase { + public: + ChromeFrameActiveXMemoryTest() + : chrome_frame_container_(NULL), + test_completed_(false) {} + + ~ChromeFrameActiveXMemoryTest() { + } + + void StartTest(const std::string& url, const std::string& test_name) { + ASSERT_TRUE(chrome_frame_container_ == NULL); + + test_name_ = test_name; + + SimpleModule module; + AtlAxWinInit(); + + CComObject<ChromeFrameActiveXContainerMemory>::CreateInstance( + &chrome_frame_container_); + chrome_frame_container_->AddRef(); + + chrome_frame_container_->Initialize(this); + + chrome_frame_container_->CreateChromeFrameWindow(url.c_str()); + chrome_frame_container_->CreateControl(true); + + module.RunMessageLoop(); + + chrome_frame_container_->Release(); + + PrintResults(test_name_.c_str()); + + CoFreeUnusedLibraries(); + //ASSERT_TRUE(GetModuleHandle(L"npchrome_tab.dll") == NULL); + } + + void NavigateImpl(const std::string& url) { + ASSERT_TRUE(chrome_frame_container_ != NULL); + ASSERT_TRUE(!url.empty()); + chrome_frame_container_->Navigate(url.c_str()); + } + + void TestCompleted() { + // This can get called multiple times if the last url results in a + // redirect. + if (!test_completed_) { + ASSERT_NE(browser_pid_, 0); + + // Measure memory usage for the browser process. + AccountProcessMemoryUsage(browser_pid_); + // Measure memory usage for the current process. + AccountProcessMemoryUsage(GetCurrentProcessId()); + + test_completed_ = true; + EXPECT_TRUE(PostMessage(static_cast<HWND>(*chrome_frame_container_), + WM_CLOSE, 0, 0)); + } + } + + protected: + CComObject<ChromeFrameActiveXContainerMemory>* chrome_frame_container_; + std::string test_name_; + bool test_completed_; +}; + +// This class runs tests to measure chrome frame creation only. This will help +// track overall page load performance with chrome frame instances. +class ChromeFrameCreationTest : public ChromeFrameStartupTest { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainer> wnd; + wnd.CreateChromeFrameWindow(startup_url_); + *start_time = TimeTicks::Now(); + wnd.CreateControl(false); + *end_time = TimeTicks::Now(); + } +}; + +// This class provides functionality to run the chrome frame +// performance test against a reference build. +class ChromeFrameCreationTestReference : public ChromeFrameCreationTest { + public: + // override the browser directory to use the reference build instead. + virtual void SetUp() { + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + chrome_frame_registrar_->RegisterReferenceChromeFrameBuild(); + ChromeFrameStartupTest::SetUp(); + } + + virtual void TearDown() { + chrome_frame_registrar_.reset(NULL); + } +}; + +// This class measures the creation time for Flash, which would be used +// as a baseline to measure chrome frame creation performance. +class FlashCreationTest : public ChromeFrameStartupTest { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + SimpleModule module; + AtlAxWinInit(); + CAxWindow host; + RECT rc = {0, 0, 800, 600}; + host.Create(NULL, rc, NULL, + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); + EXPECT_TRUE(host.m_hWnd != NULL); + + *start_time = TimeTicks::Now(); + HRESULT hr = host.CreateControl(L"ShockwaveFlash.ShockwaveFlash"); + EXPECT_HRESULT_SUCCEEDED(hr); + *end_time = TimeTicks::Now(); + + ReleaseHostComReferences(host); + } +}; + +// This class measures the creation time for Silverlight, which would be used +// as a baseline to measure chrome frame creation performance. +class SilverlightCreationTest : public ChromeFrameStartupTest { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + SimpleModule module; + AtlAxWinInit(); + CAxWindow host; + RECT rc = {0, 0, 800, 600}; + host.Create(NULL, rc, NULL, + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); + EXPECT_TRUE(host.m_hWnd != NULL); + + *start_time = TimeTicks::Now(); + HRESULT hr = host.CreateControl(L"AgControl.AgControl"); + EXPECT_HRESULT_SUCCEEDED(hr); + *end_time = TimeTicks::Now(); + + ReleaseHostComReferences(host); + } +}; + +TEST(ChromeFramePerf, DISABLED_HostActiveX) { + // TODO(stoyan): Create a low integrity level thread && perform the test there + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd; + wnd.CreateChromeFrameWindow("http://www.google.com"); + wnd.CreateControl(true); + module.RunMessageLoop(); +} + +TEST(ChromeFramePerf, DISABLED_HostActiveXInvalidURL) { + // TODO(stoyan): Create a low integrity level thread && perform the test there + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd; + wnd.CreateChromeFrameWindow("http://non-existent-domain.org/"); + wnd.CreateControl(true); + module.RunMessageLoop(); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfWarm) { + RunStartupTest("warm", "t", "about:blank", false /* cold */, 0, NULL, + true /* important */, false); +} + +TEST_F(ChromeFrameBinariesLoadTest, PerfWarm) { + RunStartupTest("binary_load_warm", "t", "", false /* cold */, 0, NULL, + true /* important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfCold) { + FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_, chrome_frame_dll_}; + RunStartupTest("cold", "t", "about:blank", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* not important */, false); +} + +TEST_F(ChromeFrameBinariesLoadTest, PerfCold) { + FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_, chrome_frame_dll_}; + RunStartupTest("binary_load_cold", "t", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* not important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveXReference, PerfWarm) { + RunStartupTest("warm", "t_ref", "about:blank", false /* cold */, 0, NULL, + true /* important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationWarm) { + RunStartupTest("ChromeFrame_init_warm", "t", "", false /* cold */, 0, + NULL, true /* important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationCold) { + FilePath binaries_to_evict[] = {chrome_frame_dll_}; + RunStartupTest("ChromeFrame_init_cold", "t", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* not important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveXReference, + PerfChromeFrameInitializationWarm) { + RunStartupTest("ChromeFrame_init_warm", "t_ref", "", false /* cold */, 0, + NULL, true /* important */, false); +} + +typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTest> + RegularChromeFrameActiveXMemoryTest; + +TEST_F(RegularChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) { + char *urls[] = {"about:blank"}; + RunTest("memory_about_blank", urls, arraysize(urls)); +} + +// TODO(iyengar) +// Revisit why the chrome frame dll does not unload correctly when this test is +// run. +TEST_F(RegularChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) { + // TODO(iyengar) + // We should use static pages to measure memory usage. + char *urls[] = { + "http://www.youtube.com/watch?v=PN2HAroA12w", + "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel" + }; + + RunTest("memory", urls, arraysize(urls)); +} + +typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTestReference> + ReferenceBuildChromeFrameActiveXMemoryTest; + +TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) { + char *urls[] = {"about:blank"}; + RunTest("memory_about_blank_reference", urls, arraysize(urls)); +} + +// TODO(iyengar) +// Revisit why the chrome frame dll does not unload correctly when this test is +// run. +TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) { + // TODO(iyengar) + // We should use static pages to measure memory usage. + char *urls[] = { + "http://www.youtube.com/watch?v=PN2HAroA12w", + "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel" + }; + + RunTest("memory_reference", urls, arraysize(urls)); +} + +TEST_F(ChromeFrameCreationTest, PerfWarm) { + RunStartupTest("creation_warm", "t", "", false /* cold */, 0, + NULL, true /* important */, false); +} + +TEST_F(ChromeFrameCreationTestReference, PerfWarm) { + RunStartupTest("creation_warm", "t_ref", "about:blank", false /* cold */, 0, + NULL, true /* not important */, false); +} + +TEST_F(FlashCreationTest, PerfWarm) { + RunStartupTest("creation_warm", "t_flash", "", false /* cold */, 0, NULL, + true /* not important */, false); +} + +TEST_F(SilverlightCreationTest, DISABLED_PerfWarm) { + RunStartupTest("creation_warm", "t_silverlight", "", false /* cold */, 0, + NULL, false /* not important */, false); +} + +TEST_F(ChromeFrameCreationTest, PerfCold) { + FilePath binaries_to_evict[] = {chrome_frame_dll_}; + + RunStartupTest("creation_cold", "t", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + true /* important */, false); +} + +// Attempt to evict the Flash control can fail on the buildbot as the dll +// is marked read only. The test run is aborted if we fail to evict the file +// from the cache. This could also fail if the Flash control is in use. +// On Vista this could fail because of UAC +TEST_F(FlashCreationTest, PerfCold) { + RegKey flash_key(HKEY_CLASSES_ROOT, kFlashControlKey); + + std::wstring plugin_path; + ASSERT_TRUE(flash_key.ReadValue(L"", &plugin_path)); + ASSERT_FALSE(plugin_path.empty()); + + FilePath flash_path = FilePath::FromWStringHack(plugin_path); + FilePath binaries_to_evict[] = {flash_path}; + + RunStartupTest("creation_cold", "t_flash", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false/* important */, true); +} + +// This test would fail on Vista due to UAC or if the Silverlight control is +// in use. The test run is aborted if we fail to evict the file from the cache. +// Disabling this test as the Silverlight dll does not seem to get unloaded +// correctly causing the attempt to evict the dll from the system cache to +// fail. +TEST_F(SilverlightCreationTest, DISABLED_PerfCold) { + RegKey silverlight_key(HKEY_CLASSES_ROOT, kSilverlightControlKey); + + std::wstring plugin_path; + ASSERT_TRUE(silverlight_key.ReadValue(L"", &plugin_path)); + ASSERT_FALSE(plugin_path.empty()); + + FilePath silverlight_path = FilePath::FromWStringHack(plugin_path); + FilePath binaries_to_evict[] = {silverlight_path}; + + RunStartupTest("creation_cold", "t_silverlight", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* important */, true); +} diff --git a/chrome_frame/test/perf/chrome_frame_perftest.h b/chrome_frame/test/perf/chrome_frame_perftest.h new file mode 100644 index 0000000..5b895f3 --- /dev/null +++ b/chrome_frame/test/perf/chrome_frame_perftest.h @@ -0,0 +1,21 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_ +#define CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_ +#include <atlbase.h> +#include "base/logging.h" +#include "base/perftimer.h" +#include "testing/gtest/include/gtest/gtest.h" + +class SimpleModule : public CAtlExeModuleT<SimpleModule> { + public: + // The ATL code does not set _pAtlModule to NULL on destruction, and therefore + // creating new module (for another test) will ASSERT in constructor. + ~SimpleModule() { + Term(); + _pAtlModule = NULL; + } +}; +#endif // CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_ + diff --git a/chrome_frame/test/perf/chrometab_perftests.vcproj b/chrome_frame/test/perf/chrometab_perftests.vcproj new file mode 100644 index 0000000..b894e6a --- /dev/null +++ b/chrome_frame/test/perf/chrometab_perftests.vcproj @@ -0,0 +1,247 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometab_perftests"
+ ProjectGUID="{760ABE9D-2B3E-48C5-A571-6CD221A04BD6}"
+ RootNamespace="chrometab_perftests"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets=".\chrometab_perftests.vsprops;$(SolutionDir)..\build\debug.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=".."
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets=".\chrometab_perftests.vsprops;$(SolutionDir)..\build\release.vsprops"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=".."
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="0"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\chrometab_perftest.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\chrometab_perftest.h"
+ >
+ </File>
+ <File
+ RelativePath=".\silverlight.cc"
+ >
+ </File>
+ <Filter
+ Name="Common"
+ >
+ <File
+ RelativePath="..\..\chrome\test\chrome_process_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\chrome_process_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\chrome_process_util_win.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\html_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\perf\mem_usage.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\perf_test_suite.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\perftimer.cc"
+ >
+ </File>
+ <File
+ RelativePath="run_all.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\ui\ui_test.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\utils.cc"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/chrome_frame/test/perf/chrometab_perftests.vsprops b/chrome_frame/test/perf/chrometab_perftests.vsprops new file mode 100644 index 0000000..3891381 --- /dev/null +++ b/chrome_frame/test/perf/chrometab_perftests.vsprops @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometeb_perftests"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="_ATL_APARTMENT_THREADED;_ATL_CSTRING_EXPLICIT_CONSTRUCTORS"
+ AdditionalIncludeDirectories=""
+ />
+
+</VisualStudioPropertySheet>
diff --git a/chrome_frame/test/perf/run_all.cc b/chrome_frame/test/perf/run_all.cc new file mode 100644 index 0000000..93a5a67 --- /dev/null +++ b/chrome_frame/test/perf/run_all.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "base/platform_thread.h" +#include "base/perf_test_suite.h" +#include "base/scoped_ptr.h" +#include "chrome/common/chrome_paths.h" +#include "chrome_frame/test_utils.h" + +int main(int argc, char **argv) { + PerfTestSuite perf_suite(argc, argv); + chrome::RegisterPathProvider(); + PlatformThread::SetName("ChromeFrame perf tests"); + // Use ctor/raii to register the local Chrome Frame dll. + scoped_ptr<ScopedChromeFrameRegistrar> registrar(new ScopedChromeFrameRegistrar); + return perf_suite.Run(); +} diff --git a/chrome_frame/test/perf/silverlight.cc b/chrome_frame/test/perf/silverlight.cc new file mode 100644 index 0000000..3016c2b --- /dev/null +++ b/chrome_frame/test/perf/silverlight.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include <atlbase.h>
+#include <atlwin.h>
+#include <atlhost.h>
+#include "base/scoped_comptr_win.h"
+#include "chrome_frame/test/perf/chrome_frame_perftest.h"
+
+interface IXcpControlDownloadCallback;
+interface __declspec(uuid("1B36028E-B491-4bb2-8584-8A9E0A677D6E"))
+IXcpControlHost : public IUnknown {
+ typedef enum {
+ XcpHostOption_FreezeOnInitialFrame = 0x001,
+ XcpHostOption_DisableFullScreen = 0x002,
+ XcpHostOption_DisableManagedExecution = 0x008,
+ XcpHostOption_EnableCrossDomainDownloads = 0x010,
+ XcpHostOption_UseCustomAppDomain = 0x020,
+ XcpHostOption_DisableNetworking = 0x040,
+ XcpHostOption_DisableScriptCallouts = 0x080,
+ XcpHostOption_EnableHtmlDomAccess = 0x100,
+ XcpHostOption_EnableScriptableObjectAccess = 0x200,
+ } XcpHostOptions;
+
+ STDMETHOD(GetHostOptions)(DWORD* pdwOptions) PURE;
+ STDMETHOD(NotifyLoaded()) PURE;
+ STDMETHOD(NotifyError)(BSTR bstrError, BSTR bstrSource,
+ long nLine, long nColumn) PURE;
+ STDMETHOD(InvokeHandler)(BSTR bstrName, VARIANT varArg1, VARIANT varArg2,
+ VARIANT* pvarResult) PURE;
+ STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl) PURE;
+ STDMETHOD(GetNamedSource)(BSTR bstrSourceName, BSTR* pbstrSource) PURE;
+ STDMETHOD(DownloadUrl)(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback,
+ IStream** ppStream) PURE;
+};
+
+// Not templatized, to trade execution speed vs typing
+class IXcpControlHostImpl : public IXcpControlHost {
+ public:
+ STDMETHOD(GetHostOptions)(DWORD* pdwOptions) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(NotifyLoaded()) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(NotifyError)(BSTR bstrError, BSTR bstrSource,
+ long nLine, long nColumn) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(InvokeHandler)(BSTR bstrName, VARIANT varArg1, VARIANT varArg2,
+ VARIANT* pvarResult) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetNamedSource)(BSTR bstrSourceName, BSTR* pbstrSource) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(DownloadUrl)(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback,
+ IStream** ppStream) {
+ return E_NOTIMPL;
+ }
+};
+
+// Silverlight container. Supports do-nothing implementation of IXcpControlHost.
+// Should be extended to do some real movie-or-something download.
+class SilverlightContainer :
+ public IServiceProviderImpl<SilverlightContainer>,
+ public IXcpControlHostImpl,
+ public CWindowImpl<SilverlightContainer, CWindow, CWinTraits<
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+ WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >,
+ public CComObjectRootEx<CComSingleThreadModel> {
+ public:
+ DECLARE_WND_CLASS_EX(L"Silverlight_container", 0, 0)
+ BEGIN_COM_MAP(SilverlightContainer)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY(IXcpControlHost)
+ END_COM_MAP()
+
+ BEGIN_SERVICE_MAP(SilverlightContainer)
+ SERVICE_ENTRY(__uuidof(IXcpControlHost))
+ END_SERVICE_MAP()
+
+ BEGIN_MSG_MAP(ChromeFrameActiveXContainer)
+ MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
+ END_MSG_MAP()
+
+ LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) {
+ host_.Release();
+ return 0;
+ }
+
+ virtual void OnFinalMessage(HWND ) {
+ }
+
+ static const wchar_t* GetWndCaption() {
+ return L"Silverlight Container";
+ }
+
+ HRESULT CreateWndAndHost(RECT* r) {
+ Create(NULL, r);
+ ShowWindow(SW_SHOWDEFAULT);
+
+ CComPtr<IUnknown> spUnkContainer;
+ HRESULT hr = CAxHostWindow::_CreatorClass::CreateInstance(NULL,
+ __uuidof(IAxWinHostWindow), reinterpret_cast<void**>(&host_));
+ if (SUCCEEDED(hr)) {
+ CComPtr<IObjectWithSite> p;
+ hr = host_.QueryInterface(&p);
+ if (SUCCEEDED(hr)) {
+ p->SetSite(GetUnknown());
+ }
+ }
+ return hr;
+ }
+
+ HRESULT CreateControl() {
+ HRESULT hr = host_->CreateControl(L"AgControl.AgControl", m_hWnd, NULL);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ return hr;
+ }
+
+ ScopedComPtr<IAxWinHostWindow> host_;
+};
+
+// Create and in-place Silverlight control. Should be extended to do something
+// more meaningful.
+TEST(ChromeFramePerf, DISABLED_HostSilverlight2) {
+ SimpleModule module;
+ AtlAxWinInit();
+ CComObjectStackEx<SilverlightContainer> wnd;
+ RECT rc = {0, 0, 800, 600};
+ wnd.CreateWndAndHost(&rc);
+ PerfTimeLogger perf_create("Create Silverlight Control2");
+ wnd.CreateControl();
+ perf_create.Done();
+ wnd.DestroyWindow();
+}
+
+// Simplest test - creates in-place Silverlight control.
+TEST(ChromeFramePerf, DISABLED_HostSilverlight) {
+ SimpleModule module;
+ AtlAxWinInit();
+ CAxWindow host;
+ RECT rc = {0, 0, 800, 600};
+ PerfTimeLogger perf_create("Create Silverlight Control");
+ host.Create(NULL, rc, L"AgControl.AgControl",
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+ WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
+ EXPECT_TRUE(host.m_hWnd != NULL);
+ ScopedComPtr<IDispatch> disp;
+ HRESULT hr = host.QueryControl(disp.Receive());
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ disp.Release();
+ perf_create.Done();
+}
+
diff --git a/chrome_frame/test/run_all_unittests.cc b/chrome_frame/test/run_all_unittests.cc new file mode 100644 index 0000000..787d765 --- /dev/null +++ b/chrome_frame/test/run_all_unittests.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <atlbase.h> + +#include "base/at_exit.h" +#include "base/platform_thread.h" +#include "base/process_util.h" +#include "base/test_suite.h" +#include "base/command_line.h" +#include "chrome/common/chrome_paths.h" + +#include "chrome_frame/test_utils.h" + +// To enable ATL-based code to run in this module +class ChromeFrameUnittestsModule + : public CAtlExeModuleT<ChromeFrameUnittestsModule> { +}; + +ChromeFrameUnittestsModule _AtlModule; + +const wchar_t kNoRegistrationSwitch[] = L"no-registration"; + +int main(int argc, char **argv) { + base::EnableTerminationOnHeapCorruption(); + PlatformThread::SetName("ChromeFrame tests"); + + TestSuite test_suite(argc, argv); + + // If mini_installer is used to register CF, we use the switch + // --no-registration to avoid repetitive registration. + if (CommandLine::ForCurrentProcess()->HasSwitch(kNoRegistrationSwitch)) { + return test_suite.Run(); + } else { + // Register paths needed by the ScopedChromeFrameRegistrar. + chrome::RegisterPathProvider(); + + // This will register the chrome frame in the build directory. It currently + // leaves that chrome frame registered once the tests are done. It must be + // constructed AFTER the TestSuite is created since TestSuites create THE + // AtExitManager. + // TODO(robertshield): Make these tests restore the original registration + // once done. + ScopedChromeFrameRegistrar registrar; + + return test_suite.Run(); + } +} diff --git a/chrome_frame/test/test_server.cc b/chrome_frame/test/test_server.cc new file mode 100644 index 0000000..79ea2cf --- /dev/null +++ b/chrome_frame/test/test_server.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/registry.h" +#include "base/string_util.h" + +#include "chrome_frame/test/test_server.h" + +#include "net/base/winsock_init.h" +#include "net/http/http_util.h" + +namespace test_server { +const char kDefaultHeaderTemplate[] = + "HTTP/1.1 %hs\r\n" + "Connection: close\r\n" + "Content-Type: %hs\r\n" + "Content-Length: %i\r\n\r\n"; +const char kStatusOk[] = "200 OK"; +const char kStatusNotFound[] = "404 Not Found"; +const char kDefaultContentType[] = "text/html; charset=UTF-8"; + +void Request::ParseHeaders(const std::string& headers) { + size_t pos = headers.find("\r\n"); + DCHECK(pos != std::string::npos); + if (pos != std::string::npos) { + headers_ = headers.substr(pos + 2); + + StringTokenizer tokenizer(headers.begin(), headers.begin() + pos, " "); + std::string* parse[] = { &method_, &path_, &version_ }; + int field = 0; + while (tokenizer.GetNext() && field < arraysize(parse)) { + parse[field++]->assign(tokenizer.token_begin(), + tokenizer.token_end()); + } + } + + // Check for content-length in case we're being sent some data. + net::HttpUtil::HeadersIterator it(headers_.begin(), headers_.end(), + "\r\n"); + while (it.GetNext()) { + if (LowerCaseEqualsASCII(it.name(), "content-length")) { + content_length_ = StringToInt(it.values().c_str()); + break; + } + } +} + +bool Connection::CheckRequestReceived() { + bool ready = false; + if (request_.method().length()) { + // Headers have already been parsed. Just check content length. + ready = (data_.size() >= request_.content_length()); + } else { + size_t index = data_.find("\r\n\r\n"); + if (index != std::string::npos) { + // Parse the headers before returning and chop them of the + // data buffer we've already received. + std::string headers(data_.substr(0, index + 2)); + request_.ParseHeaders(headers); + data_.erase(0, index + 4); + ready = (data_.size() >= request_.content_length()); + } + } + + return ready; +} + +bool FileResponse::GetContentType(std::string* content_type) const { + size_t length = ContentLength(); + char buffer[4096]; + void* data = NULL; + + if (length) { + // Create a copy of the first few bytes of the file. + // If we try and use the mapped file directly, FindMimeFromData will crash + // 'cause it cheats and temporarily tries to write to the buffer! + length = std::min(arraysize(buffer), length); + memcpy(buffer, file_->data(), length); + data = buffer; + } + + LPOLESTR mime_type = NULL; + FindMimeFromData(NULL, file_path_.value().c_str(), data, length, NULL, + FMFD_DEFAULT, &mime_type, 0); + if (mime_type) { + *content_type = WideToASCII(mime_type); + ::CoTaskMemFree(mime_type); + } + + return content_type->length() > 0; +} + +void FileResponse::WriteContents(ListenSocket* socket) const { + DCHECK(file_.get()); + if (file_.get()) { + socket->Send(reinterpret_cast<const char*>(file_->data()), + file_->length(), false); + } +} + +size_t FileResponse::ContentLength() const { + if (file_.get() == NULL) { + file_.reset(new file_util::MemoryMappedFile()); + if (!file_->Initialize(file_path_)) { + NOTREACHED(); + file_.reset(); + } + } + return file_.get() ? file_->length() : 0; +} + +bool RedirectResponse::GetCustomHeaders(std::string* headers) const { + *headers = StringPrintf("HTTP/1.1 302 Found\r\n" + "Connection: close\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html\r\n" + "Location: %hs\r\n\r\n", redirect_url_.c_str()); + return true; +} + +SimpleWebServer::SimpleWebServer(int port) { + CHECK(MessageLoop::current()) << "SimpleWebServer requires a message loop"; + net::EnsureWinsockInit(); + AddResponse(&quit_); + server_ = ListenSocket::Listen("127.0.0.1", port, this); + DCHECK(server_.get() != NULL); +} + +SimpleWebServer::~SimpleWebServer() { + ConnectionList::const_iterator it; + for (it = connections_.begin(); it != connections_.end(); it++) + delete (*it); + connections_.clear(); +} + +void SimpleWebServer::AddResponse(Response* response) { + responses_.push_back(response); +} + +Response* SimpleWebServer::FindResponse(const Request& request) const { + std::list<Response*>::const_iterator it; + for (it = responses_.begin(); it != responses_.end(); it++) { + Response* response = (*it); + if (response->Matches(request)) { + return response; + } + } + return NULL; +} + +Connection* SimpleWebServer::FindConnection(const ListenSocket* socket) const { + ConnectionList::const_iterator it; + for (it = connections_.begin(); it != connections_.end(); it++) { + if ((*it)->IsSame(socket)) { + return (*it); + } + } + return NULL; +} + +void SimpleWebServer::DidAccept(ListenSocket* server, + ListenSocket* connection) { + connections_.push_back(new Connection(connection)); +} + +void SimpleWebServer::DidRead(ListenSocket* connection, + const std::string& data) { + Connection* c = FindConnection(connection); + DCHECK(c); + c->AddData(data); + if (c->CheckRequestReceived()) { + const Request& request = c->request(); + Response* response = FindResponse(request); + if (response) { + std::string headers; + if (!response->GetCustomHeaders(&headers)) { + std::string content_type; + if (!response->GetContentType(&content_type)) + content_type = kDefaultContentType; + headers = StringPrintf(kDefaultHeaderTemplate, kStatusOk, + content_type.c_str(), response->ContentLength()); + } + + connection->Send(headers, false); + response->WriteContents(connection); + response->IncrementAccessCounter(); + } else { + std::string payload = "sorry, I can't find " + request.path(); + std::string headers(StringPrintf(kDefaultHeaderTemplate, kStatusNotFound, + kDefaultContentType, payload.length())); + connection->Send(headers, false); + connection->Send(payload, false); + } + } +} + +void SimpleWebServer::DidClose(ListenSocket* sock) { + // To keep the historical list of connections reasonably tidy, we delete + // 404's when the connection ends. + Connection* c = FindConnection(sock); + DCHECK(c); + if (!FindResponse(c->request())) { + // extremely inefficient, but in one line and not that common... :) + connections_.erase(std::find(connections_.begin(), connections_.end(), c)); + delete c; + } +} + +} // namespace test_server diff --git a/chrome_frame/test/test_server.h b/chrome_frame/test/test_server.h new file mode 100644 index 0000000..896b2b3 --- /dev/null +++ b/chrome_frame/test/test_server.h @@ -0,0 +1,296 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_TEST_TEST_SERVER_H_ +#define CHROME_FRAME_TEST_TEST_SERVER_H_ + +// Implementation of an HTTP server for tests. +// To instantiate the server, make sure you have a message loop on the +// current thread and then create an instance of the SimpleWebServer class. +// The server uses two basic concepts, a request and a response. +// The Response interface represents an item (e.g. a document) available from +// the server. A Request object represents a request from a client (e.g. a +// browser). There are several basic Response classes implemented in this file, +// all derived from the Response interface. +// +// Here's a simple example that starts a web server that can serve up +// a single document (http://localhost:1337/foo). +// All other requests will get a 404. +// +// MessageLoopForUI loop; +// test_server::SimpleWebServer server(1337); +// test_server::SimpleResponse document("/foo", "Hello World!"); +// test_server.AddResponse(&document); +// loop.MessageLoop::Run(); +// +// To close the web server, just go to http://localhost:1337/quit. +// +// All Response classes count how many times they have been accessed. Just +// call Response::accessed(). +// +// To implement a custom response object (e.g. to match against a request +// based on some data, serve up dynamic content or take some action on the +// server), just inherit from one of the response classes or directly from the +// Response interface and add your response object to the server's list of +// response objects. + +#include <list> +#include <string> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "net/base/listen_socket.h" + +namespace test_server { + +class Request { + public: + Request() : content_length_(0) { + } + + void ParseHeaders(const std::string& headers); + + const std::string& method() const { + return method_; + } + + const std::string& path() const { + return path_; + } + + const std::string& headers() const { + return headers_; + } + + size_t content_length() const { + return content_length_; + } + + protected: + std::string method_; + std::string path_; + std::string version_; + std::string headers_; + size_t content_length_; + + private: + DISALLOW_COPY_AND_ASSIGN(Request); +}; + +// Manages request headers for a single request. +// For each successful request that's made, the server will keep an instance +// of this class so that they can be checked even after the server has been +// shut down. +class Connection { + public: + explicit Connection(ListenSocket* sock) : socket_(sock) { + } + + ~Connection() { + } + + bool IsSame(const ListenSocket* socket) const { + return socket_ == socket; + } + + void AddData(const std::string& data) { + data_ += data; + } + + bool CheckRequestReceived(); + + const Request& request() const { + return request_; + } + + protected: + scoped_refptr<ListenSocket> socket_; + std::string data_; + Request request_; + + private: + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +// Abstract interface with default implementations for some of the methods and +// a counter for how many times the response object has served requests. +class Response { + public: + Response() : accessed_(0) { + } + + virtual ~Response() { + } + + // Returns true if this response object should be used for a given request. + virtual bool Matches(const Request& r) const = 0; + + // Response objects can optionally supply their own HTTP headers, completely + // bypassing the default ones. + virtual bool GetCustomHeaders(std::string* headers) const { + return false; + } + + // Optionally provide a content type. Return false if you don't specify + // a content type. + virtual bool GetContentType(std::string* content_type) const { + return false; + } + + virtual size_t ContentLength() const { + return 0; + } + + virtual void WriteContents(ListenSocket* socket) const { + } + + void IncrementAccessCounter() { + accessed_++; + } + + size_t accessed() const { + return accessed_; + } + + protected: + size_t accessed_; + + private: + DISALLOW_COPY_AND_ASSIGN(Response); +}; + +// Partial implementation of Response that matches a request's path. +// This is just a convenience implementation for the boilerplate implementation +// of Matches(). Don't instantiate directly. +class ResponseForPath : public Response { + public: + explicit ResponseForPath(const char* request_path) + : request_path_(request_path) { + } + + virtual bool Matches(const Request& r) const { + return r.path().compare(request_path_) == 0; + } + + protected: + std::string request_path_; + + private: + DISALLOW_COPY_AND_ASSIGN(ResponseForPath); +}; + +// A very basic implementation of a response. +// A simple response matches a single document path on the server +// (e.g. "/foo") and returns a document in the form of a string. +class SimpleResponse : public ResponseForPath { + public: + SimpleResponse(const char* request_path, const std::string& contents) + : ResponseForPath(request_path), contents_(contents) { + } + + virtual void WriteContents(ListenSocket* socket) const { + socket->Send(contents_.c_str(), contents_.length(), false); + } + + virtual size_t ContentLength() const { + return contents_.length(); + } + + protected: + std::string contents_; + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleResponse); +}; + +// To serve up files from the web server, create an instance of FileResponse +// and add it to the server's list of responses. The content type of the +// file will be determined by calling FindMimeFromData which examines the +// contents of the file and performs registry lookups. +class FileResponse : public ResponseForPath { + public: + FileResponse(const char* request_path, const FilePath& file_path) + : ResponseForPath(request_path), file_path_(file_path) { + } + + virtual bool GetContentType(std::string* content_type) const; + virtual void WriteContents(ListenSocket* socket) const; + virtual size_t ContentLength() const; + + protected: + FilePath file_path_; + mutable scoped_ptr<file_util::MemoryMappedFile> file_; + + private: + DISALLOW_COPY_AND_ASSIGN(FileResponse); +}; + +// Returns a 302 (temporary redirect) to redirect the client from a path +// on the test server to a different URL. +class RedirectResponse : public ResponseForPath { + public: + RedirectResponse(const char* request_path, const std::string& redirect_url) + : ResponseForPath(request_path), redirect_url_(redirect_url) { + } + + virtual bool GetCustomHeaders(std::string* headers) const; + + protected: + std::string redirect_url_; + + private: + DISALLOW_COPY_AND_ASSIGN(RedirectResponse); +}; + +// typedef for a list of connections. Used by SimpleWebServer. +typedef std::list<Connection*> ConnectionList; + +// Implementation of a simple http server. +// Before creating an instance of the server, make sure the current thread +// has a message loop. +class SimpleWebServer : public ListenSocket::ListenSocketDelegate { + public: + explicit SimpleWebServer(int port); + virtual ~SimpleWebServer(); + + void AddResponse(Response* response); + + // ListenSocketDelegate overrides. + virtual void DidAccept(ListenSocket* server, ListenSocket* connection); + virtual void DidRead(ListenSocket* connection, const std::string& data); + virtual void DidClose(ListenSocket* sock); + + const ConnectionList& connections() { + return connections_; + } + + protected: + class QuitResponse : public SimpleResponse { + public: + QuitResponse() + : SimpleResponse("/quit", "So long and thanks for all the fish.") { + } + + virtual void QuitResponse::WriteContents(ListenSocket* socket) const { + SimpleResponse::WriteContents(socket); + MessageLoop::current()->Quit(); + } + }; + + Response* FindResponse(const Request& request) const; + Connection* FindConnection(const ListenSocket* socket) const; + + protected: + scoped_refptr<ListenSocket> server_; + ConnectionList connections_; + std::list<Response*> responses_; + QuitResponse quit_; + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleWebServer); +}; + +} // namespace test_server + +#endif // CHROME_FRAME_TEST_TEST_SERVER_H_ diff --git a/chrome_frame/test/test_server_test.cc b/chrome_frame/test/test_server_test.cc new file mode 100644 index 0000000..4bd139e --- /dev/null +++ b/chrome_frame/test/test_server_test.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> +#include <wininet.h> + +#include "base/basictypes.h" +#include "base/path_service.h" +#include "base/scoped_handle_win.h" +#include "chrome_frame/test/test_server.h" +#include "net/base/cookie_monster.h" +#include "net/base/host_resolver_proc.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_cache.h" +#include "net/proxy/proxy_service.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TestServerTest: public testing::Test { + protected: + virtual void SetUp() { + PathService::Get(base::DIR_SOURCE_ROOT, &source_path_); + source_path_ = source_path_.Append(FILE_PATH_LITERAL("chrome_frame")); + } + virtual void TearDown() { + } + + public: + const FilePath& source_path() const { + return source_path_; + } + + protected: + FilePath source_path_; +}; + +namespace { + +class ScopedInternet { + public: + explicit ScopedInternet(HINTERNET handle) + : h_(handle) { + } + ~ScopedInternet() { + if (h_) { + InternetCloseHandle(h_); + } + } + + operator HINTERNET() { + return h_; + } + + protected: + HINTERNET h_; +}; + +class URLRequestTestContext : public URLRequestContext { + public: + URLRequestTestContext() { + host_resolver_ = net::CreateSystemHostResolver(); + proxy_service_ = net::ProxyService::CreateNull(); + ssl_config_service_ = new net::SSLConfigServiceDefaults; + http_transaction_factory_ = + new net::HttpCache( + net::HttpNetworkLayer::CreateFactory(host_resolver_, proxy_service_, + ssl_config_service_), + disk_cache::CreateInMemoryCacheBackend(0)); + // In-memory cookie store. + cookie_store_ = new net::CookieMonster(); + } + + virtual ~URLRequestTestContext() { + delete http_transaction_factory_; + } +}; + +class TestURLRequest : public URLRequest { + public: + TestURLRequest(const GURL& url, Delegate* delegate) + : URLRequest(url, delegate) { + set_context(new URLRequestTestContext()); + } +}; + +class UrlTaskChain { + public: + UrlTaskChain(const char* url, UrlTaskChain* next) + : url_(url), next_(next) { + } + + void Run() { + EXPECT_EQ(0, delegate_.response_started_count()); + + MessageLoopForIO loop; + + TestURLRequest r(GURL(url_), &delegate_); + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + EXPECT_EQ(1, delegate_.response_started_count()); + EXPECT_FALSE(delegate_.received_data_before_response()); + EXPECT_NE(0, delegate_.bytes_received()); + } + + UrlTaskChain* next() const { + return next_; + } + + const std::string& response() const { + return delegate_.data_received(); + } + + protected: + std::string url_; + TestDelegate delegate_; + UrlTaskChain* next_; +}; + +DWORD WINAPI FetchUrl(void* param) { + UrlTaskChain* task = reinterpret_cast<UrlTaskChain*>(param); + while (task != NULL) { + task->Run(); + task = task->next(); + } + + return 0; +} + +struct QuitMessageHit { + explicit QuitMessageHit(MessageLoopForUI* loop) : loop_(loop), hit_(false) { + } + + MessageLoopForUI* loop_; + bool hit_; +}; + +void QuitMessageLoop(QuitMessageHit* msg) { + msg->hit_ = true; + msg->loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask); +} + +} // end namespace + +TEST_F(TestServerTest, DISABLED_TestServer) { + // The web server needs a loop to exist on this thread during construction + // the loop must be created before we construct the server. + MessageLoopForUI loop; + + test_server::SimpleWebServer server(1337); + test_server::SimpleResponse person("/person", "Guthrie Govan!"); + server.AddResponse(&person); + test_server::FileResponse file("/file", source_path().Append( + FILE_PATH_LITERAL("CFInstance.js"))); + server.AddResponse(&file); + test_server::RedirectResponse redir("/goog", "http://www.google.com/"); + server.AddResponse(&redir); + + // We should never hit this, but it's our way to break out of the test if + // things start hanging. + QuitMessageHit quit_msg(&loop); + loop.PostDelayedTask(FROM_HERE, + NewRunnableFunction(QuitMessageLoop, &quit_msg), + 10 * 1000); + + UrlTaskChain quit_task("http://localhost:1337/quit", NULL); + UrlTaskChain fnf_task("http://localhost:1337/404", &quit_task); + UrlTaskChain person_task("http://localhost:1337/person", &fnf_task); + UrlTaskChain file_task("http://localhost:1337/file", &person_task); + UrlTaskChain goog_task("http://localhost:1337/goog", &file_task); + + DWORD tid = 0; + ScopedHandle worker(::CreateThread(NULL, 0, FetchUrl, &goog_task, 0, &tid)); + loop.MessageLoop::Run(); + + EXPECT_FALSE(quit_msg.hit_); + if (!quit_msg.hit_) { + EXPECT_EQ(::WaitForSingleObject(worker, 10 * 1000), WAIT_OBJECT_0); + + EXPECT_EQ(person.accessed(), 1); + EXPECT_EQ(file.accessed(), 1); + EXPECT_EQ(redir.accessed(), 1); + + EXPECT_TRUE(person_task.response().find("Guthrie") != std::string::npos); + EXPECT_TRUE(file_task.response().find("function") != std::string::npos); + EXPECT_TRUE(goog_task.response().find("<title>") != std::string::npos); + } else { + ::TerminateThread(worker, ~0); + } +} diff --git a/chrome_frame/test/util_unittests.cc b/chrome_frame/test/util_unittests.cc new file mode 100644 index 0000000..dc3d72d --- /dev/null +++ b/chrome_frame/test/util_unittests.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_version_info.h"
+#include "chrome_frame/test/chrome_frame_unittests.h"
+#include "chrome_frame/utils.h"
+
+const wchar_t kChannelName[] = L"-dev";
+const wchar_t kSuffix[] = L"-fix";
+
+TEST(UtilTests, AppendSuffixToChannelNameTest) {
+ std::wstring str_base;
+ std::wstring channel_name(kChannelName);
+ std::wstring suffix(kSuffix);
+
+ str_base = L"2.0-dev-bar";
+ EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fix-bar", str_base.c_str());
+
+ str_base = L"2.0-dev-fix-bar";
+ EXPECT_FALSE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fix-bar", str_base.c_str());
+
+ str_base = L"2.0-dev-bar-dev-bar";
+ EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fix-bar-dev-bar", str_base.c_str());
+
+ str_base = L"2.0";
+ EXPECT_FALSE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0", str_base.c_str());
+
+ str_base = L"2.0-devvvv";
+ EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fixvvv", str_base.c_str());
+}
+
+TEST(UtilTests, RemoveSuffixFromStringTest) {
+ std::wstring str_base;
+ std::wstring channel_name(kChannelName);
+ std::wstring suffix(kSuffix);
+
+ str_base = L"2.0-dev-fix";
+ EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev", str_base.c_str());
+
+ str_base = L"2.0-dev-fix-full";
+ EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-full", str_base.c_str());
+
+ str_base = L"2.0";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0", str_base.c_str());
+
+ str_base = L"2.0-dev";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev", str_base.c_str());
+
+ str_base = L"2.0-fix";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-fix", str_base.c_str());
+
+ str_base = L"2.0-full-fix";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-full-fix", str_base.c_str());
+
+ str_base = L"2.0-dev-dev-fix";
+ EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-dev", str_base.c_str());
+}
+
+TEST(UtilTests, GetModuleVersionTest) {
+ HMODULE mod = GetModuleHandle(L"kernel32.dll");
+ EXPECT_NE(mod, static_cast<HMODULE>(NULL));
+ wchar_t path[MAX_PATH] = {0};
+ GetModuleFileName(mod, path, arraysize(path));
+ + // Use the method that goes to disk + scoped_ptr<FileVersionInfo> base_info( + FileVersionInfo::CreateFileVersionInfo(path)); + EXPECT_TRUE(base_info.get() != NULL); + + // Use the method that doesn't go to disk + uint32 low = 0, high = 0; + EXPECT_TRUE(GetModuleVersion(mod, &high, &low)); + EXPECT_NE(high, 0); + EXPECT_NE(low, 0); + + // Make sure they give the same results. + VS_FIXEDFILEINFO* fixed_info = base_info->fixed_file_info(); + EXPECT_TRUE(fixed_info != NULL); +
+ EXPECT_EQ(fixed_info->dwFileVersionMS, static_cast<DWORD>(high));
+ EXPECT_EQ(fixed_info->dwFileVersionLS, static_cast<DWORD>(low));
+}
+
+TEST(UtilTests, HaveSameOrigin) {
+ struct OriginCompare {
+ const char* a;
+ const char* b;
+ bool same_origin;
+ } test_cases[] = {
+ { "", "", true },
+ { "*", "*", true },
+ { "*", "+", false },
+ { "http://www.google.com/", "http://www.google.com/", true },
+ { "http://www.google.com", "http://www.google.com/", true },
+ { "http://www.google.com:80/", "http://www.google.com/", true },
+ { "http://www.google.com:8080/", "http://www.google.com/", false },
+ { "https://www.google.com/", "http://www.google.com/", false },
+ { "http://docs.google.com/", "http://www.google.com/", false },
+ { "https://www.google.com/", "https://www.google.com:443/", true },
+ { "https://www.google.com/", "https://www.google.com:443", true },
+ };
+
+ for (int i = 0; i < arraysize(test_cases); ++i) {
+ const OriginCompare& test = test_cases[i];
+ EXPECT_EQ(test.same_origin, HaveSameOrigin(test.a, test.b));
+ }
+}
|