Browse Source

refactor: type-safe module imports / requires (#41236)

* refactor: type-safe module imports / requires

Co-authored-by: Milan Burda <[email protected]>

* chore: update patches

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <[email protected]>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
trop[bot] 1 year ago
parent
commit
d6221eaf2d

+ 4 - 2
lib/browser/init.ts

@@ -3,6 +3,8 @@ import * as fs from 'fs';
 import * as path from 'path';
 
 import type * as defaultMenuModule from '@electron/internal/browser/default-menu';
+import type * as url from 'url';
+import type * as v8 from 'v8';
 
 const Module = require('module') as NodeJS.ModuleInternal;
 
@@ -132,7 +134,7 @@ if (packageJson.desktopName != null) {
 // Set v8 flags, deliberately lazy load so that apps that do not use this
 // feature do not pay the price
 if (packageJson.v8Flags != null) {
-  require('v8').setFlagsFromString(packageJson.v8Flags);
+  (require('v8') as typeof v8).setFlagsFromString(packageJson.v8Flags);
 }
 
 app.setAppPath(packagePath);
@@ -195,7 +197,7 @@ if (packagePath) {
   // Finally load app's main.js and transfer control to C++.
   if ((packageJson.type === 'module' && !mainStartupScript.endsWith('.cjs')) || mainStartupScript.endsWith('.mjs')) {
     const { loadESM } = __non_webpack_require__('internal/process/esm_loader');
-    const main = require('url').pathToFileURL(path.join(packagePath, mainStartupScript));
+    const main = (require('url') as typeof url).pathToFileURL(path.join(packagePath, mainStartupScript));
     loadESM(async (esmLoader: any) => {
       try {
         await esmLoader.import(main.toString(), undefined, Object.create(null));

+ 3 - 2
lib/common/init.ts

@@ -1,6 +1,7 @@
 import * as util from 'util';
+import type * as stream from 'stream';
 
-const timers = require('timers');
+import timers = require('timers');
 
 type AnyFn = (...args: any[]) => any
 
@@ -62,7 +63,7 @@ if (process.type === 'browser' ||
 
 if (process.platform === 'win32') {
   // Always returns EOF for stdin stream.
-  const { Readable } = require('stream');
+  const { Readable } = require('stream') as typeof stream;
   const stdin = new Readable();
   stdin.push(null);
   Object.defineProperty(process, 'stdin', {

+ 3 - 1
lib/node/asar-fs-wrapper.ts

@@ -2,7 +2,9 @@ import { Buffer } from 'buffer';
 import { constants } from 'fs';
 import * as path from 'path';
 import * as util from 'util';
+
 import type * as Crypto from 'crypto';
+import type * as os from 'os';
 
 const asar = process._linkedBinding('electron_common_asar');
 
@@ -255,7 +257,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
     if (!process.env.ELECTRON_LOG_ASAR_READS) return;
     if (!logFDs.has(asarPath)) {
       const logFilename = `${path.basename(asarPath, '.asar')}-access-log.txt`;
-      const logPath = path.join(require('os').tmpdir(), logFilename);
+      const logPath = path.join((require('os') as typeof os).tmpdir(), logFilename);
       logFDs.set(asarPath, fs.openSync(logPath, 'a'));
     }
     fs.writeSync(logFDs.get(asarPath), `${offset}: ${filePath}\n`);

+ 1 - 1
lib/sandboxed_renderer/init.ts

@@ -1,4 +1,5 @@
 import * as events from 'events';
+import { setImmediate, clearImmediate } from 'timers';
 import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
 
 import type * as ipcRendererUtilsModule from '@electron/internal/renderer/ipc-renderer-internal-utils';
@@ -126,7 +127,6 @@ function runPreloadScript (preloadSrc: string) {
 
   // eval in window scope
   const preloadFn = binding.createPreloadScript(preloadWrapperSrc);
-  const { setImmediate, clearImmediate } = require('timers');
   const exports = {};
 
   preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate, clearImmediate, exports, { exports });

+ 0 - 4
patches/chromium/.patches

@@ -144,7 +144,3 @@ feat_allow_code_cache_in_custom_schemes.patch
 enable_partition_alloc_ref_count_size.patch
 ensure_an_axcontext_before_painting.patch
 safely_crash_on_dangling_profile.patch
-cherry-pick-91a02d67a83d.patch
-cherry-pick-8755f76bec32.patch
-cherry-pick-1f8bec968902.patch
-cherry-pick-4a98f9e304be.patch

+ 0 - 125
patches/chromium/cherry-pick-1f8bec968902.patch

@@ -1,125 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Tsuyoshi Horo <[email protected]>
-Date: Wed, 24 Jan 2024 02:04:24 +0000
-Subject: Fix UAF in SourceStreamToDataPipe
-
-SourceStreamToDataPipe::ReadMore() is passing a callback with
-Unretained(this) to net::SourceStream::Read(). But this callback may be
-called even after the SourceStream is destructed. This is causing UAF
-issue (crbug.com/1511085).
-
-To solve this problem, this CL changes ReadMore() method to pass a
-callback with a weak ptr of this.
-
-(cherry picked from commit 6e36a69da1b73f9aea9c54bfbe6c5b9cb2c672a5)
-
-Bug: 1511085
-Change-Id: Idd4e34ff300ff5db2de1de7b303841c7db3a964a
-Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5179746
-Reviewed-by: Adam Rice <[email protected]>
-Commit-Queue: Tsuyoshi Horo <[email protected]>
-Cr-Original-Commit-Position: refs/heads/main@{#1244526}
-Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5231558
-Reviewed-by: Kenichi Ishibashi <[email protected]>
-Cr-Commit-Position: refs/branch-heads/6099@{#1860}
-Cr-Branched-From: e6ee4500f7d6549a9ac1354f8d056da49ef406be-refs/heads/main@{#1217362}
-
-diff --git a/services/network/public/cpp/source_stream_to_data_pipe.cc b/services/network/public/cpp/source_stream_to_data_pipe.cc
-index 6be121003a1044ea55e086e7b0853589ded52732..62ad4f9fd58f29fae844cde5bbdb8273fdb40ae4 100644
---- a/services/network/public/cpp/source_stream_to_data_pipe.cc
-+++ b/services/network/public/cpp/source_stream_to_data_pipe.cc
-@@ -56,9 +56,9 @@ void SourceStreamToDataPipe::ReadMore() {
-   int num_bytes = base::checked_cast<int>(pending_write_->size());
-   scoped_refptr<net::IOBuffer> buffer(
-       new network::NetToMojoIOBuffer(pending_write_.get()));
--  int result = source_->Read(
--      buffer.get(), num_bytes,
--      base::BindOnce(&SourceStreamToDataPipe::DidRead, base::Unretained(this)));
-+  int result = source_->Read(buffer.get(), num_bytes,
-+                             base::BindOnce(&SourceStreamToDataPipe::DidRead,
-+                                            weak_factory_.GetWeakPtr()));
- 
-   if (result != net::ERR_IO_PENDING)
-     DidRead(result);
-diff --git a/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc b/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc
-index 7061418c5141d936f04b1193c98e66efc5e72ac5..54159df39afa7cf6e2faa51da185dc034b923209 100644
---- a/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc
-+++ b/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc
-@@ -6,7 +6,9 @@
- 
- #include "base/functional/bind.h"
- #include "base/memory/raw_ptr.h"
-+#include "base/test/bind.h"
- #include "base/test/task_environment.h"
-+#include "net/base/net_errors.h"
- #include "net/filter/mock_source_stream.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
-@@ -42,6 +44,33 @@ struct SourceStreamToDataPipeTestParam {
-   const ReadResultType read_result_type;
- };
- 
-+class DummyPendingSourceStream : public net::SourceStream {
-+ public:
-+  DummyPendingSourceStream() : net::SourceStream(SourceStream::TYPE_NONE) {}
-+  ~DummyPendingSourceStream() override = default;
-+
-+  DummyPendingSourceStream(const DummyPendingSourceStream&) = delete;
-+  DummyPendingSourceStream& operator=(const DummyPendingSourceStream&) = delete;
-+
-+  // SourceStream implementation
-+  int Read(net::IOBuffer* dest_buffer,
-+           int buffer_size,
-+           net::CompletionOnceCallback callback) override {
-+    callback_ = std::move(callback);
-+    return net::ERR_IO_PENDING;
-+  }
-+  std::string Description() const override { return ""; }
-+  bool MayHaveMoreBytes() const override { return true; }
-+
-+  net::CompletionOnceCallback TakeCompletionCallback() {
-+    CHECK(callback_);
-+    return std::move(callback_);
-+  }
-+
-+ private:
-+  net::CompletionOnceCallback callback_;
-+};
-+
- }  // namespace
- 
- class SourceStreamToDataPipeTest
-@@ -212,4 +241,33 @@ TEST_P(SourceStreamToDataPipeTest, MayHaveMoreBytes) {
-   EXPECT_EQ(ReadPipe(&output), net::OK);
-   EXPECT_EQ(output, message);
- }
-+
-+TEST(SourceStreamToDataPipeCallbackTest, CompletionCallbackAfterDestructed) {
-+  base::test::TaskEnvironment task_environment;
-+
-+  std::unique_ptr<DummyPendingSourceStream> source =
-+      std::make_unique<DummyPendingSourceStream>();
-+  DummyPendingSourceStream* source_ptr = source.get();
-+  const MojoCreateDataPipeOptions data_pipe_options{
-+      sizeof(MojoCreateDataPipeOptions), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1, 1};
-+  mojo::ScopedDataPipeProducerHandle producer_end;
-+  mojo::ScopedDataPipeConsumerHandle consumer_end;
-+  CHECK_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(&data_pipe_options,
-+                                                producer_end, consumer_end));
-+
-+  std::unique_ptr<SourceStreamToDataPipe> adapter =
-+      std::make_unique<SourceStreamToDataPipe>(std::move(source),
-+                                               std::move(producer_end));
-+  bool callback_called = false;
-+  adapter->Start(
-+      base::BindLambdaForTesting([&](int result) { callback_called = true; }));
-+  net::CompletionOnceCallback callback = source_ptr->TakeCompletionCallback();
-+  adapter.reset();
-+
-+  // Test that calling `callback` after deleting `adapter` must not cause UAF
-+  // (crbug.com/1511085).
-+  std::move(callback).Run(net::ERR_FAILED);
-+  EXPECT_FALSE(callback_called);
-+}
-+
- }  // namespace network

+ 0 - 67
patches/chromium/cherry-pick-4a98f9e304be.patch

@@ -1,67 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Peter=20Bostr=C3=B6m?= <[email protected]>
-Date: Fri, 26 Jan 2024 19:37:57 +0000
-Subject: Speculatively fix race in mojo ShutDownOnIOThread
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-This acquires `write_lock_` before resetting handles used by WriteNoLock
-(which is called under the same lock in another thread). We also set
-`reject_writes_` to prevent future write attempts after shutdown. That
-seems strictly more correct.
-
-We also acquire `fds_to_close_lock_` before clearing the FDs.
-
-I was unable to repro locally as content_browsertests just times out
-in my local setup without reporting anything interesting. This seems
-strictly more correct though.
-
-(cherry picked from commit 9755d9d81e4a8cb5b4f76b23b761457479dbb06b)
-
-Bug: 1519980
-Change-Id: I96279936ca908ecb98eddd381df20d61597cba43
-Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5226127
-Auto-Submit: Peter Boström <[email protected]>
-Reviewed-by: Ken Rockot <[email protected]>
-Commit-Queue: Ken Rockot <[email protected]>
-Commit-Queue: Peter Boström <[email protected]>
-Cr-Original-Commit-Position: refs/heads/main@{#1250580}
-Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5239564
-Cr-Commit-Position: refs/branch-heads/6099@{#1883}
-Cr-Branched-From: e6ee4500f7d6549a9ac1354f8d056da49ef406be-refs/heads/main@{#1217362}
-
-diff --git a/mojo/core/channel_posix.cc b/mojo/core/channel_posix.cc
-index 0a3596382d0e9a40c72bfb4ead6f0338a61253d6..eae6b0768463679b5043514dc5745da52b80ae10 100644
---- a/mojo/core/channel_posix.cc
-+++ b/mojo/core/channel_posix.cc
-@@ -246,16 +246,21 @@ void ChannelPosix::WaitForWriteOnIOThreadNoLock() {
- void ChannelPosix::ShutDownOnIOThread() {
-   base::CurrentThread::Get()->RemoveDestructionObserver(this);
- 
--  read_watcher_.reset();
--  write_watcher_.reset();
--  if (leak_handle_) {
--    std::ignore = socket_.release();
--  } else {
--    socket_.reset();
--  }
-+  {
-+    base::AutoLock lock(write_lock_);
-+    reject_writes_ = true;
-+    read_watcher_.reset();
-+    write_watcher_.reset();
-+    if (leak_handle_) {
-+      std::ignore = socket_.release();
-+    } else {
-+      socket_.reset();
-+    }
- #if BUILDFLAG(IS_IOS)
--  fds_to_close_.clear();
-+    base::AutoLock fd_lock(fds_to_close_lock_);
-+    fds_to_close_.clear();
- #endif
-+  }
- 
-   // May destroy the |this| if it was the last reference.
-   self_ = nullptr;

+ 0 - 110
patches/chromium/cherry-pick-8755f76bec32.patch

@@ -1,110 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Guido Urdaneta <[email protected]>
-Date: Thu, 18 Jan 2024 16:47:18 +0000
-Subject: Exit early from RTCPeerConnectionHandler
-
-For certain operations that require a live client
-(i.e., RTCPeerConnection, which is garbage collected),
-PeerConnectionHandler keeps a pointer to the client on the stack
-to prevent garbage collection.
-
-In some cases, the client may have already been garbage collected
-(the client is null). In that case, there is no point in doing the
-operation and it should exit early to avoid UAF/crashes.
-
-This CL adds early exit to the cases that do not already have it.
-
-Bug: 1514777
-Change-Id: I27e9541cfaa74d978799c03e2832a0980f9e5710
-Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5210359
-Reviewed-by: Tomas Gunnarsson <[email protected]>
-Commit-Queue: Guido Urdaneta <[email protected]>
-Cr-Commit-Position: refs/heads/main@{#1248826}
-
-diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
-index 015c5abec1facd3fe9b427b197a137682b11f526..3afc7074a5775542ba2393aa007ff7c5abb3f728 100644
---- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
-+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
-@@ -1056,15 +1056,19 @@ bool RTCPeerConnectionHandler::Initialize(
-     WebLocalFrame* frame,
-     ExceptionState& exception_state) {
-   DCHECK(task_runner_->RunsTasksInCurrentSequence());
--  DCHECK(frame);
-   DCHECK(dependency_factory_);
--  frame_ = frame;
- 
-   CHECK(!initialize_called_);
-   initialize_called_ = true;
- 
-   // Prevent garbage collection of client_ during processing.
-   auto* client_on_stack = client_.Get();
-+  if (!client_on_stack) {
-+    return false;
-+  }
-+
-+  DCHECK(frame);
-+  frame_ = frame;
-   peer_connection_tracker_ = PeerConnectionTracker::From(*frame);
- 
-   configuration_ = server_configuration;
-@@ -2321,10 +2325,13 @@ void RTCPeerConnectionHandler::OnIceCandidate(const String& sdp,
-                                               int sdp_mline_index,
-                                               int component,
-                                               int address_family) {
-+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-   // In order to ensure that the RTCPeerConnection is not garbage collected
-   // from under the function, we keep a pointer to it on the stack.
-   auto* client_on_stack = client_.Get();
--  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-+  if (!client_on_stack) {
-+    return;
-+  }
-   TRACE_EVENT0("webrtc", "RTCPeerConnectionHandler::OnIceCandidateImpl");
-   // This line can cause garbage collection.
-   auto* platform_candidate = MakeGarbageCollected<RTCIceCandidatePlatform>(
-diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
-index eb44aabb481a66e08d432a9ed4f202d5ca27ee90..ee703c4317f57483a67ea0b67a19137cc859ecc6 100644
---- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
-+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
-@@ -602,7 +602,7 @@ class RTCPeerConnectionHandlerTest : public SimTest {
-     waitable_event->Signal();
-   }
- 
-- public:
-+ protected:
-   ScopedTestingPlatformSupport<AudioCapturerSourceTestingPlatformSupport>
-       webrtc_audio_device_platform_support_;
-   Persistent<MockRTCPeerConnectionHandlerClient> mock_client_;
-@@ -1170,4 +1170,32 @@ TEST_F(RTCPeerConnectionHandlerTest,
-   observer->OnIceCandidate(native_candidate.get());
- }
- 
-+TEST_F(RTCPeerConnectionHandlerTest,
-+       OnIceCandidateAfterClientGarbageCollectionDoesNothing) {
-+  testing::InSequence sequence;
-+  EXPECT_CALL(*mock_tracker_.Get(),
-+              TrackAddIceCandidate(pc_handler_.get(), _,
-+                                   PeerConnectionTracker::kSourceLocal, true))
-+      .Times(0);
-+
-+  std::unique_ptr<webrtc::IceCandidateInterface> native_candidate(
-+      mock_dependency_factory_->CreateIceCandidate("sdpMid", 1, kDummySdp));
-+  mock_client_ = nullptr;
-+  WebHeap::CollectAllGarbageForTesting();
-+  pc_handler_->observer()->OnIceCandidate(native_candidate.get());
-+  RunMessageLoopsUntilIdle();
-+}
-+
-+TEST_F(RTCPeerConnectionHandlerTest,
-+       OnIceCandidateAfterClientGarbageCollectionFails) {
-+  DummyExceptionStateForTesting exception_state;
-+  auto pc_handler = CreateRTCPeerConnectionHandlerUnderTest();
-+  mock_client_ = nullptr;
-+  WebHeap::CollectAllGarbageForTesting();
-+  EXPECT_FALSE(pc_handler->Initialize(
-+      /*context=*/nullptr, webrtc::PeerConnectionInterface::RTCConfiguration(),
-+      /*media_constraints=*/nullptr,
-+      /*frame=*/nullptr, exception_state));
-+}
-+
- }  // namespace blink

+ 0 - 125
patches/chromium/cherry-pick-91a02d67a83d.patch

@@ -1,125 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jean-Philippe Gravel <[email protected]>
-Date: Tue, 23 Jan 2024 21:37:50 +0000
-Subject: Fix use-after-free in DrawTextInternal
-
-DrawTextInternal was calling GetOrCreatePaintCanvas multiple times,
-once at the start of the function, once inside of the
-BaseRenderingContext2DAutoRestoreSkCanvas helper class and once in the
-Draw call. GetOrCreatePaintCanvas destroys the canvas resource provider
-if the GPU context is lost. If this happens on the second call to
-GetOrCreatePaintCanvas, destroying the resource provider will
-invalidate the cc::PaintCanvas returned by the first call to
-GetOrCreatePaintCanvas.
-
-The GPU process can technically crash at any point during the renderer
-process execution (perhaps because of something another renderer
-process did). We therefore have to assume that any call to
-GetOrCreatePaintCanvas can invalidate previously returned
-cc::PaintCanvas.
-
-(cherry picked from commit d4a197e4913f8e5072263b59aedc29f2b5af3e93)
-
-Change-Id: Ifa77735ab1b2b55b3d494f886b8566299937f6fe
-Fixed: 1511567
-Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5198419
-Reviewed-by: Fernando Serboncini <[email protected]>
-Commit-Queue: Jean-Philippe Gravel <[email protected]>
-Cr-Original-Commit-Position: refs/heads/main@{#1248204}
-Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5230237
-Bot-Commit: Rubber Stamper <[email protected]>
-Cr-Commit-Position: refs/branch-heads/6099@{#1859}
-Cr-Branched-From: e6ee4500f7d6549a9ac1354f8d056da49ef406be-refs/heads/main@{#1217362}
-
-diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
-index cf74c803a0d4b5a3229f2ebc9d8637ba4294f6a9..aa61546c345d0de41f36f372fb9981a8881269d6 100644
---- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
-+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
-@@ -2709,40 +2709,6 @@ const Font& BaseRenderingContext2D::AccessFont(HTMLCanvasElement* canvas) {
-   return GetState().GetFont();
- }
- 
--namespace {
--
--// Drawing methods need to use this instead of SkAutoCanvasRestore in case
--// overdraw detection substitutes the recording canvas (to discard overdrawn
--// draw calls).
--class BaseRenderingContext2DAutoRestoreSkCanvas {
--  STACK_ALLOCATED();
--
-- public:
--  explicit BaseRenderingContext2DAutoRestoreSkCanvas(
--      BaseRenderingContext2D* context)
--      : context_(context) {
--    DCHECK(context_);
--    cc::PaintCanvas* c = context_->GetOrCreatePaintCanvas();
--    if (c) {
--      save_count_ = c->getSaveCount();
--    }
--  }
--
--  ~BaseRenderingContext2DAutoRestoreSkCanvas() {
--    cc::PaintCanvas* c = context_->GetOrCreatePaintCanvas();
--    if (c) {
--      c->restoreToCount(save_count_);
--    }
--    context_->ValidateStateStack();
--  }
--
-- private:
--  BaseRenderingContext2D* context_;
--  int save_count_ = 0;
--};
--
--}  // namespace
--
- void BaseRenderingContext2D::DrawTextInternal(
-     const String& text,
-     double x,
-@@ -2763,8 +2729,10 @@ void BaseRenderingContext2D::DrawTextInternal(
-     canvas->GetDocument().UpdateStyleAndLayoutTreeForNode(
-         canvas, DocumentUpdateReason::kCanvas);
-   }
--  cc::PaintCanvas* c = GetOrCreatePaintCanvas();
--  if (!c) {
-+
-+  // Abort if we don't have a paint canvas (e.g. the context was lost).
-+  cc::PaintCanvas* paint_canvas = GetOrCreatePaintCanvas();
-+  if (!paint_canvas) {
-     return;
-   }
- 
-@@ -2835,14 +2803,13 @@ void BaseRenderingContext2D::DrawTextInternal(
-     InflateStrokeRect(bounds);
-   }
- 
--  BaseRenderingContext2DAutoRestoreSkCanvas state_restorer(this);
-   if (use_max_width) {
--    c->save();
-+    paint_canvas->save();
-     // We draw when fontWidth is 0 so compositing operations (eg, a "copy" op)
-     // still work. As the width of canvas is scaled, so text can be scaled to
-     // match the given maxwidth, update text location so it appears on desired
-     // place.
--    c->scale(ClampTo<float>(width / font_width), 1);
-+    paint_canvas->scale(ClampTo<float>(width / font_width), 1);
-     location.set_x(location.x() / ClampTo<float>(width / font_width));
-   }
- 
-@@ -2873,6 +2840,16 @@ void BaseRenderingContext2D::DrawTextInternal(
-       { return false; },
-       bounds, paint_type, CanvasRenderingContext2DState::kNoImage,
-       CanvasPerformanceMonitor::DrawType::kText);
-+
-+  if (use_max_width) {
-+    // Cannot use `paint_canvas` in case recording canvas was substituted or
-+    // destroyed during draw call.
-+    cc::PaintCanvas* c = GetPaintCanvas();
-+    if (c) {
-+      c->restore();
-+    }
-+  }
-+  ValidateStateStack();
- }
- 
- TextMetrics* BaseRenderingContext2D::measureText(const String& text) {