From d0d1953fe2aee59206e45e714875a1c64b070315 Mon Sep 17 00:00:00 2001 From: Chongyun Lee Date: Fri, 4 Aug 2023 17:37:16 +0800 Subject: [PATCH] new package: carbonyl --- .../carbonyl/0001-override-build-target.patch | 11 + .../carbonyl/0002-fix-cxx-include.patch | 9 + .../0003-rename-gfx-namespace-to-gl.patch | 22 + .../carbonyl/0004-render_frame_impl.cc.patch | 11 + ...cc51019997830fd6b506f7577028203ec4efc.diff | 2727 +++++++++++++++++ .../9999-fix-patch-for-headless-shell.diff | 20 + tur-continuous/carbonyl/build.sh | 285 ++ tur-continuous/carbonyl/toolchain.gn.in | 32 + 8 files changed, 3117 insertions(+) create mode 100644 tur-continuous/carbonyl/0001-override-build-target.patch create mode 100644 tur-continuous/carbonyl/0002-fix-cxx-include.patch create mode 100644 tur-continuous/carbonyl/0003-rename-gfx-namespace-to-gl.patch create mode 100644 tur-continuous/carbonyl/0004-render_frame_impl.cc.patch create mode 100644 tur-continuous/carbonyl/9998-upstream-dc9cc51019997830fd6b506f7577028203ec4efc.diff create mode 100644 tur-continuous/carbonyl/9999-fix-patch-for-headless-shell.diff create mode 100644 tur-continuous/carbonyl/build.sh create mode 100644 tur-continuous/carbonyl/toolchain.gn.in diff --git a/tur-continuous/carbonyl/0001-override-build-target.patch b/tur-continuous/carbonyl/0001-override-build-target.patch new file mode 100644 index 000000000..76c22f34b --- /dev/null +++ b/tur-continuous/carbonyl/0001-override-build-target.patch @@ -0,0 +1,11 @@ +--- a/src/browser/BUILD.gn ++++ b/src/browser/BUILD.gn +@@ -40,7 +40,7 @@ + if (is_mac) { + target += "apple-darwin" + } else if (is_linux) { +- target += "unknown-linux-gnu" ++ target += "linux-android" + } + + libs = ["carbonyl"] diff --git a/tur-continuous/carbonyl/0002-fix-cxx-include.patch b/tur-continuous/carbonyl/0002-fix-cxx-include.patch new file mode 100644 index 000000000..da6d95245 --- /dev/null +++ b/tur-continuous/carbonyl/0002-fix-cxx-include.patch @@ -0,0 +1,9 @@ +--- a/chromium/src/carbonyl/src/browser/renderer.cc ++++ b/chromium/src/carbonyl/src/browser/renderer.cc +@@ -1,5 +1,6 @@ + #include "carbonyl/src/browser/renderer.h" + ++#include + #include + #include + #include diff --git a/tur-continuous/carbonyl/0003-rename-gfx-namespace-to-gl.patch b/tur-continuous/carbonyl/0003-rename-gfx-namespace-to-gl.patch new file mode 100644 index 000000000..4f15fab3b --- /dev/null +++ b/tur-continuous/carbonyl/0003-rename-gfx-namespace-to-gl.patch @@ -0,0 +1,22 @@ +--- a/chromium/src/components/viz/service/display_embedder/software_output_device_proxy.h ++++ b/chromium/src/components/viz/service/display_embedder/software_output_device_proxy.h +@@ -64,7 +64,7 @@ + SoftwareOutputDeviceProxy& operator=(const SoftwareOutputDeviceProxy&) = delete; + + // SoftwareOutputDevice implementation. +- void OnSwapBuffers(SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback, gfx::FrameData data) override; ++ void OnSwapBuffers(SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback, gl::FrameData data) override; + + // SoftwareOutputDeviceBase implementation. + void ResizeDelegated() override; +--- a/chromium/src/components/viz/service/display_embedder/software_output_device_proxy.cc ++++ b/chromium/src/components/viz/service/display_embedder/software_output_device_proxy.cc +@@ -73,7 +73,7 @@ + + void SoftwareOutputDeviceProxy::OnSwapBuffers( + SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback, +- gfx::FrameData data) { ++ gl::FrameData data) { + DCHECK(swap_ack_callback_.is_null()); + + // We aren't waiting on DrawAck() and can immediately run the callback. diff --git a/tur-continuous/carbonyl/0004-render_frame_impl.cc.patch b/tur-continuous/carbonyl/0004-render_frame_impl.cc.patch new file mode 100644 index 000000000..08fc4d10b --- /dev/null +++ b/tur-continuous/carbonyl/0004-render_frame_impl.cc.patch @@ -0,0 +1,11 @@ +--- a/chromium/src/content/renderer/render_frame_impl.cc ++++ b/chromium/src/content/renderer/render_frame_impl.cc +@@ -2223,7 +2223,7 @@ + auto* view = static_cast(GetWebFrame()->View()); + std::vector data; + +- view->MainFrameImpl()->GetFrame()->View()->GetPaintRecord().Playback( ++ view->MainFrameImpl()->GetFrame()->View()->GetPaintRecord()->Playback( + renderer->BeginPaint(width, height) + ); + diff --git a/tur-continuous/carbonyl/9998-upstream-dc9cc51019997830fd6b506f7577028203ec4efc.diff b/tur-continuous/carbonyl/9998-upstream-dc9cc51019997830fd6b506f7577028203ec4efc.diff new file mode 100644 index 000000000..4fd3c71e0 --- /dev/null +++ b/tur-continuous/carbonyl/9998-upstream-dc9cc51019997830fd6b506f7577028203ec4efc.diff @@ -0,0 +1,2727 @@ +diff -uNr a/chromium/src/build/args/headless.gn b/chromium/src/build/args/headless.gn +--- a/chromium/src/build/args/headless.gn 2023-02-23 02:41:38.693335300 +0800 ++++ b/chromium/src/build/args/headless.gn 2023-08-04 12:43:55.229936729 +0800 +@@ -17,6 +17,9 @@ + # Embed resource.pak into binary to simplify deployment. + headless_use_embedded_resources = true + ++# Disable headless commands support. ++headless_enable_commands = false ++ + # Don't use Prefs component, disabling access to Local State prefs. + headless_use_prefs = false + +diff -uNr a/chromium/src/chrome/chrome_paks.gni b/chromium/src/chrome/chrome_paks.gni +--- a/chromium/src/chrome/chrome_paks.gni 2023-02-23 02:41:44.225331500 +0800 ++++ b/chromium/src/chrome/chrome_paks.gni 2023-08-04 12:43:55.229936729 +0800 +@@ -9,6 +9,7 @@ + import("//chrome/browser/buildflags.gni") + import("//chrome/common/features.gni") + import("//extensions/buildflags/buildflags.gni") ++import("//headless/headless.gni") + import("//pdf/features.gni") + import("//ui/base/ui_features.gni") + import("chrome_repack_locales.gni") +@@ -460,6 +461,11 @@ + "//chrome/browser/resources/chromeos/chromebox_for_meetings:resources", + ] + } ++ ++ if (headless_enable_commands && !is_android) { ++ sources += [ "$root_gen_dir/headless/headless_command_resources.pak" ] ++ deps += [ "//headless:headless_command_resources" ] ++ } + } + } + +diff -uNr a/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.cc b/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.cc +--- a/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.cc 2023-02-23 02:41:46.925329700 +0800 ++++ b/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -70,6 +70,11 @@ + AttachClient(DevToolsAgentHost::GetOrCreateFor(web_contents)); + } + ++std::string SimpleDevToolsProtocolClient::GetTargetId() { ++ DCHECK(agent_host_); ++ return agent_host_->GetId(); ++} ++ + std::unique_ptr + SimpleDevToolsProtocolClient::CreateSession(const std::string& session_id) { + auto client = std::make_unique(session_id); +diff -uNr a/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h b/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h +--- a/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h 2023-02-23 02:41:46.925329700 +0800 ++++ b/chromium/src/components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h 2023-08-04 12:43:55.229936729 +0800 +@@ -58,6 +58,8 @@ + + void SendCommand(const std::string& method); + ++ std::string GetTargetId(); ++ + protected: + // content::DevToolsAgentHostClient implementation. + void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, +diff -uNr a/chromium/src/headless/BUILD.gn b/chromium/src/headless/BUILD.gn +--- a/chromium/src/headless/BUILD.gn 2023-02-23 02:41:51.261326800 +0800 ++++ b/chromium/src/headless/BUILD.gn 2023-08-04 12:43:55.229936729 +0800 +@@ -23,10 +23,20 @@ + "'headless_use_policy' requires 'headless_use_prefs'.") + } + ++if (headless_enable_commands) { ++ assert( ++ !headless_use_embedded_resources, ++ "'headless_enable_commands' is not compatible with 'headless_use_embedded_resources'.") ++} ++ + # Headless defines config applied to every target below. + config("headless_defines_config") { + defines = [] + ++ if (headless_enable_commands) { ++ defines += [ "HEADLESS_ENABLE_COMMANDS" ] ++ } ++ + if (headless_use_prefs) { + defines += [ "HEADLESS_USE_PREFS" ] + } +@@ -162,6 +172,26 @@ + deps = [ ":resource_pack_strings" ] + } + ++if (headless_enable_commands) { ++ grit("headless_command_resources") { ++ source = "app/headless_command.grd" ++ outputs = [ ++ "grit/headless_command_resources.h", ++ "$root_gen_dir/headless/headless_command_resources.pak", ++ ] ++ ++ use_brotli = true ++ } ++ ++ repack("headless_command_resources_pack") { ++ sources = [ "$root_gen_dir/headless/headless_command_resources.pak" ] ++ ++ output = "$root_out_dir/headless_command_resources.pak" ++ ++ deps = [ ":headless_command_resources" ] ++ } ++} ++ + devtools_domains = [ + "accessibility", + "animation", +@@ -319,6 +349,13 @@ + "public/util/user_agent.h", + ] + ++ if (headless_enable_commands) { ++ sources += [ ++ "app/headless_command_switches.cc", ++ "app/headless_command_switches.h", ++ ] ++ } ++ + sources += generated_devtools_api_headers + generated_devtools_api_sources + + if (!is_fuchsia) { +@@ -779,6 +816,8 @@ + "//v8:external_startup_data", + ] + sources = [ ++ "test/capture_std_stream.cc", ++ "test/capture_std_stream.h", + "test/headless_browser_browsertest.cc", + "test/headless_browser_context_browsertest.cc", + "test/headless_browser_test.cc", +@@ -797,7 +836,11 @@ + ] + + if (enable_printing && enable_pdf) { +- sources += [ "test/headless_printtopdf_browsertest.cc" ] ++ sources += [ ++ "test/headless_printtopdf_browsertest.cc", ++ "test/pdf_utils.cc", ++ "test/pdf_utils.h", ++ ] + } + + if (headless_use_policy) { +@@ -807,6 +850,10 @@ + ] + } + ++ if (headless_enable_commands) { ++ sources += [ "test/headless_command_browsertest.cc" ] ++ } ++ + # TODO(crbug.com/1318548): Enable on Fuchsia when no longer flakily timeout. + if (!is_fuchsia) { + sources += [ +@@ -827,6 +874,10 @@ + "//third_party/pywebsocket3/", + ] + ++ if (headless_enable_commands) { ++ data += [ "$root_out_dir/headless_command_resources.pak" ] ++ } ++ + data_deps = [] + + if (is_fuchsia) { +@@ -931,6 +982,17 @@ + if (enable_printing) { + deps += [ "//components/printing/browser/headless:headless" ] + } ++ if (headless_enable_commands) { ++ sources += [ ++ "app/headless_command_handler.cc", ++ "app/headless_command_handler.h", ++ ] ++ deps += [ ++ ":headless_command_resources", ++ ":headless_command_resources_pack", ++ ] ++ } ++ + configs += [ ":headless_defines_config" ] + } + } +@@ -1000,6 +1062,17 @@ + deps += [ "//components/policy/content" ] + } + ++ if (headless_enable_commands) { ++ sources += [ ++ "app/headless_command_handler.cc", ++ "app/headless_command_handler.h", ++ ] ++ deps += [ ++ ":headless_command_resources", ++ ":headless_command_resources_pack", ++ ] ++ } ++ + if (is_win) { + defines = [ "HEADLESS_USE_CRASHPAD" ] + +diff -uNr a/chromium/src/headless/app/headless_command.grd b/chromium/src/headless/app/headless_command.grd +--- a/chromium/src/headless/app/headless_command.grd 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/app/headless_command.grd 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,16 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff -uNr a/chromium/src/headless/app/headless_command.html b/chromium/src/headless/app/headless_command.html +--- a/chromium/src/headless/app/headless_command.html 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/app/headless_command.html 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ +diff -uNr a/chromium/src/headless/app/headless_command.js b/chromium/src/headless/app/headless_command.js +--- a/chromium/src/headless/app/headless_command.js 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/app/headless_command.js 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,301 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++// ++// CDPClient ++// ++class CDPClient { ++ constructor() { ++ this._requestId = 0; ++ this._sessions = new Map(); ++ } ++ ++ nextRequestId() { ++ return ++this._requestId; ++ } ++ ++ addSession(session) { ++ this._sessions.set(session.sessionId(), session); ++ } ++ ++ getSession(sessionId) { ++ this._sessions.get(sessionId); ++ } ++ ++ async dispatchMessage(message) { ++ const messageObject = JSON.parse(message); ++ const session = this._sessions.get(messageObject.sessionId || ''); ++ if (session) ++ session.dispatchMessage(messageObject); ++ } ++ ++ reportError(message, error) { ++ if (error) ++ console.error(`${message}: ${error}\n${error.stack}`); ++ else ++ console.error(message); ++ } ++} ++ ++const cdpClient = new CDPClient(); ++ ++// ++// CDPSession ++// ++class CDPSession { ++ constructor(sessionId) { ++ this._sessionId = sessionId || ''; ++ this._parentSessionId = null; ++ this._dispatchTable = new Map(); ++ this._eventHandlers = new Map(); ++ this._protocol = this._getProtocol(); ++ cdpClient.addSession(this); ++ } ++ ++ sessionId() { ++ return this._sessionId; ++ } ++ ++ protocol() { ++ return this._protocol; ++ } ++ ++ createSession(sessionId) { ++ const session = new CDPSession(sessionId); ++ session._parentSessionId = this._sessionId; ++ return session; ++ } ++ ++ async sendCommand(method, params) { ++ const requestId = cdpClient.nextRequestId(); ++ const messageObject = {'id': requestId, 'method': method, 'params': params}; ++ if (this._sessionId) ++ messageObject.sessionId = this._sessionId; ++ sendDevToolsMessage(JSON.stringify(messageObject)); ++ return new Promise(f => this._dispatchTable.set(requestId, f)); ++ } ++ ++ async dispatchMessage(message) { ++ try { ++ const messageId = message.id; ++ if (typeof messageId === 'number') { ++ const handler = this._dispatchTable.get(messageId); ++ if (handler) { ++ this._dispatchTable.delete(messageId); ++ handler(message); ++ } else { ++ cdpClient.reportError(`Unexpected result id ${messageId}`); ++ } ++ } else { ++ const eventName = message.method; ++ for (const handler of (this._eventHandlers.get(eventName) || [])) ++ handler(message); ++ } ++ } catch (e) { ++ cdpClient.reportError(`Exception when dispatching message\n' + ++ '${JSON.stringify(message)}`, e); ++ } ++ } ++ ++ _getProtocol() { ++ return new Proxy({}, { ++ get: (target, domainName, receiver) => new Proxy({}, { ++ get: (target, methodName, receiver) => { ++ const eventPattern = /^(on(ce)?|off)([A-Z][A-Za-z0-9]*)/; ++ const match = eventPattern.exec(methodName); ++ if (!match) { ++ return args => this.sendCommand( ++ `${domainName}.${methodName}`, args || {}); ++ } ++ let eventName = match[3]; ++ eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1); ++ if (match[1] === 'once') { ++ return eventMatcher => this._waitForEvent( ++ `${domainName}.${eventName}`, eventMatcher); ++ } ++ if (match[1] === 'off') { ++ return listener => this._removeEventHandler( ++ `${domainName}.${eventName}`, listener); ++ } ++ return listener => this._addEventHandler( ++ `${domainName}.${eventName}`, listener); ++ } ++ }) ++ }); ++ } ++ ++ _waitForEvent(eventName, eventMatcher) { ++ return new Promise(callback => { ++ const handler = result => { ++ if (eventMatcher && !eventMatcher(result)) ++ return; ++ this._removeEventHandler(eventName, handler); ++ callback(result); ++ }; ++ this._addEventHandler(eventName, handler); ++ }); ++ } ++ ++ _addEventHandler(eventName, handler) { ++ const handlers = this._eventHandlers.get(eventName) || []; ++ handlers.push(handler); ++ this._eventHandlers.set(eventName, handlers); ++ } ++ ++ _removeEventHandler(eventName, handler) { ++ const handlers = this._eventHandlers.get(eventName) || []; ++ const index = handlers.indexOf(handler); ++ if (index === -1) ++ return; ++ handlers.splice(index, 1); ++ this._eventHandlers.set(eventName, handlers); ++ } ++} ++ ++// ++// TargetPage ++// ++class TargetPage { ++ constructor(browserSession) { ++ this._browserSession = browserSession; ++ this._targetId = ''; ++ this._session; ++ } ++ ++ static async createAndNavigate(browserSession, url) { ++ const targetPage = new TargetPage(browserSession); ++ ++ const dp = browserSession.protocol(); ++ const params = {url: 'about:blank', width: 800, height: 600}; ++ const createContextOptions = {}; ++ params.browserContextId = (await dp.Target.createBrowserContext( ++ createContextOptions)).result.browserContextId; ++ targetPage._targetId = ++ (await dp.Target.createTarget(params)).result.targetId; ++ ++ const sessionId = (await dp.Target.attachToTarget( ++ {targetId: targetPage._targetId, flatten: true})).result.sessionId; ++ targetPage._session = browserSession.createSession(sessionId); ++ ++ await targetPage._navigate(url); ++ ++ return targetPage; ++ } ++ ++ targetId() { ++ return this._targetId; ++ } ++ ++ session() { ++ return this._session; ++ } ++ ++ async _navigate(url) { ++ const dp = this._session.protocol(); ++ await dp.Page.enable(); ++ await dp.Page.setLifecycleEventsEnabled({enabled: true}); ++ const frameId = (await dp.Page.navigate({url})).result.frameId; ++ await dp.Page.onceLifecycleEvent( ++ event => event.params.name === 'load' && ++ event.params.frameId === frameId); ++ } ++} ++ ++// ++// Command handlers ++// ++async function dumpDOM(dp) { ++ const script = ++ "(document.doctype ? new " + ++ "XMLSerializer().serializeToString(document.doctype) + '\\n' : '')" + ++ " + document.documentElement.outerHTML"; ++ ++ const response = await dp.Runtime.evaluate({expression: script}); ++ return response.result.result.value; ++} ++ ++async function printToPDF(dp, params) { ++ const displayHeaderFooter = !params.noHeaderFooter; ++ ++ const printToPDFParams = { ++ displayHeaderFooter, ++ printBackground: true, ++ preferCSSPageSize: true, ++ }; ++ ++ const response = await dp.Page.printToPDF(printToPDFParams); ++ return response.result.data; ++} ++ ++async function screenshot(dp, params) { ++ const format = params.format || 'png'; ++ const screenshotParams = { ++ format, ++ }; ++ const response = await dp.Page.captureScreenshot(screenshotParams); ++ return response.result.data; ++} ++ ++async function handleCommands(dp, commands) { ++ const result = {}; ++ if ('dumpDom' in commands) ++ result.dumpDomResult = await dumpDOM(dp); ++ ++ if ('printToPDF' in commands) ++ result.printToPdfResult = await printToPDF(dp, commands.printToPDF); ++ ++ if ('screenshot' in commands) ++ result.screenshotResult = await screenshot(dp, commands.screenshot); ++ ++ return result; ++} ++ ++// ++// Target.exposeDevToolsProtocol() communication functions. ++// ++window.cdp.onmessage = json => { ++ //console.log('[recv] ' + json); ++ cdpClient.dispatchMessage(json); ++} ++ ++function sendDevToolsMessage(json) { ++ //console.log('[send] ' + json); ++ window.cdp.send(json); ++} ++ ++// ++// This is called from the host. ++// ++async function executeCommands(commands) { ++ const browserSession = new CDPSession(); ++ const targetPage = await TargetPage.createAndNavigate( ++ browserSession, commands.targetUrl); ++ const dp = targetPage.session().protocol(); ++ ++ if ('defaultBackgroundColor' in commands) { ++ await dp.Emulation.setDefaultBackgroundColorOverride( ++ {color: commands.defaultBackgroundColor}); ++ } ++ ++ let promises = []; ++ if ('timeout' in commands) { ++ const timeoutPromise = new Promise(resolve => { ++ setTimeout(resolve, commands.timeout); ++ }); ++ promises.push(timeoutPromise); ++ } ++ ++ if ('virtualTimeBudget' in commands) { ++ await dp.Emulation.setVirtualTimePolicy( ++ {budget: commands.virtualTimeBudget, ++ maxVirtualTimeTaskStarvationCount: 9999, ++ policy: 'pauseIfNetworkFetchesPending' }); ++ promises.push(dp.Emulation.onceVirtualTimeBudgetExpired()); ++ } ++ ++ if (promises.length > 0) ++ await Promise.race(promises); ++ ++ return await handleCommands(dp, commands); ++} +diff -uNr a/chromium/src/headless/app/headless_command_handler.cc b/chromium/src/headless/app/headless_command_handler.cc +--- a/chromium/src/headless/app/headless_command_handler.cc 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/app/headless_command_handler.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,339 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include "headless/app/headless_command_handler.h" ++ ++#include ++#include ++#include ++ ++#include "base/base64.h" ++#include "base/bind.h" ++#include "base/callback.h" ++#include "base/command_line.h" ++#include "base/containers/adapters.h" ++#include "base/containers/span.h" ++#include "base/files/file.h" ++#include "base/files/file_path.h" ++#include "base/files/file_util.h" ++#include "base/i18n/rtl.h" ++#include "base/json/json_writer.h" ++#include "base/logging.h" ++#include "base/path_service.h" ++#include "base/strings/strcat.h" ++#include "base/strings/string_number_conversions.h" ++#include "base/strings/string_util.h" ++#include "base/task/sequenced_task_runner.h" ++#include "base/task/task_traits.h" ++#include "base/task/thread_pool.h" ++#include "build/build_config.h" ++#include "content/public/app/content_main.h" ++#include "content/public/browser/browser_task_traits.h" ++#include "content/public/browser/browser_thread.h" ++#include "content/public/browser/web_contents.h" ++#include "content/public/browser/web_ui_data_source.h" ++#include "headless/app/headless_command_switches.h" ++#include "headless/grit/headless_command_resources.h" ++#include "ui/base/resource/resource_bundle.h" ++ ++namespace headless { ++ ++namespace { ++ ++// Default file name for screenshot. Can be overridden by "--screenshot" switch. ++const char kDefaultScreenshotFileName[] = "screenshot.png"; ++// Default file name for pdf. Can be overridden by "--print-to-pdf" switch. ++const char kDefaultPDFFileName[] = "output.pdf"; ++ ++const char kChromeHeadlessHost[] = "headless"; ++const char kChromeHeadlessURL[] = "chrome://headless/"; ++ ++const char kHeadlessCommandHtml[] = "headless_command.html"; ++const char kHeadlessCommandJs[] = "headless_command.js"; ++ ++content::WebUIDataSource* CreateHeadlessHostDataSource() { ++ base::FilePath resource_dir; ++ CHECK(base::PathService::Get(base::DIR_ASSETS, &resource_dir)); ++ ++ base::FilePath resource_pack = ++ resource_dir.Append(FILE_PATH_LITERAL("headless_command_resources.pak")); ++ CHECK(base::PathExists(resource_pack)) << resource_pack; ++ ++ ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath( ++ resource_pack, ui::kScaleFactorNone); ++ ++ content::WebUIDataSource* source = ++ content::WebUIDataSource::Create(kChromeHeadlessHost); ++ ++ source->AddResourcePath(kHeadlessCommandHtml, IDR_HEADLESS_COMMAND_HTML); ++ source->AddResourcePath(kHeadlessCommandJs, IDR_HEADLESS_COMMAND_JS); ++ ++ return source; ++} ++ ++base::Value::Dict GetColorDictFromHexColor(uint32_t color, bool has_alpha) { ++ base::Value::Dict dict; ++ if (has_alpha) { ++ dict.Set("r", static_cast((color & 0xff000000) >> 24)); ++ dict.Set("g", static_cast((color & 0x00ff0000) >> 16)); ++ dict.Set("b", static_cast((color & 0x0000ff00) >> 8)); ++ dict.Set("a", static_cast((color & 0x000000ff))); ++ } else { ++ dict.Set("r", static_cast((color & 0xff0000) >> 16)); ++ dict.Set("g", static_cast((color & 0x00ff00) >> 8)); ++ dict.Set("b", static_cast((color & 0x0000ff))); ++ } ++ ++ return dict; ++} ++ ++bool GetCommandDictAndOutputPaths(base::Value::Dict* commands, ++ base::FilePath* pdf_file_path, ++ base::FilePath* screenshot_file_path) { ++ const base::CommandLine* command_line = ++ base::CommandLine::ForCurrentProcess(); ++ ++ // --dump-dom ++ if (command_line->HasSwitch(switches::kDumpDom)) { ++ commands->Set("dumpDom", true); ++ } ++ ++ // --print-to-pdf=[output path] ++ if (command_line->HasSwitch(switches::kPrintToPDF)) { ++ base::FilePath path = ++ command_line->GetSwitchValuePath(switches::kPrintToPDF); ++ if (path.empty()) { ++ path = base::FilePath().AppendASCII(kDefaultPDFFileName); ++ } ++ *pdf_file_path = path; ++ ++ base::Value::Dict params; ++ if (command_line->HasSwitch(switches::kPrintToPDFNoHeader)) { ++ params.Set("noHeaderFooter", true); ++ } ++ ++ commands->Set("printToPDF", std::move(params)); ++ } ++ ++ // --screenshot=[output path] ++ if (command_line->HasSwitch(switches::kScreenshot)) { ++ base::FilePath path = ++ command_line->GetSwitchValuePath(switches::kScreenshot); ++ if (path.empty()) { ++ path = base::FilePath().AppendASCII(kDefaultScreenshotFileName); ++ } ++ *screenshot_file_path = path; ++ ++ base::FilePath::StringType extension = ++ base::ToLowerASCII(path.FinalExtension()); ++ ++ static const std::map ++ kImageFileTypes{ ++ {FILE_PATH_LITERAL(".jpeg"), "jpeg"}, ++ {FILE_PATH_LITERAL(".jpg"), "jpeg"}, ++ {FILE_PATH_LITERAL(".png"), "png"}, ++ {FILE_PATH_LITERAL(".webp"), "webp"}, ++ }; ++ ++ auto it = kImageFileTypes.find(extension); ++ if (it == kImageFileTypes.cend()) { ++ LOG(ERROR) << "Unsupported screenshot image file type: " ++ << path.FinalExtension(); ++ return false; ++ } ++ ++ base::Value::Dict params; ++ params.Set("format", it->second); ++ commands->Set("screenshot", std::move(params)); ++ } ++ ++ // --default-background-color=rrggbb[aa] ++ if (command_line->HasSwitch(switches::kDefaultBackgroundColor)) { ++ std::string hex_color = ++ command_line->GetSwitchValueASCII(switches::kDefaultBackgroundColor); ++ uint32_t color; ++ if (!(hex_color.length() == 6 || hex_color.length() == 8) || ++ !base::HexStringToUInt(hex_color, &color)) { ++ LOG(ERROR) ++ << "Expected a hex RGB or RGBA value for --default-background-color=" ++ << hex_color; ++ return false; ++ } ++ ++ commands->Set("defaultBackgroundColor", ++ GetColorDictFromHexColor(color, hex_color.length() == 8)); ++ } ++ ++ // virtual-time-budget=[ms] ++ if (command_line->HasSwitch(switches::kVirtualTimeBudget)) { ++ std::string budget_ms_str = ++ command_line->GetSwitchValueASCII(switches::kVirtualTimeBudget); ++ int budget_ms; ++ if (!base::StringToInt(budget_ms_str, &budget_ms)) { ++ LOG(ERROR) << "Expected an integer value for --virtual-time-budget=" ++ << budget_ms_str; ++ return false; ++ } ++ ++ commands->Set("virtualTimeBudget", budget_ms); ++ } ++ ++ // timeout=[ms] ++ if (command_line->HasSwitch(switches::kTimeout)) { ++ std::string timeout_ms_str = ++ command_line->GetSwitchValueASCII(switches::kTimeout); ++ int timeout_ms; ++ if (!base::StringToInt(timeout_ms_str, &timeout_ms)) { ++ LOG(ERROR) << "Expected an integer value for --timeout=" ++ << timeout_ms_str; ++ return false; ++ } ++ ++ commands->Set("timeout", timeout_ms); ++ } ++ ++ return true; ++} ++ ++void WriteFileTask(base::FilePath file_path, std::string file_data) { ++ auto file_span = base::make_span( ++ reinterpret_cast(file_data.data()), file_data.size()); ++ if (base::WriteFile(file_path, file_span)) { ++ std::cerr << file_data.size() << " bytes written to file " << file_path ++ << std::endl; ++ } else { ++ PLOG(ERROR) << "Failed to write file " << file_path; ++ } ++} ++ ++void WriteFile(base::FilePath file_path, std::string base64_file_data) { ++ std::string file_data; ++ CHECK(base::Base64Decode(base64_file_data, &file_data)); ++ ++ base::ThreadPool::CreateSequencedTaskRunner( ++ {base::MayBlock(), base::TaskPriority::USER_BLOCKING, ++ base::TaskShutdownBehavior::BLOCK_SHUTDOWN}) ++ ->PostTask(FROM_HERE, base::BindOnce(&WriteFileTask, std::move(file_path), ++ std::move(file_data))); ++} ++ ++} // namespace ++ ++HeadlessCommandHandler::HeadlessCommandHandler( ++ content::WebContents* web_contents, ++ GURL target_url, ++ DoneCallback done_callback) ++ : web_contents_(web_contents), ++ target_url_(std::move(target_url)), ++ done_callback_(std::move(done_callback)) { ++ // Load command execution harness resources and create URL data source ++ // for chrome://headless. ++ content::WebUIDataSource::Add(web_contents_->GetBrowserContext(), ++ CreateHeadlessHostDataSource()); ++ ++ content::WebContentsObserver::Observe(web_contents_); ++ ++ browser_devtools_client_.AttachToBrowser(); ++ devtools_client_.AttachToWebContents(web_contents_); ++} ++ ++HeadlessCommandHandler::~HeadlessCommandHandler() = default; ++ ++// static ++GURL HeadlessCommandHandler::GetHandlerUrl() { ++ const std::string url = ++ base::StrCat({kChromeHeadlessURL, kHeadlessCommandHtml}); ++ return GURL(url); ++} ++ ++// static ++void HeadlessCommandHandler::ProcessCommands(content::WebContents* web_contents, ++ GURL target_url, ++ DoneCallback done_callback) { ++ // Headless Command Handler instance will self delete when done. ++ HeadlessCommandHandler* command_handler = new HeadlessCommandHandler( ++ web_contents, std::move(target_url), std::move(done_callback)); ++ ++ command_handler->ExecuteCommands(); ++} ++ ++void HeadlessCommandHandler::ExecuteCommands() { ++ // Expose DevTools protocol to the target. ++ base::Value::Dict params; ++ params.Set("targetId", devtools_client_.GetTargetId()); ++ browser_devtools_client_.SendCommand("Target.exposeDevToolsProtocol", ++ std::move(params)); ++ ++ // Set up Inspector domain. ++ devtools_client_.AddEventHandler( ++ "Inspector.targetCrashed", ++ base::BindRepeating(&HeadlessCommandHandler::OnTargetCrashed, ++ base::Unretained(this))); ++ devtools_client_.SendCommand("Inspector.enable"); ++} ++ ++void HeadlessCommandHandler::DocumentOnLoadCompletedInPrimaryMainFrame() { ++ base::Value::Dict commands; ++ if (!GetCommandDictAndOutputPaths(&commands, &pdf_file_path_, ++ &screenshot_file_path_) || ++ commands.empty()) { ++ Done(); ++ return; ++ } ++ ++ commands.Set("targetUrl", target_url_.spec()); ++ ++ std::string json_commands; ++ base::JSONWriter::Write(commands, &json_commands); ++ std::string script = "executeCommands(JSON.parse('" + json_commands + "'))"; ++ ++ base::Value::Dict params; ++ params.Set("expression", script); ++ params.Set("awaitPromise", true); ++ params.Set("returnByValue", true); ++ devtools_client_.SendCommand( ++ "Runtime.evaluate", std::move(params), ++ base::BindOnce(&HeadlessCommandHandler::OnCommandsResult, ++ base::Unretained(this))); ++} ++ ++void HeadlessCommandHandler::WebContentsDestroyed() { ++ CHECK(false); ++} ++ ++void HeadlessCommandHandler::OnTargetCrashed(const base::Value::Dict&) { ++ LOG(ERROR) << "Abnormal renderer termination."; ++ Done(); ++} ++ ++void HeadlessCommandHandler::OnCommandsResult(base::Value::Dict result) { ++ if (std::string* dom_dump = ++ result.FindStringByDottedPath("result.result.value.dumpDomResult")) { ++ std::cout << *dom_dump << std::endl; ++ } ++ ++ if (std::string* base64_data = result.FindStringByDottedPath( ++ "result.result.value.screenshotResult")) { ++ WriteFile(std::move(screenshot_file_path_), std::move(*base64_data)); ++ } ++ ++ if (std::string* base64_data = result.FindStringByDottedPath( ++ "result.result.value.printToPdfResult")) { ++ WriteFile(std::move(pdf_file_path_), std::move(*base64_data)); ++ } ++ ++ Done(); ++} ++ ++void HeadlessCommandHandler::Done() { ++ DCHECK(web_contents_); ++ devtools_client_.DetachClient(); ++ browser_devtools_client_.DetachClient(); ++ ++ DoneCallback done_callback(std::move(done_callback_)); ++ delete this; ++ std::move(done_callback).Run(); ++} ++ ++} // namespace headless +diff -uNr a/chromium/src/headless/app/headless_command_handler.h b/chromium/src/headless/app/headless_command_handler.h +--- a/chromium/src/headless/app/headless_command_handler.h 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/app/headless_command_handler.h 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,68 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#ifndef HEADLESS_APP_HEADLESS_COMMAND_HANDLER_H_ ++#define HEADLESS_APP_HEADLESS_COMMAND_HANDLER_H_ ++ ++#include "base/files/file_path.h" ++#include "base/functional/callback.h" ++#include "base/memory/raw_ptr.h" ++#include "base/values.h" ++#include "components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h" ++#include "content/public/browser/web_contents_observer.h" ++#include "url/gurl.h" ++ ++namespace content { ++class WebContents; ++} // namespace content ++ ++namespace headless { ++ ++class HeadlessCommandHandler : public content::WebContentsObserver { ++ public: ++ typedef base::OnceCallback DoneCallback; ++ ++ HeadlessCommandHandler(const HeadlessCommandHandler&) = delete; ++ HeadlessCommandHandler& operator=(const HeadlessCommandHandler&) = delete; ++ ++ static GURL GetHandlerUrl(); ++ ++ static void ProcessCommands(content::WebContents* web_contents, ++ GURL target_url, ++ DoneCallback done_callback); ++ ++ private: ++ using SimpleDevToolsProtocolClient = ++ simple_devtools_protocol_client::SimpleDevToolsProtocolClient; ++ ++ HeadlessCommandHandler(content::WebContents* web_contents, ++ GURL target_url, ++ DoneCallback done_callback); ++ ~HeadlessCommandHandler() override; ++ ++ void ExecuteCommands(); ++ ++ // content::WebContentsObserver implementation: ++ void DocumentOnLoadCompletedInPrimaryMainFrame() override; ++ void WebContentsDestroyed() override; ++ ++ void OnTargetCrashed(const base::Value::Dict&); ++ ++ void OnCommandsResult(base::Value::Dict result); ++ ++ void Done(); ++ ++ SimpleDevToolsProtocolClient devtools_client_; ++ SimpleDevToolsProtocolClient browser_devtools_client_; ++ raw_ptr web_contents_; ++ GURL target_url_; ++ DoneCallback done_callback_; ++ ++ base::FilePath pdf_file_path_; ++ base::FilePath screenshot_file_path_; ++}; ++ ++} // namespace headless ++ ++#endif // HEADLESS_APP_HEADLESS_COMMAND_HANDLER_H_ +diff -uNr a/chromium/src/headless/app/headless_command_switches.cc b/chromium/src/headless/app/headless_command_switches.cc +--- a/chromium/src/headless/app/headless_command_switches.cc 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/app/headless_command_switches.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,38 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include "headless/app/headless_command_switches.h" ++ ++namespace headless::switches { ++ ++// The background color to be used if the page doesn't specify one. Provided as ++// RGB or RGBA integer value in hex, e.g. 'ff0000ff' for red or '00000000' for ++// transparent. ++const char kDefaultBackgroundColor[] = "default-background-color"; ++ ++// Instructs headless_shell to print document.body.innerHTML to stdout. ++const char kDumpDom[] = "dump-dom"; ++ ++// Save a pdf file of the loaded page. ++const char kPrintToPDF[] = "print-to-pdf"; ++ ++// Do not display header and footer in the pdf file. ++const char kPrintToPDFNoHeader[] = "print-to-pdf-no-header"; ++ ++// Save a screenshot of the loaded page. ++const char kScreenshot[] = "screenshot"; ++ ++// Issues a stop after the specified number of milliseconds. This cancels all ++// navigation and causes the DOMContentLoaded event to fire. ++const char kTimeout[] = "timeout"; ++ ++// If set the system waits the specified number of virtual milliseconds before ++// deeming the page to be ready. For determinism virtual time does not advance ++// while there are pending network fetches (i.e no timers will fire). Once all ++// network fetches have completed, timers fire and if the system runs out of ++// virtual time is fastforwarded so the next timer fires immediately, until the ++// specified virtual time budget is exhausted. ++const char kVirtualTimeBudget[] = "virtual-time-budget"; ++ ++} // namespace headless::switches +diff -uNr a/chromium/src/headless/app/headless_command_switches.h b/chromium/src/headless/app/headless_command_switches.h +--- a/chromium/src/headless/app/headless_command_switches.h 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/app/headless_command_switches.h 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,22 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#ifndef HEADLESS_APP_HEADLESS_COMMAND_SWITCHES_H_ ++#define HEADLESS_APP_HEADLESS_COMMAND_SWITCHES_H_ ++ ++#include "headless/public/headless_export.h" ++ ++namespace headless::switches { ++ ++HEADLESS_EXPORT extern const char kDefaultBackgroundColor[]; ++HEADLESS_EXPORT extern const char kDumpDom[]; ++HEADLESS_EXPORT extern const char kPrintToPDF[]; ++HEADLESS_EXPORT extern const char kPrintToPDFNoHeader[]; ++HEADLESS_EXPORT extern const char kScreenshot[]; ++HEADLESS_EXPORT extern const char kTimeout[]; ++HEADLESS_EXPORT extern const char kVirtualTimeBudget[]; ++ ++} // namespace headless::switches ++ ++#endif // HEADLESS_APP_HEADLESS_COMMAND_SWITCHES_H_ +diff -uNr a/chromium/src/headless/app/headless_shell.cc b/chromium/src/headless/app/headless_shell.cc +--- a/chromium/src/headless/app/headless_shell.cc 2023-02-23 02:41:51.261326800 +0800 ++++ b/chromium/src/headless/app/headless_shell.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -2,49 +2,38 @@ + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. + +-#include ++#include "headless/app/headless_shell.h" ++ + #include +-#include +-#include + +-#include "base/base64.h" + #include "base/base_switches.h" + #include "base/bind.h" +-#include "base/callback.h" + #include "base/command_line.h" +-#include "base/containers/adapters.h" +-#include "base/containers/span.h" +-#include "base/files/file_path.h" + #include "base/files/file_util.h" + #include "base/i18n/rtl.h" +-#include "base/json/json_writer.h" +-#include "base/location.h" +-#include "base/numerics/safe_conversions.h" +-#include "base/path_service.h" +-#include "base/process/process.h" +-#include "base/strings/string_number_conversions.h" +-#include "base/strings/utf_string_conversions.h" + #include "base/task/thread_pool.h" + #include "build/branding_buildflags.h" + #include "build/build_config.h" + #include "content/public/app/content_main.h" +-#include "content/public/browser/browser_task_traits.h" +-#include "content/public/browser/browser_thread.h" +-#include "headless/app/headless_shell.h" ++#include "headless/app/headless_command_handler.h" + #include "headless/app/headless_shell_command_line.h" + #include "headless/app/headless_shell_switches.h" + #include "headless/lib/browser/headless_browser_impl.h" + #include "headless/lib/browser/headless_web_contents_impl.h" + #include "headless/lib/headless_content_main_delegate.h" +-#include "headless/public/headless_devtools_target.h" ++#include "headless/public/headless_browser.h" ++#include "headless/public/headless_browser_context.h" ++#include "headless/public/headless_web_contents.h" + #include "net/base/filename_util.h" + #include "net/http/http_util.h" ++#include "url/gurl.h" + + #if BUILDFLAG(IS_MAC) + #include "components/os_crypt/os_crypt_switches.h" // nogncheck + #endif + + #if BUILDFLAG(IS_WIN) ++#include "base/strings/utf_string_conversions.h" + #include "components/crash/core/app/crash_switches.h" // nogncheck + #include "components/crash/core/app/run_as_crashpad_handler_win.h" + #include "sandbox/win/src/sandbox_types.h" +@@ -54,6 +43,10 @@ + #include "headless/lib/browser/policy/headless_mode_policy.h" + #endif + ++#if defined(HEADLESS_ENABLE_COMMANDS) ++#include "headless/app/headless_command_handler.h" ++#endif ++ + namespace headless { + + namespace { +@@ -64,11 +57,6 @@ + const char kAboutBlank[] = "about:blank"; + #endif + +-// Default file name for screenshot. Can be overridden by "--screenshot" switch. +-const char kDefaultScreenshotFileName[] = "screenshot.png"; +-// Default file name for pdf. Can be overridden by "--print-to-pdf" switch. +-const char kDefaultPDFFileName[] = "output.pdf"; +- + GURL ConvertArgumentToURL(const base::CommandLine::StringType& arg) { + #if BUILDFLAG(IS_WIN) + GURL url(base::WideToUTF8(arg)); +@@ -82,32 +70,6 @@ + base::MakeAbsoluteFilePath(base::FilePath(arg))); + } + +-base::Value::Dict GetColorDictFromHexColor(const std::string& color_hex) { +- uint32_t color; +- CHECK(base::HexStringToUInt(color_hex, &color)) +- << "Expected a hex value for --default-background-color="; +- +- base::Value::Dict dict; +- dict.Set("r", static_cast((color & 0xff000000) >> 24)); +- dict.Set("g", static_cast((color & 0x00ff0000) >> 16)); +- dict.Set("b", static_cast((color & 0x0000ff00) >> 8)); +- dict.Set("a", static_cast((color & 0x000000ff))); +- +- return dict; +-} +- +-bool DoWriteFile(const base::FilePath& file_path, std::string file_data) { +- auto file_span = base::make_span( +- reinterpret_cast(file_data.data()), file_data.size()); +- bool success = base::WriteFile(file_path, file_span); +- PLOG_IF(ERROR, !success) << "Failed to write file " << file_path; +- if (!success) +- return false; +- +- LOG(INFO) << file_data.size() << " bytes written to file " << file_path; +- return true; +-} +- + } // namespace + + HeadlessShell::HeadlessShell() = default; +@@ -125,9 +87,6 @@ + } + #endif + +- file_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner( +- {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); +- + HeadlessBrowserContext::Builder context_builder = + browser_->CreateBrowserContextBuilder(); + +@@ -135,387 +94,70 @@ + // headless_content_main_delegate.cc in a way that is free of side-effects. + context_builder.SetAcceptLanguage(base::i18n::GetConfiguredLocale()); + ++ // Create browser context and set it as the default. The default browser ++ // context is used by the Target.createTarget() DevTools command when no other ++ // context is given. + browser_context_ = context_builder.Build(); + browser_->SetDefaultBrowserContext(browser_context_); + ++ // If no explicit URL is present navigate to about:blank unless we're being ++ // driven by a debugger. + base::CommandLine::StringVector args = + base::CommandLine::ForCurrentProcess()->GetArgs(); +- +- // If no explicit URL is present, navigate to about:blank, unless we're being +- // driven by a debugger. + if (args.empty() && !IsRemoteDebuggingEnabled()) + args.push_back(kAboutBlank); + +- if (!args.empty()) { +- file_task_runner_->PostTaskAndReplyWithResult( +- FROM_HERE, base::BindOnce(&ConvertArgumentToURL, args.front()), +- base::BindOnce(&HeadlessShell::OnCommandLineURL, +- weak_factory_.GetWeakPtr())); ++ if (args.empty()) { ++ return; + } +-} + +-void HeadlessShell::OnCommandLineURL(const GURL& url) { ++ GURL target_url = ConvertArgumentToURL(args.front()); ++ ++ // If driven by a debugger just open the target page and ++ // leave expecting the debugger will do what they need. ++ if (IsRemoteDebuggingEnabled()) { ++ HeadlessWebContents::Builder builder( ++ browser_context_->CreateWebContentsBuilder()); ++ HeadlessWebContents* web_contents = ++ builder.SetInitialURL(target_url).Build(); ++ if (!web_contents) { ++ LOG(ERROR) << "Navigation to " << target_url << " failed."; ++ ShutdownSoon(); ++ } ++ return; ++ } ++ ++ // Otherwise instantiate headless shell command handler that will ++ // execute the commands against the target page. ++#if defined(HEADLESS_ENABLE_COMMANDS) ++ GURL handler_url = HeadlessCommandHandler::GetHandlerUrl(); + HeadlessWebContents::Builder builder( + browser_context_->CreateWebContentsBuilder()); +- HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build(); ++ HeadlessWebContents* web_contents = ++ builder.SetInitialURL(handler_url).Build(); + if (!web_contents) { +- LOG(ERROR) << "Navigation to " << url << " failed"; +- browser_->Shutdown(); ++ LOG(ERROR) << "Navigation to " << handler_url << " failed."; ++ ShutdownSoon(); + return; + } + +- // Unless we're in remote debugging mode, associate target and +- // start observing it so we can run commands. +- if (!IsRemoteDebuggingEnabled()) { +- url_ = url; +- web_contents_ = web_contents; +- web_contents_->AddObserver(this); +- } +-} +- +-void HeadlessShell::Detach() { +- if (web_contents_) { +- devtools_client_.DetachClient(); +- web_contents_->RemoveObserver(this); +- web_contents_ = nullptr; +- } ++ HeadlessCommandHandler::ProcessCommands( ++ HeadlessWebContentsImpl::From(web_contents)->web_contents(), ++ std::move(target_url), ++ base::BindOnce(&HeadlessShell::ShutdownSoon, weak_factory_.GetWeakPtr())); ++#endif + } + + void HeadlessShell::ShutdownSoon() { +- if (shutdown_pending_) +- return; +- shutdown_pending_ = true; +- +- DCHECK(browser_); + browser_->BrowserMainThread()->PostTask( + FROM_HERE, + base::BindOnce(&HeadlessShell::Shutdown, weak_factory_.GetWeakPtr())); + } + + void HeadlessShell::Shutdown() { +- if (web_contents_) +- web_contents_->Close(); +- DCHECK(!web_contents_); +- + browser_->Shutdown(); + } + +-void HeadlessShell::DevToolsTargetReady() { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- +- devtools_client_.AttachToWebContents( +- HeadlessWebContentsImpl::From(web_contents_)->web_contents()); +- HeadlessDevToolsTarget* target = web_contents_->GetDevToolsTarget(); +- if (!target->IsAttached()) { +- LOG(ERROR) << "Could not attach DevTools target."; +- ShutdownSoon(); +- return; +- } +- +- devtools_client_.AddEventHandler( +- "Inspector.targetCrashed", +- base::BindRepeating(&HeadlessShell::OnTargetCrashed, +- weak_factory_.GetWeakPtr())); +- +- devtools_client_.AddEventHandler( +- "Page.loadEventFired", +- base::BindRepeating(&HeadlessShell::OnLoadEventFired, +- weak_factory_.GetWeakPtr())); +- devtools_client_.SendCommand("Page.enable"); +- +- devtools_client_.AddEventHandler( +- "Emulation.virtualTimeBudgetExpired", +- base::BindRepeating(&HeadlessShell::OnVirtualTimeBudgetExpired, +- weak_factory_.GetWeakPtr())); +- +- if (base::CommandLine::ForCurrentProcess()->HasSwitch( +- switches::kDefaultBackgroundColor)) { +- std::string color_hex = +- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( +- switches::kDefaultBackgroundColor); +- base::Value::Dict params; +- params.Set("color", GetColorDictFromHexColor(color_hex)); +- devtools_client_.SendCommand("Emulation.setDefaultBackgroundColorOverride", +- std::move(params)); +- } +- +- if (base::CommandLine::ForCurrentProcess()->HasSwitch( +- switches::kVirtualTimeBudget)) { +- std::string budget_ms_ascii = +- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( +- switches::kVirtualTimeBudget); +- int budget_ms; +- CHECK(base::StringToInt(budget_ms_ascii, &budget_ms)) +- << "Expected an integer value for --virtual-time-budget="; +- +- base::Value::Dict params; +- params.Set("budget", budget_ms); +- params.Set("policy", "pauseIfNetworkFetchesPending"); +- devtools_client_.SendCommand("Emulation.setVirtualTimePolicy", +- std::move(params)); +- } else { +- // Check if the document had already finished loading by the time we +- // attached. +- PollReadyState(); +- } +- +- if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) { +- std::string timeout_ms_ascii = +- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( +- switches::kTimeout); +- int timeout_ms; +- CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms)) +- << "Expected an integer value for --timeout="; +- browser_->BrowserMainThread()->PostDelayedTask( +- FROM_HERE, +- base::BindOnce(&HeadlessShell::FetchTimeout, +- weak_factory_.GetWeakPtr()), +- base::Milliseconds(timeout_ms)); +- } +-} +- +-void HeadlessShell::HeadlessWebContentsDestroyed() { +- // Detach now, but defer shutdown till the HeadlessWebContents +- // removal is complete. +- Detach(); +- ShutdownSoon(); +-} +- +-void HeadlessShell::FetchTimeout() { +- LOG(INFO) << "Timeout."; +- devtools_client_.SendCommand("Page.stopLoading"); +- // After calling page.stopLoading() the page will not fire any +- // life cycle events, so we have to proceed on our own. +- browser_->BrowserMainThread()->PostTask( +- FROM_HERE, +- base::BindOnce(&HeadlessShell::OnPageReady, weak_factory_.GetWeakPtr())); +-} +- +-void HeadlessShell::OnTargetCrashed(const base::Value::Dict&) { +- LOG(ERROR) << "Abnormal renderer termination."; +- // NB this never gets called if remote debugging is enabled. +- ShutdownSoon(); +-} +- +-void HeadlessShell::PollReadyState() { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- +- // We need to check the current location in addition to the ready state to +- // be sure the expected page is ready. +- base::Value::Dict params; +- params.Set("expression", +- "document.readyState + ' ' + document.location.href"); +- devtools_client_.SendCommand( +- "Runtime.evaluate", std::move(params), +- base::BindOnce(&HeadlessShell::OnEvaluateReadyStateResult, +- weak_factory_.GetWeakPtr())); +-} +- +-void HeadlessShell::OnEvaluateReadyStateResult(base::Value::Dict result) { +- const std::string* result_value = +- result.FindStringByDottedPath("result.result.value"); +- if (!result_value) +- return; +- +- std::stringstream stream(*result_value); +- std::string ready_state; +- std::string url; +- stream >> ready_state; +- stream >> url; +- +- if (ready_state == "complete" && +- (url_.spec() == url || url != "about:blank")) { +- OnPageReady(); +- return; +- } +-} +- +-void HeadlessShell::OnVirtualTimeBudgetExpired(const base::Value::Dict&) { +- OnPageReady(); +-} +- +-void HeadlessShell::OnLoadEventFired(const base::Value::Dict&) { +- if (base::CommandLine::ForCurrentProcess()->HasSwitch( +- switches::kVirtualTimeBudget)) { +- return; +- } +- OnPageReady(); +-} +- +-void HeadlessShell::OnPageReady() { +- if (processed_page_ready_) +- return; +- processed_page_ready_ = true; +- +- if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) { +- FetchDom(); +- } else if (base::CommandLine::ForCurrentProcess()->HasSwitch( +- switches::kRepl)) { +- LOG(INFO) +- << "Type a Javascript expression to evaluate or \"quit\" to exit."; +- InputExpression(); +- } else if (base::CommandLine::ForCurrentProcess()->HasSwitch( +- switches::kScreenshot)) { +- CaptureScreenshot(); +- } else if (base::CommandLine::ForCurrentProcess()->HasSwitch( +- switches::kPrintToPDF)) { +- PrintToPDF(); +- } else { +- ShutdownSoon(); +- } +-} +- +-void HeadlessShell::FetchDom() { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- +- base::Value::Dict params; +- params.Set( +- "expression", +- "(document.doctype ? new " +- "XMLSerializer().serializeToString(document.doctype) + '\\n' : '') + " +- "document.documentElement.outerHTML"); +- devtools_client_.SendCommand( +- "Runtime.evaluate", std::move(params), +- base::BindOnce(&HeadlessShell::OnEvaluateFetchDomResult, +- weak_factory_.GetWeakPtr())); +-} +- +-void HeadlessShell::OnEvaluateFetchDomResult(base::Value::Dict result) { +- if (const base::Value::Dict* result_exception_details = +- result.FindDictByDottedPath("result.exceptionDetails")) { +- LOG(ERROR) << "Failed to serialize document:\n" +- << *result_exception_details->FindStringByDottedPath( +- "exception.description"); +- } else if (const std::string* result_value = +- result.FindStringByDottedPath("result.result.value")) { +- printf("%s\n", result_value->c_str()); +- } +- +- ShutdownSoon(); +-} +- +-void HeadlessShell::InputExpression() { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- +- // Note that a real system should read user input asynchronously, because +- // otherwise all other browser activity is suspended (e.g., page loading). +- printf(">>> "); +- std::stringstream expression; +- while (true) { +- int c = fgetc(stdin); +- if (c == '\n') +- break; +- if (c == EOF) { +- // If there's no expression, then quit. +- if (expression.str().size() == 0) { +- printf("\n"); +- ShutdownSoon(); +- return; +- } +- break; +- } +- expression << static_cast(c); +- } +- if (expression.str() == "quit") { +- ShutdownSoon(); +- return; +- } +- +- base::Value::Dict params; +- params.Set("expression", expression.str()); +- devtools_client_.SendCommand( +- "Runtime.evaluate", std::move(params), +- base::BindOnce(&HeadlessShell::OnEvaluateExpressionResult, +- weak_factory_.GetWeakPtr())); +-} +- +-void HeadlessShell::OnEvaluateExpressionResult(base::Value::Dict result) { +- std::string result_json; +- base::JSONWriter::Write(result, &result_json); +- printf("%s\n", result_json.c_str()); +- +- InputExpression(); +-} +- +-void HeadlessShell::CaptureScreenshot() { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- +- devtools_client_.SendCommand( +- "Page.captureScreenshot", +- base::BindOnce(&HeadlessShell::OnCaptureScreenshotResult, +- weak_factory_.GetWeakPtr())); +-} +- +-void HeadlessShell::OnCaptureScreenshotResult(base::Value::Dict result) { +- const std::string* result_data = result.FindStringByDottedPath("result.data"); +- if (!result_data) { +- LOG(ERROR) << "Capture screenshot failed"; +- ShutdownSoon(); +- return; +- } +- +- std::string data; +- if (!base::Base64Decode(*result_data, &data)) { +- LOG(ERROR) << "Invalid screenshot data"; +- return; +- } +- +- WriteFile(switches::kScreenshot, kDefaultScreenshotFileName, std::move(data)); +-} +- +-void HeadlessShell::PrintToPDF() { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- +- base::Value::Dict params; +- params.Set("printBackground", true); +- params.Set("preferCSSPageSize", true); +- if (base::CommandLine::ForCurrentProcess()->HasSwitch( +- switches::kPrintToPDFNoHeader)) { +- params.Set("displayHeaderFooter", false); +- } +- devtools_client_.SendCommand("Page.printToPDF", std::move(params), +- base::BindOnce(&HeadlessShell::OnPrintToPDFDone, +- weak_factory_.GetWeakPtr())); +-} +- +-void HeadlessShell::OnPrintToPDFDone(base::Value::Dict result) { +- const std::string* result_data = result.FindStringByDottedPath("result.data"); +- if (!result_data) { +- LOG(ERROR) << "Print to PDF failed"; +- ShutdownSoon(); +- return; +- } +- +- std::string data; +- if (!base::Base64Decode(*result_data, &data)) { +- LOG(ERROR) << "Invalid PDF data"; +- return; +- } +- +- WriteFile(switches::kPrintToPDF, kDefaultPDFFileName, std::move(data)); +-} +- +-void HeadlessShell::WriteFile(const std::string& file_path_switch, +- const std::string& default_file_name, +- std::string data) { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- +- base::FilePath file_name = +- base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( +- file_path_switch); +- if (file_name.empty()) +- file_name = base::FilePath().AppendASCII(default_file_name); +- +- file_task_runner_->PostTaskAndReplyWithResult( +- FROM_HERE, base::BindOnce(&DoWriteFile, file_name, std::move(data)), +- base::BindOnce(&HeadlessShell::OnWriteFileDone, +- weak_factory_.GetWeakPtr())); +-} +- +-void HeadlessShell::OnWriteFileDone(bool success) { +- ShutdownSoon(); +-} +- + #if BUILDFLAG(IS_WIN) + int HeadlessShellMain(HINSTANCE instance, + sandbox::SandboxInterfaceInfo* sandbox_info) { +diff -uNr a/chromium/src/headless/app/headless_shell.h b/chromium/src/headless/app/headless_shell.h +--- a/chromium/src/headless/app/headless_shell.h 2023-02-23 02:41:51.261326800 +0800 ++++ b/chromium/src/headless/app/headless_shell.h 2023-08-04 12:43:55.229936729 +0800 +@@ -5,84 +5,32 @@ + #ifndef HEADLESS_APP_HEADLESS_SHELL_H_ + #define HEADLESS_APP_HEADLESS_SHELL_H_ + +-#include +-#include +- + #include "base/memory/raw_ptr.h" +-#include "base/memory/scoped_refptr.h" + #include "base/memory/weak_ptr.h" +-#include "base/task/sequenced_task_runner.h" +-#include "base/values.h" +-#include "components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h" +-#include "headless/public/headless_browser.h" +-#include "headless/public/headless_web_contents.h" +-#include "url/gurl.h" +- +-class GURL; + + namespace headless { + ++class HeadlessBrowser; ++class HeadlessBrowserContext; ++ + // An application which implements a simple headless browser. +-class HeadlessShell : public HeadlessWebContents::Observer { ++class HeadlessShell { + public: + HeadlessShell(); + + HeadlessShell(const HeadlessShell&) = delete; + HeadlessShell& operator=(const HeadlessShell&) = delete; + +- ~HeadlessShell() override; ++ ~HeadlessShell(); + + void OnBrowserStart(HeadlessBrowser* browser); + + private: +- // HeadlessWebContents::Observer implementation: +- void DevToolsTargetReady() override; +- void HeadlessWebContentsDestroyed() override; +- +- void OnTargetCrashed(const base::Value::Dict&); +- void OnLoadEventFired(const base::Value::Dict&); +- void OnVirtualTimeBudgetExpired(const base::Value::Dict&); +- +- void Detach(); + void ShutdownSoon(); + void Shutdown(); + +- void FetchTimeout(); +- +- void OnCommandLineURL(const GURL& url); +- +- void PollReadyState(); +- +- void OnEvaluateReadyStateResult(base::Value::Dict result); +- +- void OnPageReady(); +- +- void FetchDom(); +- void OnEvaluateFetchDomResult(base::Value::Dict result); +- +- void InputExpression(); +- void OnEvaluateExpressionResult(base::Value::Dict result); +- +- void CaptureScreenshot(); +- void OnCaptureScreenshotResult(base::Value::Dict result); +- +- void PrintToPDF(); +- void OnPrintToPDFDone(base::Value::Dict result); +- +- void WriteFile(const std::string& file_path_switch, +- const std::string& default_file_name, +- std::string data); +- void OnWriteFileDone(bool success); +- +- GURL url_; + raw_ptr browser_ = nullptr; // Not owned. +- simple_devtools_protocol_client::SimpleDevToolsProtocolClient +- devtools_client_; +- raw_ptr web_contents_ = nullptr; + raw_ptr browser_context_ = nullptr; +- scoped_refptr file_task_runner_; +- bool processed_page_ready_ = false; +- bool shutdown_pending_ = false; + + base::WeakPtrFactory weak_factory_{this}; + }; +diff -uNr a/chromium/src/headless/app/headless_shell_command_line.cc b/chromium/src/headless/app/headless_shell_command_line.cc +--- a/chromium/src/headless/app/headless_shell_command_line.cc 2023-02-23 02:41:51.261326800 +0800 ++++ b/chromium/src/headless/app/headless_shell_command_line.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -6,8 +6,8 @@ + + #include + +-#include "base/bind.h" + #include "base/logging.h" ++#include "build/build_config.h" + #include "cc/base/switches.h" + #include "components/viz/common/switches.h" + #include "content/public/common/content_switches.h" +@@ -20,13 +20,19 @@ + #include "ui/gfx/font_render_params.h" + #include "ui/gfx/geometry/size.h" + ++#if defined(HEADLESS_ENABLE_COMMANDS) ++#include "headless/app/headless_command_switches.h" ++#endif ++ + namespace headless { + + namespace { ++ + // By default listen to incoming DevTools connections on localhost. + const char kLocalHost[] = "localhost"; + + bool ValidateCommandLineSwitches(const base::CommandLine& command_line) { ++#if defined(HEADLESS_ENABLE_COMMANDS) + if (command_line.HasSwitch(switches::kRemoteDebuggingPort) || + command_line.HasSwitch(switches::kRemoteDebuggingPipe)) { + static const char* kIncompatibleSwitches[] = { +@@ -47,6 +53,7 @@ + } + } + } ++#endif // defined(HEADLESS_ENABLE_COMMANDS) + + return true; + } +diff -uNr a/chromium/src/headless/app/headless_shell_switches.cc b/chromium/src/headless/app/headless_shell_switches.cc +--- a/chromium/src/headless/app/headless_shell_switches.cc 2023-02-23 02:41:51.261326800 +0800 ++++ b/chromium/src/headless/app/headless_shell_switches.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -7,11 +7,6 @@ + namespace headless { + namespace switches { + +-// The background color to be used if the page doesn't specify one. Provided as +-// RGBA integer value in hex, e.g. 'ff0000ff' for red or '00000000' for +-// transparent. +-const char kDefaultBackgroundColor[] = "default-background-color"; +- + // Whether cookies stored as part of user profile are encrypted. + const char kDisableCookieEncryption[] = "disable-cookie-encryption"; + +@@ -38,9 +33,6 @@ + // UserDatadir. + const char kDiskCacheDir[] = "disk-cache-dir"; + +-// Instructs headless_shell to print document.body.innerHTML to stdout. +-const char kDumpDom[] = "dump-dom"; +- + // Specifies which encryption storage backend to use. Possible values are + // kwallet, kwallet5, gnome, gnome-keyring, gnome-libsecret, basic. Any other + // value will lead to Chrome detecting the best backend automatically. +@@ -51,12 +43,6 @@ + // or KWallets. + const char kPasswordStore[] = "password-store"; + +-// Save a pdf file of the loaded page. +-const char kPrintToPDF[] = "print-to-pdf"; +- +-// Do not display header and footer in the pdf file. +-const char kPrintToPDFNoHeader[] = "print-to-pdf-no-header"; +- + // Do not emit tags when printing PDFs. + const char kDisablePDFTagging[] = "disable-pdf-tagging"; + +@@ -83,13 +69,6 @@ + // expressions. + const char kRepl[] = "repl"; + +-// Save a screenshot of the loaded page. +-const char kScreenshot[] = "screenshot"; +- +-// Issues a stop after the specified number of milliseconds. This cancels all +-// navigation and causes the DOMContentLoaded event to fire. +-const char kTimeout[] = "timeout"; +- + // Sets the GL implementation to use. Use a blank string to disable GL + // rendering. + const char kUseGL[] = "use-gl"; +@@ -110,14 +89,6 @@ + // --user-data-dir switch. + const char kIncognito[] = "incognito"; + +-// If set the system waits the specified number of virtual milliseconds before +-// deeming the page to be ready. For determinism virtual time does not advance +-// while there are pending network fetches (i.e no timers will fire). Once all +-// network fetches have completed, timers fire and if the system runs out of +-// virtual time is fastforwarded so the next timer fires immediatley, until the +-// specified virtual time budget is exhausted. +-const char kVirtualTimeBudget[] = "virtual-time-budget"; +- + // Sets the initial window size. Provided as string in the format "800,600". + const char kWindowSize[] = "window-size"; + +diff -uNr a/chromium/src/headless/app/headless_shell_switches.h b/chromium/src/headless/app/headless_shell_switches.h +--- a/chromium/src/headless/app/headless_shell_switches.h 2023-02-23 02:41:51.261326800 +0800 ++++ b/chromium/src/headless/app/headless_shell_switches.h 2023-08-04 12:43:55.229936729 +0800 +@@ -12,31 +12,24 @@ + namespace switches { + + HEADLESS_EXPORT extern const char kCrashDumpsDir[]; +-HEADLESS_EXPORT extern const char kDefaultBackgroundColor[]; + HEADLESS_EXPORT extern const char kDeterministicMode[]; + HEADLESS_EXPORT extern const char kDisableCookieEncryption[]; + HEADLESS_EXPORT extern const char kDisableCrashReporter[]; + HEADLESS_EXPORT extern const char kDiskCacheDir[]; +-HEADLESS_EXPORT extern const char kDumpDom[]; + HEADLESS_EXPORT extern const char kEnableBeginFrameControl[]; + HEADLESS_EXPORT extern const char kEnableCrashReporter[]; + HEADLESS_EXPORT extern const char kPasswordStore[]; +-HEADLESS_EXPORT extern const char kPrintToPDF[]; +-HEADLESS_EXPORT extern const char kPrintToPDFNoHeader[]; + HEADLESS_EXPORT extern const char kDisablePDFTagging[]; + HEADLESS_EXPORT extern const char kProxyBypassList[]; + HEADLESS_EXPORT extern const char kProxyServer[]; + HEADLESS_EXPORT extern const char kNoSystemProxyConfigService[]; + HEADLESS_EXPORT extern const char kRemoteDebuggingAddress[]; + HEADLESS_EXPORT extern const char kRepl[]; +-HEADLESS_EXPORT extern const char kScreenshot[]; +-HEADLESS_EXPORT extern const char kTimeout[]; + HEADLESS_EXPORT extern const char kUseANGLE[]; + HEADLESS_EXPORT extern const char kUseGL[]; + HEADLESS_EXPORT extern const char kUserAgent[]; + HEADLESS_EXPORT extern const char kUserDataDir[]; + HEADLESS_EXPORT extern const char kIncognito[]; +-HEADLESS_EXPORT extern const char kVirtualTimeBudget[]; + HEADLESS_EXPORT extern const char kWindowSize[]; + HEADLESS_EXPORT extern const char kAuthServerAllowlist[]; + HEADLESS_EXPORT extern const char kFontRenderHinting[]; +diff -uNr a/chromium/src/headless/headless.gni b/chromium/src/headless/headless.gni +--- a/chromium/src/headless/headless.gni 2023-02-23 02:41:51.261326800 +0800 ++++ b/chromium/src/headless/headless.gni 2023-08-04 12:43:55.229936729 +0800 +@@ -6,6 +6,10 @@ + # Embed resource.pak file into the binary for easier distribution. + headless_use_embedded_resources = false + ++ # Enable support for --screenshot, --print-to-pdf and --dump-dom commands ++ # Note: this option is not available if |headless_use_embedded_resources|. ++ headless_enable_commands = true ++ + # Use Prefs component to access Local State and other preferences. + headless_use_prefs = true + +diff -uNr a/chromium/src/headless/test/capture_std_stream.cc b/chromium/src/headless/test/capture_std_stream.cc +--- a/chromium/src/headless/test/capture_std_stream.cc 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/test/capture_std_stream.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,88 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include "headless/test/capture_std_stream.h" ++ ++#include ++#include ++ ++#include "base/check_op.h" ++#include "build/build_config.h" ++ ++#if BUILDFLAG(IS_WIN) ++#include ++#else ++#include ++#endif ++ ++namespace headless { ++ ++namespace { ++enum { kReadPipe, kWritePipe }; ++static constexpr char kPipeEnd = '\xff'; ++} // namespace ++ ++CaptureStdStream::CaptureStdStream(FILE* stream) : stream_(stream) { ++#if BUILDFLAG(IS_WIN) ++ CHECK_EQ(_pipe(pipes_, 4096, O_BINARY), 0); ++#else ++ CHECK_EQ(pipe(pipes_), 0); ++#endif ++ fileno_ = dup(fileno(stream_)); ++ CHECK_NE(fileno_, -1); ++} ++ ++CaptureStdStream::~CaptureStdStream() { ++ StopCapture(); ++ close(pipes_[kReadPipe]); ++ close(pipes_[kWritePipe]); ++ close(fileno_); ++} ++ ++void CaptureStdStream::StartCapture() { ++ if (capturing_) { ++ return; ++ } ++ ++ fflush(stream_); ++ CHECK_NE(dup2(pipes_[kWritePipe], fileno(stream_)), -1); ++ ++ capturing_ = true; ++} ++ ++void CaptureStdStream::StopCapture() { ++ if (!capturing_) { ++ return; ++ } ++ ++ char eop = kPipeEnd; ++ CHECK_NE(write(pipes_[kWritePipe], &eop, sizeof(eop)), -1); ++ ++ fflush(stream_); ++ CHECK_NE(dup2(fileno_, fileno(stream_)), -1); ++ ++ capturing_ = false; ++} ++ ++std::string CaptureStdStream::TakeCapturedData() { ++ CHECK(!capturing_); ++ ++ std::string captured_data; ++ for (;;) { ++ constexpr size_t kChunkSize = 256; ++ char buffer[kChunkSize]; ++ int bytes_read = read(pipes_[kReadPipe], buffer, kChunkSize); ++ CHECK_GT(bytes_read, 0); ++ if (buffer[bytes_read - 1] != kPipeEnd) { ++ captured_data.append(buffer, bytes_read); ++ } else { ++ captured_data.append(buffer, bytes_read - 1); ++ break; ++ } ++ } ++ ++ return captured_data; ++} ++ ++} // namespace headless +diff -uNr a/chromium/src/headless/test/capture_std_stream.h b/chromium/src/headless/test/capture_std_stream.h +--- a/chromium/src/headless/test/capture_std_stream.h 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/test/capture_std_stream.h 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,48 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#ifndef HEADLESS_TEST_CAPTURE_STD_STREAM_H_ ++#define HEADLESS_TEST_CAPTURE_STD_STREAM_H_ ++ ++#include ++#include ++ ++#include "base/threading/thread_restrictions.h" ++ ++namespace headless { ++ ++// A class to capture data sent to a standard stream. ++class CaptureStdStream { ++ public: ++ explicit CaptureStdStream(FILE* stream); ++ ~CaptureStdStream(); ++ ++ void StartCapture(); ++ void StopCapture(); ++ ++ std::string TakeCapturedData(); ++ ++ private: ++ FILE* stream_; ++ ++ int fileno_ = -1; ++ int pipes_[2] = {-1, -1}; ++ bool capturing_ = false; ++ ++ base::ScopedAllowBlockingForTesting allow_blocking_calls_; ++}; ++ ++class CaptureStdOut : public CaptureStdStream { ++ public: ++ CaptureStdOut() : CaptureStdStream(stdout) {} ++}; ++ ++class CaptureStdErr : public CaptureStdStream { ++ public: ++ CaptureStdErr() : CaptureStdStream(stderr) {} ++}; ++ ++} // namespace headless ++ ++#endif // HEADLESS_TEST_CAPTURE_STD_STREAM_H_ +diff -uNr a/chromium/src/headless/test/data/centered_blue_box.html b/chromium/src/headless/test/data/centered_blue_box.html +--- a/chromium/src/headless/test/data/centered_blue_box.html 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/test/data/centered_blue_box.html 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,14 @@ ++ ++ ++ ++ +\ No newline at end of file +diff -uNr a/chromium/src/headless/test/data/stepper_page.html b/chromium/src/headless/test/data/stepper_page.html +--- a/chromium/src/headless/test/data/stepper_page.html 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/test/data/stepper_page.html 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,13 @@ ++ ++ ++
++ ++ ++ +diff -uNr a/chromium/src/headless/test/headless_command_browsertest.cc b/chromium/src/headless/test/headless_command_browsertest.cc +--- a/chromium/src/headless/test/headless_command_browsertest.cc 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/test/headless_command_browsertest.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,333 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include ++#include ++ ++#include "base/bind.h" ++#include "base/command_line.h" ++#include "base/files/file_path.h" ++#include "base/files/file_util.h" ++#include "base/files/scoped_temp_dir.h" ++#include "base/threading/thread_restrictions.h" ++#include "build/build_config.h" ++#include "content/public/test/browser_test.h" ++#include "headless/app/headless_command_handler.h" ++#include "headless/app/headless_command_switches.h" ++#include "headless/lib/browser/headless_browser_context_impl.h" ++#include "headless/lib/browser/headless_browser_impl.h" ++#include "headless/lib/browser/headless_web_contents_impl.h" ++#include "headless/public/headless_browser.h" ++#include "headless/public/headless_browser_context.h" ++#include "headless/public/headless_web_contents.h" ++#include "headless/test/capture_std_stream.h" ++#include "headless/test/headless_browser_test.h" ++#include "headless/test/headless_browser_test_utils.h" ++#include "net/test/embedded_test_server/embedded_test_server.h" ++#include "pdf/buildflags.h" ++#include "printing/buildflags/buildflags.h" ++#include "testing/gtest/include/gtest/gtest.h" ++#include "third_party/skia/include/core/SkBitmap.h" ++#include "third_party/skia/include/core/SkColor.h" ++#include "ui/gfx/codec/png_codec.h" ++#include "ui/gfx/geometry/point.h" ++#include "ui/gfx/geometry/rect.h" ++#include "url/gurl.h" ++ ++#if BUILDFLAG(ENABLE_PRINTING) && BUILDFLAG(ENABLE_PDF) ++#include "headless/test/pdf_utils.h" ++#endif ++ ++namespace headless { ++ ++namespace { ++bool DecodePNG(const std::string& png_data, SkBitmap* bitmap) { ++ return gfx::PNGCodec::Decode( ++ reinterpret_cast(png_data.data()), png_data.size(), ++ bitmap); ++} ++} // namespace ++ ++class HeadlessCommandBrowserTest : public HeadlessBrowserTest { ++ public: ++ HeadlessCommandBrowserTest() = default; ++ ++ void RunTest() { ++ ASSERT_TRUE(embedded_test_server()->Start()); ++ ++ HeadlessBrowserContext::Builder context_builder = ++ browser()->CreateBrowserContextBuilder(); ++ HeadlessBrowserContext* browser_context = context_builder.Build(); ++ browser()->SetDefaultBrowserContext(browser_context); ++ ++ GURL handler_url = HeadlessCommandHandler::GetHandlerUrl(); ++ HeadlessWebContents::Builder builder( ++ browser_context->CreateWebContentsBuilder()); ++ HeadlessWebContents* web_contents = ++ builder.SetInitialURL(handler_url).Build(); ++ ++ HeadlessCommandHandler::ProcessCommands( ++ HeadlessWebContentsImpl::From(web_contents)->web_contents(), ++ GetTargetUrl(), ++ base::BindOnce(&HeadlessCommandBrowserTest::FinishTest, ++ base::Unretained(this))); ++ ++ RunAsynchronousTest(); ++ ++ web_contents->Close(); ++ browser_context->Close(); ++ base::RunLoop().RunUntilIdle(); ++ } ++ ++ private: ++ virtual GURL GetTargetUrl() = 0; ++ ++ void FinishTest() { FinishAsynchronousTest(); } ++}; ++ ++class HeadlessDumpDomCommandBrowserTest : public HeadlessCommandBrowserTest { ++ public: ++ HeadlessDumpDomCommandBrowserTest() = default; ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ HeadlessCommandBrowserTest::SetUpCommandLine(command_line); ++ command_line->AppendSwitch(switches::kDumpDom); ++ } ++ ++ GURL GetTargetUrl() override { ++ return embedded_test_server()->GetURL("/hello.html"); ++ } ++}; ++ ++IN_PROC_BROWSER_TEST_F(HeadlessDumpDomCommandBrowserTest, DumpDom) { ++ base::ScopedAllowBlockingForTesting allow_blocking; ++ ++ CaptureStdOut capture_stdout; ++ capture_stdout.StartCapture(); ++ RunTest(); ++ capture_stdout.StopCapture(); ++ ++ std::string captured_stdout = capture_stdout.TakeCapturedData(); ++ ++ static const char kDomDump[] = ++ "\n" ++ "

Hello headless world!

\n" ++ "\n"; ++ EXPECT_THAT(captured_stdout, testing::HasSubstr(kDomDump)); ++} ++ ++class HeadlessDumpDomVirtualTimeBudgetCommandBrowserTest ++ : public HeadlessDumpDomCommandBrowserTest { ++ public: ++ HeadlessDumpDomVirtualTimeBudgetCommandBrowserTest() = default; ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ HeadlessDumpDomCommandBrowserTest::SetUpCommandLine(command_line); ++ command_line->AppendSwitchASCII(switches::kVirtualTimeBudget, "5500"); ++ } ++ ++ GURL GetTargetUrl() override { ++ return embedded_test_server()->GetURL("/stepper_page.html"); ++ } ++}; ++ ++IN_PROC_BROWSER_TEST_F(HeadlessDumpDomVirtualTimeBudgetCommandBrowserTest, ++ DumpDomVirtualTimeBudget) { ++ base::ScopedAllowBlockingForTesting allow_blocking; ++ ++ CaptureStdOut capture_stdout; ++ capture_stdout.StartCapture(); ++ RunTest(); ++ capture_stdout.StopCapture(); ++ ++ std::vector captured_lines = ++ base::SplitString(capture_stdout.TakeCapturedData(), "\n", ++ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); ++ ++ EXPECT_THAT(captured_lines, testing::Contains(R"(
5
)")); ++} ++ ++class HeadlessFileCommandBrowserTest : public HeadlessCommandBrowserTest { ++ public: ++ HeadlessFileCommandBrowserTest() = default; ++ ++ void SetUp() override { ++ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ++ ASSERT_TRUE(base::IsDirectoryEmpty(temp_dir())); ++ ++ HeadlessCommandBrowserTest::SetUp(); ++ } ++ ++ void TearDown() override { ++ HeadlessCommandBrowserTest::TearDown(); ++ ++ ASSERT_TRUE(temp_dir_.Delete()); ++ } ++ ++ const base::FilePath& temp_dir() const { return temp_dir_.GetPath(); } ++ ++ base::ScopedTempDir temp_dir_; ++}; ++ ++class HeadlessScreenshotCommandBrowserTest ++ : public HeadlessFileCommandBrowserTest { ++ public: ++ HeadlessScreenshotCommandBrowserTest() = default; ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ HeadlessFileCommandBrowserTest::SetUpCommandLine(command_line); ++ ++ screenshot_filename_ = ++ temp_dir().Append(FILE_PATH_LITERAL("screenshot.png")); ++ command_line->AppendSwitchPath(switches::kScreenshot, screenshot_filename_); ++ } ++ ++ GURL GetTargetUrl() override { ++ return embedded_test_server()->GetURL("/centered_blue_box.html"); ++ } ++ ++ base::FilePath screenshot_filename_; ++}; ++ ++IN_PROC_BROWSER_TEST_F(HeadlessScreenshotCommandBrowserTest, Screenshot) { ++ base::ScopedAllowBlockingForTesting allow_blocking; ++ ++ RunTest(); ++ ++ ASSERT_TRUE(base::PathExists(screenshot_filename_)) << screenshot_filename_; ++ ++ std::string png_data; ++ ASSERT_TRUE(base::ReadFileToString(screenshot_filename_, &png_data)) ++ << screenshot_filename_; ++ ++ SkBitmap bitmap; ++ ASSERT_TRUE(DecodePNG(png_data, &bitmap)); ++ ++ ASSERT_EQ(800, bitmap.width()); ++ ASSERT_EQ(600, bitmap.height()); ++ ++ // Expect white background and a centered blue rectangle. ++ EXPECT_EQ(SkColorSetRGB(0xff, 0xff, 0xff), bitmap.getColor(1, 1)); ++ EXPECT_EQ(SkColorSetRGB(0xff, 0xff, 0xff), bitmap.getColor(800 - 1, 600 - 1)); ++ EXPECT_EQ(SkColorSetRGB(0x00, 0x00, 0xff), bitmap.getColor(800 / 2, 1)); ++} ++ ++class HeadlessScreenshotWithBackgroundCommandBrowserTest ++ : public HeadlessScreenshotCommandBrowserTest { ++ public: ++ HeadlessScreenshotWithBackgroundCommandBrowserTest() = default; ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ HeadlessScreenshotCommandBrowserTest::SetUpCommandLine(command_line); ++ ++ command_line->AppendSwitchASCII(switches::kDefaultBackgroundColor, ++ "ff0000"); ++ } ++}; ++ ++IN_PROC_BROWSER_TEST_F(HeadlessScreenshotWithBackgroundCommandBrowserTest, ++ ScreenshotBackground) { ++ base::ScopedAllowBlockingForTesting allow_blocking; ++ ++ RunTest(); ++ ++ ASSERT_TRUE(base::PathExists(screenshot_filename_)) << screenshot_filename_; ++ ++ std::string png_data; ++ ASSERT_TRUE(base::ReadFileToString(screenshot_filename_, &png_data)) ++ << screenshot_filename_; ++ ++ SkBitmap bitmap; ++ ASSERT_TRUE(DecodePNG(png_data, &bitmap)); ++ ++ ASSERT_EQ(800, bitmap.width()); ++ ASSERT_EQ(600, bitmap.height()); ++ ++ // Expect red background and a centered blue rectangle. ++ EXPECT_EQ(SkColorSetRGB(0xff, 0x00, 0x00), bitmap.getColor(1, 1)); ++ EXPECT_EQ(SkColorSetRGB(0xff, 0x00, 0x00), bitmap.getColor(800 - 1, 600 - 1)); ++ EXPECT_EQ(SkColorSetRGB(0x00, 0x00, 0xff), bitmap.getColor(800 / 2, 1)); ++} ++ ++#if BUILDFLAG(ENABLE_PRINTING) && BUILDFLAG(ENABLE_PDF) ++ ++class HeadlessPrintToPdfCommandBrowserTest ++ : public HeadlessFileCommandBrowserTest { ++ public: ++ static constexpr float kPageMarginsInInches = ++ 0.393701; // See Page.PrintToPDF specs. ++ ++ HeadlessPrintToPdfCommandBrowserTest() = default; ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ HeadlessFileCommandBrowserTest::SetUpCommandLine(command_line); ++ print_to_pdf_filename_ = ++ temp_dir().Append(FILE_PATH_LITERAL("print_to.pdf")); ++ command_line->AppendSwitchPath(switches::kPrintToPDF, ++ print_to_pdf_filename_); ++ command_line->AppendSwitch(switches::kPrintToPDFNoHeader); ++ } ++ ++ GURL GetTargetUrl() override { ++ return embedded_test_server()->GetURL("/centered_blue_box.html"); ++ } ++ ++ base::FilePath print_to_pdf_filename_; ++}; ++ ++IN_PROC_BROWSER_TEST_F(HeadlessPrintToPdfCommandBrowserTest, PrintToPdf) { ++ base::ScopedAllowBlockingForTesting allow_blocking; ++ ++ RunTest(); ++ ++ ASSERT_TRUE(base::PathExists(print_to_pdf_filename_)) ++ << print_to_pdf_filename_; ++ ++ std::string pdf_data; ++ ASSERT_TRUE(base::ReadFileToString(print_to_pdf_filename_, &pdf_data)) ++ << print_to_pdf_filename_; ++ ++ PDFPageBitmap page_bitmap; ++ ASSERT_TRUE(page_bitmap.Render(pdf_data, 0)); ++ ++ // Expect blue rectangle on white background. ++ EXPECT_TRUE(page_bitmap.CheckRect(0x0000ff, 0xffffff)); ++} ++ ++class HeadlessPrintToPdfWithBackgroundCommandBrowserTest ++ : public HeadlessPrintToPdfCommandBrowserTest { ++ public: ++ HeadlessPrintToPdfWithBackgroundCommandBrowserTest() = default; ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ HeadlessPrintToPdfCommandBrowserTest::SetUpCommandLine(command_line); ++ ++ command_line->AppendSwitchASCII(switches::kDefaultBackgroundColor, ++ "ff0000"); ++ } ++}; ++ ++IN_PROC_BROWSER_TEST_F(HeadlessPrintToPdfWithBackgroundCommandBrowserTest, ++ PrintToPdfBackground) { ++ base::ScopedAllowBlockingForTesting allow_blocking; ++ ++ RunTest(); ++ ++ ASSERT_TRUE(base::PathExists(print_to_pdf_filename_)) ++ << print_to_pdf_filename_; ++ ++ std::string pdf_data; ++ ASSERT_TRUE(base::ReadFileToString(print_to_pdf_filename_, &pdf_data)) ++ << print_to_pdf_filename_; ++ ++ PDFPageBitmap page_bitmap; ++ ASSERT_TRUE(page_bitmap.Render(pdf_data, 0)); ++ ++ // Expect blue rectangle on red background sans margin. ++ EXPECT_TRUE(page_bitmap.CheckRect(0x0000ff, 0xff0000, 120)); ++} ++ ++#endif // BUILDFLAG(ENABLE_PRINTING) && BUILDFLAG(ENABLE_PDF) ++ ++} // namespace headless +diff -uNr a/chromium/src/headless/test/headless_policy_browsertest.cc b/chromium/src/headless/test/headless_policy_browsertest.cc +--- a/chromium/src/headless/test/headless_policy_browsertest.cc 2023-02-23 02:41:51.293326900 +0800 ++++ b/chromium/src/headless/test/headless_policy_browsertest.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -23,6 +23,7 @@ + #include "content/public/test/browser_test.h" + #include "headless/lib/browser/policy/headless_mode_policy.h" + #include "headless/public/headless_browser.h" ++#include "headless/test/capture_std_stream.h" + #include "headless/test/headless_browser_test.h" + #include "headless/test/headless_browser_test_utils.h" + #include "net/base/host_port_pair.h" +@@ -116,85 +117,6 @@ + EXPECT_EQ(error, net::ERR_BLOCKED_BY_ADMINISTRATOR); + } + +-namespace { +- +-class CaptureStdErr { +- public: +- CaptureStdErr() { +-#if BUILDFLAG(IS_WIN) +- CHECK_EQ(_pipe(pipes_, 4096, O_BINARY), 0); +-#else +- CHECK_EQ(pipe(pipes_), 0); +-#endif +- stderr_ = dup(fileno(stderr)); +- CHECK_NE(stderr_, -1); +- } +- +- ~CaptureStdErr() { +- StopCapture(); +- close(pipes_[kReadPipe]); +- close(pipes_[kWritePipe]); +- close(stderr_); +- } +- +- void StartCapture() { +- if (capturing_) +- return; +- +- fflush(stderr); +- CHECK_NE(dup2(pipes_[kWritePipe], fileno(stderr)), -1); +- +- capturing_ = true; +- } +- +- void StopCapture() { +- if (!capturing_) +- return; +- +- char eop = kPipeEnd; +- CHECK_NE(write(pipes_[kWritePipe], &eop, sizeof(eop)), -1); +- +- fflush(stderr); +- CHECK_NE(dup2(stderr_, fileno(stderr)), -1); +- +- capturing_ = false; +- } +- +- std::string ReadCapturedData() { +- CHECK(!capturing_); +- +- std::string captured_data; +- for (;;) { +- constexpr size_t kChunkSize = 256; +- char buffer[kChunkSize]; +- int bytes_read = read(pipes_[kReadPipe], buffer, kChunkSize); +- CHECK_NE(bytes_read, -1); +- captured_data.append(buffer, bytes_read); +- if (captured_data.rfind(kPipeEnd) != std::string::npos) +- break; +- } +- return captured_data; +- } +- +- std::vector ReadCapturedLines() { +- return base::SplitString(ReadCapturedData(), "\n", base::TRIM_WHITESPACE, +- base::SPLIT_WANT_NONEMPTY); +- } +- +- private: +- enum { kReadPipe, kWritePipe }; +- +- static constexpr char kPipeEnd = '\xff'; +- +- base::ScopedAllowBlockingForTesting allow_blocking_calls_; +- +- bool capturing_ = false; +- int pipes_[2] = {-1, -1}; +- int stderr_ = -1; +-}; +- +-} // namespace +- + class HeadlessBrowserTestWithRemoteDebuggingAllowedPolicy + : public HeadlessBrowserTestWithPolicy, + public testing::WithParamInterface { +@@ -239,8 +161,12 @@ + base::PlatformThread::Sleep(TestTimeouts::action_timeout()); + capture_stderr_.StopCapture(); + ++ std::vector captured_lines = ++ base::SplitString(capture_stderr_.TakeCapturedData(), "\n", ++ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); ++ + enum { kUnknown, kDisallowed, kListening } remote_debugging_state = kUnknown; +- for (const std::string& line : capture_stderr_.ReadCapturedLines()) { ++ for (const std::string& line : captured_lines) { + LOG(INFO) << "stderr: " << line; + if (base::MatchPattern(line, "DevTools remote debugging is disallowed *")) { + EXPECT_EQ(remote_debugging_state, kUnknown); +diff -uNr a/chromium/src/headless/test/headless_printtopdf_browsertest.cc b/chromium/src/headless/test/headless_printtopdf_browsertest.cc +--- a/chromium/src/headless/test/headless_printtopdf_browsertest.cc 2023-02-23 02:41:51.293326900 +0800 ++++ b/chromium/src/headless/test/headless_printtopdf_browsertest.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -20,6 +20,7 @@ + #include "headless/test/headless_browser_test.h" + #include "headless/test/headless_browser_test_utils.h" + #include "headless/test/headless_devtooled_browsertest.h" ++#include "headless/test/pdf_utils.h" + #include "pdf/pdf.h" + #include "printing/buildflags/buildflags.h" + #include "printing/pdf_render_settings.h" +@@ -34,59 +35,6 @@ + + namespace headless { + +-namespace { +- +-// Utility class to render the specified PDF page into a bitmap and +-// inspect the resulting pixels. +-class PDFPageBitmap { +- public: +- static constexpr int kColorChannels = 4; +- static constexpr int kDpi = 300; +- +- PDFPageBitmap() = default; +- ~PDFPageBitmap() = default; +- +- void Render(base::span pdf_span, int page_index) { +- absl::optional page_size_in_points = +- chrome_pdf::GetPDFPageSizeByIndex(pdf_span, page_index); +- ASSERT_TRUE(page_size_in_points.has_value()); +- +- gfx::SizeF page_size_in_pixels = +- gfx::ScaleSize(page_size_in_points.value(), +- static_cast(kDpi) / printing::kPointsPerInch); +- +- gfx::Rect page_rect(gfx::ToCeiledSize(page_size_in_pixels)); +- +- constexpr chrome_pdf::RenderOptions options = { +- .stretch_to_bounds = false, +- .keep_aspect_ratio = true, +- .autorotate = true, +- .use_color = true, +- .render_device_type = chrome_pdf::RenderDeviceType::kPrinter, +- }; +- +- bitmap_size_ = page_rect.size(); +- bitmap_data_.resize(kColorChannels * bitmap_size_.GetArea()); +- ASSERT_TRUE(chrome_pdf::RenderPDFPageToBitmap( +- pdf_span, page_index, bitmap_data_.data(), bitmap_size_, +- gfx::Size(kDpi, kDpi), options)); +- } +- +- uint32_t GetPixelRGB(int x, int y) { +- int pixel_index = +- bitmap_size_.width() * y * kColorChannels + x * kColorChannels; +- return bitmap_data_[pixel_index + 0] // B +- | (bitmap_data_[pixel_index + 1] << 8) // G +- | (bitmap_data_[pixel_index + 2] << 16); // R +- } +- +- protected: +- std::vector bitmap_data_; +- gfx::Size bitmap_size_; +-}; +- +-} // namespace +- + class HeadlessPDFPagesBrowserTest : public HeadlessDevTooledBrowserTest { + public: + const double kPaperWidth = 10; +@@ -417,7 +365,7 @@ + EXPECT_THAT(num_pages, testing::Eq(1)); + + PDFPageBitmap page_image; +- page_image.Render(pdf_span, 0); ++ ASSERT_TRUE(page_image.Render(pdf_span, 0)); + + // Expect red iframe pixel at 1 inch into the page. + EXPECT_EQ(page_image.GetPixelRGB(1 * PDFPageBitmap::kDpi, +diff -uNr a/chromium/src/headless/test/pdf_utils.cc b/chromium/src/headless/test/pdf_utils.cc +--- a/chromium/src/headless/test/pdf_utils.cc 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/test/pdf_utils.cc 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,120 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include "headless/test/pdf_utils.h" ++ ++#include "base/logging.h" ++#include "pdf/pdf.h" ++#include "printing/units.h" ++#include "third_party/abseil-cpp/absl/types/optional.h" ++#include "ui/gfx/geometry/rect.h" ++#include "ui/gfx/geometry/size_conversions.h" ++#include "ui/gfx/geometry/size_f.h" ++ ++namespace headless { ++ ++PDFPageBitmap::PDFPageBitmap() = default; ++PDFPageBitmap::~PDFPageBitmap() = default; ++ ++bool PDFPageBitmap::Render(const std::string& pdf_data, int page_index) { ++ auto pdf_span = base::make_span( ++ reinterpret_cast(pdf_data.data()), pdf_data.size()); ++ return Render(pdf_span, page_index); ++} ++ ++bool PDFPageBitmap::Render(base::span pdf_data, int page_index) { ++ absl::optional page_size_in_points = ++ chrome_pdf::GetPDFPageSizeByIndex(pdf_data, page_index); ++ if (!page_size_in_points) { ++ return false; ++ } ++ ++ gfx::SizeF page_size_in_pixels = ++ gfx::ScaleSize(page_size_in_points.value(), ++ static_cast(kDpi) / printing::kPointsPerInch); ++ ++ gfx::Rect page_rect(gfx::ToCeiledSize(page_size_in_pixels)); ++ ++ constexpr chrome_pdf::RenderOptions options = { ++ .stretch_to_bounds = false, ++ .keep_aspect_ratio = true, ++ .autorotate = true, ++ .use_color = true, ++ .render_device_type = chrome_pdf::RenderDeviceType::kPrinter, ++ }; ++ ++ bitmap_size_ = page_rect.size(); ++ bitmap_data_.resize(kColorChannels * bitmap_size_.GetArea()); ++ return chrome_pdf::RenderPDFPageToBitmap(pdf_data, page_index, ++ bitmap_data_.data(), bitmap_size_, ++ gfx::Size(kDpi, kDpi), options); ++} ++ ++uint32_t PDFPageBitmap::GetPixelRGB(const gfx::Point& pt) const { ++ return GetPixelRGB(pt.x(), pt.y()); ++} ++ ++uint32_t PDFPageBitmap::GetPixelRGB(int x, int y) const { ++ CHECK_LT(x, bitmap_size_.width()); ++ CHECK_LT(y, bitmap_size_.height()); ++ ++ int pixel_index = ++ bitmap_size_.width() * y * kColorChannels + x * kColorChannels; ++ return bitmap_data_[pixel_index + 0] // B ++ | (bitmap_data_[pixel_index + 1] << 8) // G ++ | (bitmap_data_[pixel_index + 2] << 16); // R ++} ++ ++bool PDFPageBitmap::CheckRect(uint32_t rect_color, ++ uint32_t bkgr_color, ++ int margins) { ++ gfx::Rect body(bitmap_size_); ++ if (margins) { ++ body.Inset(margins); ++ } ++ ++ // Build color rectangle by including every pixel with the specified ++ // rectangle color into a rectangle. ++ gfx::Rect rect; ++ for (int y = body.y(); y < body.bottom(); y++) { ++ for (int x = body.x(); x < body.right(); x++) { ++ uint32_t color = GetPixelRGB(x, y); ++ if (color == rect_color) { ++ gfx::Rect pixel_rect(x, y, 1, 1); ++ if (rect.IsEmpty()) { ++ rect = pixel_rect; ++ } else { ++ rect.Union(pixel_rect); ++ } ++ } ++ } ++ } ++ ++ // Verify that all pixels outside the found color rectangle are of ++ // the specified background color, and the ones that are inside ++ // the found rectangle are all of the rectangle color. ++ for (int y = body.y(); y < body.bottom(); y++) { ++ for (int x = body.x(); x < body.right(); x++) { ++ gfx::Point pt(x, y); ++ uint32_t color = GetPixelRGB(pt); ++ if (rect.Contains(pt)) { ++ if (color != rect_color) { ++ LOG(ERROR) << "pt=" << pt.ToString() << " color=" << color ++ << ", expected rect color=" << rect_color; ++ return false; ++ } ++ } else { ++ if (color != bkgr_color) { ++ LOG(ERROR) << "pt=" << pt.ToString() << " color=" << color ++ << ", expected bkgr color=" << bkgr_color; ++ return false; ++ } ++ } ++ } ++ } ++ ++ return true; ++} ++ ++} // namespace headless +diff -uNr a/chromium/src/headless/test/pdf_utils.h b/chromium/src/headless/test/pdf_utils.h +--- a/chromium/src/headless/test/pdf_utils.h 1970-01-01 08:00:00.000000000 +0800 ++++ b/chromium/src/headless/test/pdf_utils.h 2023-08-04 12:43:55.229936729 +0800 +@@ -0,0 +1,49 @@ ++// Copyright 2022 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#ifndef HEADLESS_TEST_PDF_UTILS_H_ ++#define HEADLESS_TEST_PDF_UTILS_H_ ++ ++#include ++#include ++#include ++ ++#include "base/containers/span.h" ++#include "ui/gfx/geometry/point.h" ++#include "ui/gfx/geometry/size.h" ++ ++namespace headless { ++ ++// Utility class to render PDF page into a bitmap and inspect its pixels. ++class PDFPageBitmap { ++ public: ++ static constexpr int kDpi = 300; ++ static constexpr int kColorChannels = 4; ++ ++ PDFPageBitmap(); ++ ~PDFPageBitmap(); ++ ++ bool Render(const std::string& pdf_data, int page_index); ++ bool Render(base::span pdf_data, int page_index); ++ ++ uint32_t GetPixelRGB(const gfx::Point& pt) const; ++ uint32_t GetPixelRGB(int x, int y) const; ++ ++ bool CheckRect(uint32_t rect_color, uint32_t bkgr_color, int margins); ++ bool CheckRect(uint32_t rect_color, uint32_t bkgr_color) { ++ return CheckRect(rect_color, bkgr_color, /*margins=*/0); ++ } ++ ++ int width() const { return bitmap_size_.width(); } ++ int height() const { return bitmap_size_.height(); } ++ gfx::Size size() const { return bitmap_size_; } ++ ++ private: ++ std::vector bitmap_data_; ++ gfx::Size bitmap_size_; ++}; ++ ++} // namespace headless ++ ++#endif // HEADLESS_TEST_PDF_UTILS_H_ +diff -uNr a/chromium/src/tools/gritsettings/resource_ids.spec b/chromium/src/tools/gritsettings/resource_ids.spec +--- a/chromium/src/tools/gritsettings/resource_ids.spec 2023-02-23 02:42:14.273311600 +0800 ++++ b/chromium/src/tools/gritsettings/resource_ids.spec 2023-08-04 12:43:55.229936729 +0800 +@@ -958,6 +958,10 @@ + "messages": [4380], + }, + ++ "headless/app/headless_command.grd": { ++ "includes": [4410], ++ }, ++ + "mojo/public/js/mojo_bindings_resources.grd": { + "includes": [4420], + }, diff --git a/tur-continuous/carbonyl/9999-fix-patch-for-headless-shell.diff b/tur-continuous/carbonyl/9999-fix-patch-for-headless-shell.diff new file mode 100644 index 000000000..aacb30e00 --- /dev/null +++ b/tur-continuous/carbonyl/9999-fix-patch-for-headless-shell.diff @@ -0,0 +1,20 @@ +--- a/chromium/patches/chromium/0013-Refactor-rendering-bridge.patch ++++ b/chromium/patches/chromium/0013-Refactor-rendering-bridge.patch +@@ -829,14 +829,14 @@ + index c29495a7060d7..654438b8d7424 100644 + --- a/third_party/blink/renderer/core/BUILD.gn + +++ b/third_party/blink/renderer/core/BUILD.gn +-@@ -312,6 +312,7 @@ component("core") { ++@@ -312,6 +312,7 @@ + ":generate_eventhandler_names", + ":generated_settings_macros", + "//build:chromeos_buildflags", + + "//carbonyl/src/browser:bridge", +- "//components/attribution_reporting", +- "//components/attribution_reporting:mojom_blink", + "//components/paint_preview/common", ++ "//components/performance_manager/public/mojom:mojom_blink", ++ "//components/power_scheduler", + diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc + index cb116ee07c8f6..7129982acf4a6 100644 + --- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc diff --git a/tur-continuous/carbonyl/build.sh b/tur-continuous/carbonyl/build.sh new file mode 100644 index 000000000..5d865910a --- /dev/null +++ b/tur-continuous/carbonyl/build.sh @@ -0,0 +1,285 @@ +TERMUX_PKG_HOMEPAGE=https://github.com/fathyb/carbonyl +TERMUX_PKG_DESCRIPTION="Chromium based browser built to run in a terminal" +TERMUX_PKG_LICENSE="BSD 3-Clause" +TERMUX_PKG_LICENSE_FILE="license.md" +TERMUX_PKG_MAINTAINER="Chongyun Lee " +_CHROMIUM_VERSION=110.0.5481.177 +TERMUX_PKG_VERSION=0.0.3 +TERMUX_PKG_SRCURL=(https://github.com/fathyb/carbonyl/archive/refs/tags/v$TERMUX_PKG_VERSION.tar.gz) +TERMUX_PKG_SRCURL+=(https://commondatastorage.googleapis.com/chromium-browser-official/chromium-$_CHROMIUM_VERSION.tar.xz) +TERMUX_PKG_SHA256=(bf421b9498a084a7cf2238a574d37d31b498d3e271fdb3dcf466e7ed6c80013d) +TERMUX_PKG_SHA256+=(7b2f454d1195270a39f94a9ff6d8d68126be315e0da4e31c20f4ba9183a1c9b7) +TERMUX_PKG_DEPENDS="electron-deps" +TERMUX_PKG_BUILD_DEPENDS="libnotify, libffi-static" +# Chromium doesn't support i686 on Linux. +# Carbonyl donesn't support arm. +TERMUX_PKG_BLACKLISTED_ARCHES="arm, i686" + +termux_step_post_get_source() { + # Move chromium source + mv chromium-$_CHROMIUM_VERSION chromium/src + + # Apply diffs + for f in $(find "$TERMUX_PKG_BUILDER_DIR/" -maxdepth 1 -type f -name *.diff | sort); do + echo "Applying patch: $(basename $f)" + patch -p1 < "$f" + done + + # Apply patches related to chromium + pushd chromium/src + python $TERMUX_SCRIPTDIR/common-files/apply-chromium-patches.py -v $_CHROMIUM_VERSION + popd + + # Apply patches related to carbonyl + pushd chromium/src + for f in $(find "$TERMUX_PKG_SRCDIR/chromium/patches/chromium/" -maxdepth 1 -type f -name *.patch | sort); do + echo "Applying patch: $(basename $f)" + patch --silent -p1 < "$f" || bash + done + popd + + pushd chromium/src/third_party/skia + for f in $(find "$TERMUX_PKG_SRCDIR/chromium/patches/skia/" -maxdepth 1 -type f -name *.diff | sort); do + echo "Applying patch: $(basename $f)" + patch --silent -p1 < "$f" || bash + done + popd + + pushd chromium/src/third_party/webrtc + for f in $(find "$TERMUX_PKG_SRCDIR/chromium/patches/webrtc/" -maxdepth 1 -type f -name *.diff | sort); do + echo "Applying patch: $(basename $f)" + patch --silent -p1 < "$f" || bash + done + popd +} + +termux_step_configure() { + cd $TERMUX_PKG_SRCDIR/chromium/src + termux_setup_ninja + termux_setup_gn + termux_setup_nodejs + + # Remove termux's dummy pkg-config + local _target_pkg_config=$(command -v pkg-config) + local _host_pkg_config="$(cat $_target_pkg_config | grep exec | awk '{print $2}')" + rm -rf $TERMUX_PKG_TMPDIR/host-pkg-config-bin + mkdir -p $TERMUX_PKG_TMPDIR/host-pkg-config-bin + ln -s $_host_pkg_config $TERMUX_PKG_TMPDIR/host-pkg-config-bin/pkg-config + export PATH="$TERMUX_PKG_TMPDIR/host-pkg-config-bin:$PATH" + + env -i PATH="$PATH" sudo apt update + env -i PATH="$PATH" sudo apt install libdrm-dev libjpeg-turbo8-dev libpng-dev fontconfig libfontconfig-dev libfontconfig1-dev libfreetype6-dev zlib1g-dev libcups2-dev libxkbcommon-dev libglib2.0-dev -yq + env -i PATH="$PATH" sudo apt install libdrm-dev:i386 libjpeg-turbo8-dev:i386 libpng-dev:i386 libfontconfig-dev:i386 libfontconfig1-dev:i386 libfreetype6-dev:i386 zlib1g-dev:i386 libcups2-dev:i386 libglib2.0-dev:i386 libxkbcommon-dev:i386 -yq + + # Install amd64 rootfs if necessary, it should have been installed by source hooks. + build/linux/sysroot_scripts/install-sysroot.py --arch=amd64 + local _amd64_sysroot_path="$(pwd)/build/linux/$(ls build/linux | grep 'amd64-sysroot')" + + # Install i386 rootfs if necessary, it should have been installed by source hooks. + build/linux/sysroot_scripts/install-sysroot.py --arch=i386 + local _i386_sysroot_path="$(pwd)/build/linux/$(ls build/linux | grep 'i386-sysroot')" + + # Link to system tools required by the build + mkdir -p third_party/node/linux/node-linux-x64/bin + ln -sf $(command -v node) third_party/node/linux/node-linux-x64/bin/ + ln -sf $(command -v java) third_party/jdk/current/bin/ + + # Dummy librt.so + # Why not dummy a librt.a? Some of the binaries reference symbols only exists in Android + # for some reason, such as the `chrome_crashpad_handler`, which needs to link with + # libprotobuf_lite.a, but it is hard to remove the usage of `android/log.h` in protobuf. + echo "INPUT(-llog -liconv -landroid-shmem)" > "$TERMUX_PREFIX/lib/librt.so" + + # Dummy libpthread.a and libresolv.a + echo '!' > "$TERMUX_PREFIX/lib/libpthread.a" + echo '!' > "$TERMUX_PREFIX/lib/libresolv.a" + + # Symlink libffi.a to libffi_pic.a + ln -sfr $TERMUX_PREFIX/lib/libffi.a $TERMUX_PREFIX/lib/libffi_pic.a + + # Merge sysroots + rm -rf $TERMUX_PKG_TMPDIR/sysroot + mkdir -p $TERMUX_PKG_TMPDIR/sysroot + pushd $TERMUX_PKG_TMPDIR/sysroot + mkdir -p usr/include usr/lib usr/bin + cp -R $TERMUX_STANDALONE_TOOLCHAIN/sysroot/usr/include/* usr/include + cp -R $TERMUX_STANDALONE_TOOLCHAIN/sysroot/usr/include/$TERMUX_HOST_PLATFORM/* usr/include + cp -R $TERMUX_STANDALONE_TOOLCHAIN/sysroot/usr/lib/$TERMUX_HOST_PLATFORM/$TERMUX_PKG_API_LEVEL/* usr/lib/ + cp "$TERMUX_STANDALONE_TOOLCHAIN/sysroot/usr/lib/$TERMUX_HOST_PLATFORM/libc++_shared.so" usr/lib/ + cp "$TERMUX_STANDALONE_TOOLCHAIN/sysroot/usr/lib/$TERMUX_HOST_PLATFORM/libc++_static.a" usr/lib/ + cp "$TERMUX_STANDALONE_TOOLCHAIN/sysroot/usr/lib/$TERMUX_HOST_PLATFORM/libc++abi.a" usr/lib/ + cp -Rf $TERMUX_PREFIX/include/* usr/include + cp -Rf $TERMUX_PREFIX/lib/* usr/lib + ln -sf /data ./data + # This is needed to build cups + cp -Rf $TERMUX_PREFIX/bin/cups-config usr/bin/ + chmod +x usr/bin/cups-config + popd + + # Construct args + local _target_cpu _v8_current_cpu _v8_sysroot_path + local _v8_toolchain_name _target_sysroot="$TERMUX_PKG_TMPDIR/sysroot" + if [ "$TERMUX_ARCH" = "aarch64" ]; then + _target_cpu="arm64" + _v8_current_cpu="x64" + _v8_sysroot_path="$_amd64_sysroot_path" + _v8_toolchain_name="clang_x64_v8_arm64" + elif [ "$TERMUX_ARCH" = "arm" ]; then + _target_cpu="arm" + _v8_current_cpu="x86" + _v8_sysroot_path="$_i386_sysroot_path" + _v8_toolchain_name="clang_x86_v8_arm" + elif [ "$TERMUX_ARCH" = "x86_64" ]; then + _target_cpu="x64" + _v8_current_cpu="x64" + _v8_sysroot_path="$_amd64_sysroot_path" + _v8_toolchain_name="clang_x64" + fi + + local _common_args_file=$TERMUX_PKG_TMPDIR/common-args-file + rm -f $_common_args_file + touch $_common_args_file + + echo " +import(\"//carbonyl/src/browser/args.gn\") +# Do not build with symbols +is_debug = false +is_component_build = false +symbol_level = 0 +# Use our custom toolchain +use_sysroot = false +target_cpu = \"$_target_cpu\" +target_rpath = \"$TERMUX_PREFIX/lib\" +target_sysroot = \"$_target_sysroot\" +clang_base_path = \"$TERMUX_STANDALONE_TOOLCHAIN\" +custom_toolchain = \"//build/toolchain/linux/unbundle:default\" +host_toolchain = \"$TERMUX_PKG_CACHEDIR/custom-toolchain:host\" +v8_snapshot_toolchain = \"$TERMUX_PKG_CACHEDIR/custom-toolchain:$_v8_toolchain_name\" +clang_use_chrome_plugins = false +dcheck_always_on = false +chrome_pgo_phase = 0 +treat_warnings_as_errors = false +# Use system libraries as little as possible +use_bundled_fontconfig = false +use_system_freetype = true +use_system_libdrm = true +use_custom_libcxx = false +use_allocator_shim = false +use_partition_alloc_as_malloc = false +enable_backup_ref_ptr_support = false +enable_mte_checked_ptr_support = false +use_nss_certs = true +use_udev = false +use_gnome_keyring = false +use_alsa = false +use_libpci = false +use_pulseaudio = true +use_ozone = true +use_qt = false +ozone_auto_platforms = false +ozone_platform = \"x11\" +ozone_platform_x11 = true +ozone_platform_wayland = true +ozone_platform_headless = true +angle_enable_vulkan = true +angle_enable_swiftshader = true +rtc_use_pipewire = false +use_vaapi_x11 = false +# See comments on Chromium package +enable_nacl = false +use_thin_lto=false +" >> $_common_args_file + + if [ "$TERMUX_ARCH" = "arm" ]; then + echo "arm_arch = \"armv7-a\"" >> $_common_args_file + echo "arm_float_abi = \"softfp\"" >> $_common_args_file + fi + + # Use custom toolchain + mkdir -p $TERMUX_PKG_CACHEDIR/custom-toolchain + cp -f $TERMUX_PKG_BUILDER_DIR/toolchain.gn.in $TERMUX_PKG_CACHEDIR/custom-toolchain/BUILD.gn + sed -i "s|@HOST_CC@|/usr/bin/clang-14|g + s|@HOST_CXX@|/usr/bin/clang++-14|g + s|@HOST_LD@|/usr/bin/clang++-14|g + s|@HOST_AR@|$(command -v llvm-ar)|g + s|@HOST_NM@|$(command -v llvm-nm)|g + s|@HOST_IS_CLANG@|true|g + s|@HOST_USE_GOLD@|false|g + s|@HOST_SYSROOT@|$_amd64_sysroot_path|g + " $TERMUX_PKG_CACHEDIR/custom-toolchain/BUILD.gn + sed -i "s|@V8_CC@|/usr/bin/clang-14|g + s|@V8_CXX@|/usr/bin/clang++-14|g + s|@V8_LD@|/usr/bin/clang++-14|g + s|@V8_AR@|$(command -v llvm-ar)|g + s|@V8_NM@|$(command -v llvm-nm)|g + s|@V8_TOOLCHAIN_NAME@|$_v8_toolchain_name|g + s|@V8_CURRENT_CPU@|$_v8_current_cpu|g + s|@V8_V8_CURRENT_CPU@|$_target_cpu|g + s|@V8_IS_CLANG@|true|g + s|@V8_USE_GOLD@|false|g + s|@V8_SYSROOT@|$_v8_sysroot_path|g + " $TERMUX_PKG_CACHEDIR/custom-toolchain/BUILD.gn + + cd $TERMUX_PKG_SRCDIR/chromium/src + mkdir -p $TERMUX_PKG_BUILDDIR/out/Release + cat $_common_args_file > $TERMUX_PKG_BUILDDIR/out/Release/args.gn + gn gen $TERMUX_PKG_BUILDDIR/out/Release --export-compile-commands || bash +} + +termux_step_make() { + cd $TERMUX_PKG_SRCDIR + (termux_setup_rust + cargo build --jobs $TERMUX_MAKE_PROCESSES --target $CARGO_TARGET_NAME --release) + + cd $TERMUX_PKG_BUILDDIR + ninja -C $TERMUX_PKG_BUILDDIR/out/Release headless:headless_shell || bash +} + +termux_step_make_install() { + cd $TERMUX_PKG_BUILDDIR + mkdir -p $TERMUX_PREFIX/lib/$TERMUX_PKG_NAME + + # Install the runtime library + termux_setup_rust + cp $TERMUX_PKG_SRCDIR/build/$CARGO_TARGET_NAME/release/*.so $TERMUX_PREFIX/lib/$TERMUX_PKG_NAME/ + + # Install chromium-related files + local normal_files=( + # Binary files + headless_shell + chrome_crashpad_handler + + # Resource files + headless_lib_data.pak + headless_lib_strings.pak + + # V8 Snapshot data + v8_context_snapshot.bin + + # ICU Data + icudtl.dat + + # Angle + libEGL.so + libGLESv2.so + + # Vulkan + libvulkan.so.1 + libvk_swiftshader.so + vk_swiftshader_icd.json + ) + + cp "${normal_files[@]/#/out/Release/}" "$TERMUX_PREFIX/lib/$TERMUX_PKG_NAME/" + + cp -Rf out/Release/angledata $TERMUX_PREFIX/lib/$TERMUX_PKG_NAME/ + + chmod +x $TERMUX_PREFIX/lib/$TERMUX_PKG_NAME/headless_shell + + # Install as the default binary + ln -sfr $TERMUX_PREFIX/lib/$TERMUX_PKG_NAME/headless_shell $TERMUX_PREFIX/bin/carbonyl +} + +termux_step_post_make_install() { + # Remove the dummy files + rm $TERMUX_PREFIX/lib/lib{{pthread,resolv,ffi_pic}.a,rt.so} +} diff --git a/tur-continuous/carbonyl/toolchain.gn.in b/tur-continuous/carbonyl/toolchain.gn.in new file mode 100644 index 000000000..6c26578e8 --- /dev/null +++ b/tur-continuous/carbonyl/toolchain.gn.in @@ -0,0 +1,32 @@ +import("//build/config/sysroot.gni") +import("//build/toolchain/gcc_toolchain.gni") + +gcc_toolchain("host") { + cc = "@HOST_CC@" + cxx = "@HOST_CXX@" + ld = "@HOST_LD@" + ar = "@HOST_AR@" + nm = "@HOST_NM@" + toolchain_args = { + current_os = "linux" + current_cpu = "x64" + is_clang = @HOST_IS_CLANG@ + use_gold = @HOST_USE_GOLD@ + sysroot = "@HOST_SYSROOT@" + } +} +gcc_toolchain("@V8_TOOLCHAIN_NAME@") { + cc = "@V8_CC@" + cxx = "@V8_CXX@" + ld = "@V8_LD@" + ar = "@V8_AR@" + nm = "@V8_NM@" + toolchain_args = { + current_os = "linux" + current_cpu = "@V8_CURRENT_CPU@" + v8_current_cpu = "@V8_V8_CURRENT_CPU@" + is_clang = @V8_IS_CLANG@ + use_gold = @V8_USE_GOLD@ + sysroot = "@V8_SYSROOT@" + } +}