Merge "ui: Move static initializers to their own file"
diff --git a/ui/src/common/immer_init.ts b/ui/src/common/immer_init.ts
deleted file mode 100644
index 9b8817c..0000000
--- a/ui/src/common/immer_init.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use size file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {enableMapSet, enablePatches, setAutoFreeze} from 'immer';
-
-export function initializeImmerJs() {
-  enablePatches();
-
-  // TODO(primiano): re-enable this, requires fixing some bugs that this bubbles
-  // up. This is a new feature of immer which freezes object after a produce().
-  // Unfortunately we piled up a bunch of bugs where we shallow-copy objects
-  // from the global state (which is frozen) and later try to update the copies.
-  // By doing so, we  accidentally the local copy of global state, which is
-  // supposed to be immutable.
-  setAutoFreeze(false);
-
-  enableMapSet();
-}
diff --git a/ui/src/common/query_result.ts b/ui/src/common/query_result.ts
index ea84c0c..ad5e1ee 100644
--- a/ui/src/common/query_result.ts
+++ b/ui/src/common/query_result.ts
@@ -47,15 +47,10 @@
 // the next batch (if any) within the QueryResultImpl.
 // This object is part of the API exposed to tracks / controllers.
 
-import protobuf from 'protobufjs/minimal';
+// Ensure protobuf is initialized.
+import '../core/static_initializers';
 
-// Disable Long.js support in protobuf. This seems to be enabled only in tests
-// but not in production code. In any case, for now we want casting to number
-// accepting the 2**53 limitation. This is consistent with passing
-// --force-number in the protobuf.js codegen invocation in //ui/BUILD.gn .
-// See also https://github.com/protobufjs/protobuf.js/issues/1253 .
-protobuf.util.Long = undefined as any;
-protobuf.configure();
+import protobuf from 'protobufjs/minimal';
 
 import {defer, Deferred} from '../base/deferred';
 import {assertExists, assertFalse, assertTrue} from '../base/logging';
diff --git a/ui/src/core/static_initializers.ts b/ui/src/core/static_initializers.ts
new file mode 100644
index 0000000..4959d68
--- /dev/null
+++ b/ui/src/core/static_initializers.ts
@@ -0,0 +1,65 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Occasionally libraries require configuration code to be called
+// before that library is used. This can become troublesome when the
+// library is used in many places through out the code base. To ensure
+// a consistent initialization this file exists as a place such code
+// can live. It's the first import in the root file of the bundle.
+
+// We use 'static initializers' (e.g. first import causes
+// initialization) rather than allowing the importer control over when
+// it happens. This sounds worse on paper but it works a lot better in
+// tests where (in JS) there is no global main() you can edit to do the
+// initialization. Instead any test where this is a problem can easily
+// stick an import at the top of the file.
+
+import {enableMapSet, enablePatches, setAutoFreeze} from 'immer';
+import protobuf from 'protobufjs/minimal';
+
+function initializeImmer() {
+  enablePatches();
+  enableMapSet();
+
+  // TODO(primiano): re-enable this, requires fixing some bugs that this bubbles
+  // up. This is a new feature of immer which freezes object after a produce().
+  // Unfortunately we piled up a bunch of bugs where we shallow-copy objects
+  // from the global state (which is frozen) and later try to update the copies.
+  // By doing so, we  accidentally the local copy of global state, which is
+  // supposed to be immutable.
+  setAutoFreeze(false);
+}
+
+function initializeProtobuf() {
+  // Disable Long.js support in protobuf. This seems to be enabled only in tests
+  // but not in production code. In any case, for now we want casting to number
+  // accepting the 2**53 limitation. This is consistent with passing
+  // --force-number in the protobuf.js codegen invocation in //ui/BUILD.gn .
+  // See also https://github.com/protobufjs/protobuf.js/issues/1253 .
+  protobuf.util.Long = undefined as any;
+  protobuf.configure();
+}
+
+let isInitialized = false;
+function initialize() {
+  if (isInitialized) {
+    throw new Error('initialize() should be called exactly once');
+  }
+  initializeImmer();
+  initializeProtobuf();
+  isInitialized = true;
+}
+
+// JS module semantics ensure this is happens only once.
+initialize();
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index b266f15..70bdedb 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Need to turn off Long
-import '../common/query_result';
+// Keep this import first.
+import '../core/static_initializers';
 
 import {Patch, produce} from 'immer';
 import m from 'mithril';
@@ -23,7 +23,6 @@
 import {Actions, DeferredAction, StateActions} from '../common/actions';
 import {createEmptyState} from '../common/empty_state';
 import {RECORDING_V2_FLAG} from '../common/feature_flags';
-import {initializeImmerJs} from '../common/immer_init';
 import {pluginManager, pluginRegistry} from '../common/plugins';
 import {State} from '../common/state';
 import {initWasm} from '../common/wasm_engine_proxy';
@@ -214,7 +213,6 @@
   const extensionLocalChannel = new MessageChannel();
 
   initWasm(globals.root);
-  initializeImmerJs();
   initController(extensionLocalChannel.port1);
 
   const dispatch = (action: DeferredAction) => {