Skip to content

Commit 757636d

Browse files
committed
Convert the remaining functions in src/core/primitives.js to use standard classes
This patch was tested using the PDF file from issue 2618, i.e. https://bug570667.bugzilla-attachments.gnome.org/attachment.cgi?id=226471, with the following manifest file: ``` [ { "id": "issue2618", "file": "../web/pdfs/issue2618.pdf", "md5": "", "rounds": 50, "type": "eq" } ] ``` which gave the following results when comparing this patch against the `master` branch: ``` -- Grouped By browser, stat -- browser | stat | Count | Baseline(ms) | Current(ms) | +/- | % | Result(P<.05) ------- | ------------ | ----- | ------------ | ----------- | --- | ---- | ------------- firefox | Overall | 50 | 3417 | 3426 | 9 | 0.27 | firefox | Page Request | 50 | 1 | 1 | 0 | 5.41 | firefox | Rendering | 50 | 3416 | 3426 | 9 | 0.27 | ``` Based on these results, there's no significant performance regression from using standard classes and this patch should thus be OK.
1 parent dda1a9a commit 757636d

File tree

2 files changed

+139
-145
lines changed

2 files changed

+139
-145
lines changed

src/core/primitives.js

+137-143
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { assert, unreachable } from "../shared/util.js";
16+
import { assert, shadow, unreachable } from "../shared/util.js";
1717
import { BaseStream } from "./base_stream.js";
1818

1919
const EOF = {};
@@ -22,21 +22,21 @@ const Name = (function NameClosure() {
2222
let nameCache = Object.create(null);
2323

2424
// eslint-disable-next-line no-shadow
25-
function Name(name) {
26-
this.name = name;
27-
}
28-
29-
Name.prototype = {};
25+
class Name {
26+
constructor(name) {
27+
this.name = name;
28+
}
3029

31-
Name.get = function Name_get(name) {
32-
const nameValue = nameCache[name];
33-
// eslint-disable-next-line no-restricted-syntax
34-
return nameValue ? nameValue : (nameCache[name] = new Name(name));
35-
};
30+
static get(name) {
31+
const nameValue = nameCache[name];
32+
// eslint-disable-next-line no-restricted-syntax
33+
return nameValue ? nameValue : (nameCache[name] = new Name(name));
34+
}
3635

37-
Name._clearCache = function () {
38-
nameCache = Object.create(null);
39-
};
36+
static _clearCache() {
37+
nameCache = Object.create(null);
38+
}
39+
}
4040

4141
return Name;
4242
})();
@@ -45,142 +45,138 @@ const Cmd = (function CmdClosure() {
4545
let cmdCache = Object.create(null);
4646

4747
// eslint-disable-next-line no-shadow
48-
function Cmd(cmd) {
49-
this.cmd = cmd;
50-
}
51-
52-
Cmd.prototype = {};
48+
class Cmd {
49+
constructor(cmd) {
50+
this.cmd = cmd;
51+
}
5352

54-
Cmd.get = function Cmd_get(cmd) {
55-
const cmdValue = cmdCache[cmd];
56-
// eslint-disable-next-line no-restricted-syntax
57-
return cmdValue ? cmdValue : (cmdCache[cmd] = new Cmd(cmd));
58-
};
53+
static get(cmd) {
54+
const cmdValue = cmdCache[cmd];
55+
// eslint-disable-next-line no-restricted-syntax
56+
return cmdValue ? cmdValue : (cmdCache[cmd] = new Cmd(cmd));
57+
}
5958

60-
Cmd._clearCache = function () {
61-
cmdCache = Object.create(null);
62-
};
59+
static _clearCache() {
60+
cmdCache = Object.create(null);
61+
}
62+
}
6363

6464
return Cmd;
6565
})();
6666

67-
const Dict = (function DictClosure() {
68-
const nonSerializable = function nonSerializableClosure() {
69-
return nonSerializable; // creating closure on some variable
70-
};
67+
const nonSerializable = function nonSerializableClosure() {
68+
return nonSerializable; // Creating closure on some variable.
69+
};
7170

72-
// xref is optional
73-
// eslint-disable-next-line no-shadow
74-
function Dict(xref) {
71+
class Dict {
72+
constructor(xref = null) {
7573
// Map should only be used internally, use functions below to access.
7674
this._map = Object.create(null);
7775
this.xref = xref;
7876
this.objId = null;
7977
this.suppressEncryption = false;
80-
this.__nonSerializable__ = nonSerializable; // disable cloning of the Dict
78+
this.__nonSerializable__ = nonSerializable; // Disable cloning of the Dict.
8179
}
8280

83-
Dict.prototype = {
84-
assignXref: function Dict_assignXref(newXref) {
85-
this.xref = newXref;
86-
},
81+
assignXref(newXref) {
82+
this.xref = newXref;
83+
}
8784

88-
get size() {
89-
return Object.keys(this._map).length;
90-
},
85+
get size() {
86+
return Object.keys(this._map).length;
87+
}
9188

92-
// automatically dereferences Ref objects
93-
get(key1, key2, key3) {
94-
let value = this._map[key1];
95-
if (value === undefined && key2 !== undefined) {
96-
value = this._map[key2];
97-
if (value === undefined && key3 !== undefined) {
98-
value = this._map[key3];
99-
}
89+
// Automatically dereferences Ref objects.
90+
get(key1, key2, key3) {
91+
let value = this._map[key1];
92+
if (value === undefined && key2 !== undefined) {
93+
value = this._map[key2];
94+
if (value === undefined && key3 !== undefined) {
95+
value = this._map[key3];
10096
}
101-
if (value instanceof Ref && this.xref) {
102-
return this.xref.fetch(value, this.suppressEncryption);
103-
}
104-
return value;
105-
},
106-
107-
// Same as get(), but returns a promise and uses fetchIfRefAsync().
108-
async getAsync(key1, key2, key3) {
109-
let value = this._map[key1];
110-
if (value === undefined && key2 !== undefined) {
111-
value = this._map[key2];
112-
if (value === undefined && key3 !== undefined) {
113-
value = this._map[key3];
114-
}
115-
}
116-
if (value instanceof Ref && this.xref) {
117-
return this.xref.fetchAsync(value, this.suppressEncryption);
118-
}
119-
return value;
120-
},
97+
}
98+
if (value instanceof Ref && this.xref) {
99+
return this.xref.fetch(value, this.suppressEncryption);
100+
}
101+
return value;
102+
}
121103

122-
// Same as get(), but dereferences all elements if the result is an Array.
123-
getArray(key1, key2, key3) {
124-
let value = this.get(key1, key2, key3);
125-
if (!Array.isArray(value) || !this.xref) {
126-
return value;
127-
}
128-
value = value.slice(); // Ensure that we don't modify the Dict data.
129-
for (let i = 0, ii = value.length; i < ii; i++) {
130-
if (!(value[i] instanceof Ref)) {
131-
continue;
132-
}
133-
value[i] = this.xref.fetch(value[i], this.suppressEncryption);
104+
// Same as get(), but returns a promise and uses fetchIfRefAsync().
105+
async getAsync(key1, key2, key3) {
106+
let value = this._map[key1];
107+
if (value === undefined && key2 !== undefined) {
108+
value = this._map[key2];
109+
if (value === undefined && key3 !== undefined) {
110+
value = this._map[key3];
134111
}
112+
}
113+
if (value instanceof Ref && this.xref) {
114+
return this.xref.fetchAsync(value, this.suppressEncryption);
115+
}
116+
return value;
117+
}
118+
119+
// Same as get(), but dereferences all elements if the result is an Array.
120+
getArray(key1, key2, key3) {
121+
let value = this.get(key1, key2, key3);
122+
if (!Array.isArray(value) || !this.xref) {
135123
return value;
136-
},
137-
138-
// no dereferencing
139-
getRaw: function Dict_getRaw(key) {
140-
return this._map[key];
141-
},
142-
143-
getKeys: function Dict_getKeys() {
144-
return Object.keys(this._map);
145-
},
146-
147-
// no dereferencing
148-
getRawValues: function Dict_getRawValues() {
149-
return Object.values(this._map);
150-
},
151-
152-
set: function Dict_set(key, value) {
153-
if (
154-
(typeof PDFJSDev === "undefined" ||
155-
PDFJSDev.test("!PRODUCTION || TESTING")) &&
156-
value === undefined
157-
) {
158-
unreachable('Dict.set: The "value" cannot be undefined.');
124+
}
125+
value = value.slice(); // Ensure that we don't modify the Dict data.
126+
for (let i = 0, ii = value.length; i < ii; i++) {
127+
if (!(value[i] instanceof Ref)) {
128+
continue;
159129
}
160-
this._map[key] = value;
161-
},
130+
value[i] = this.xref.fetch(value[i], this.suppressEncryption);
131+
}
132+
return value;
133+
}
162134

163-
has: function Dict_has(key) {
164-
return this._map[key] !== undefined;
165-
},
135+
// No dereferencing.
136+
getRaw(key) {
137+
return this._map[key];
138+
}
166139

167-
forEach: function Dict_forEach(callback) {
168-
for (const key in this._map) {
169-
callback(key, this.get(key));
170-
}
171-
},
172-
};
140+
getKeys() {
141+
return Object.keys(this._map);
142+
}
143+
144+
// No dereferencing.
145+
getRawValues() {
146+
return Object.values(this._map);
147+
}
148+
149+
set(key, value) {
150+
if (
151+
(typeof PDFJSDev === "undefined" ||
152+
PDFJSDev.test("!PRODUCTION || TESTING")) &&
153+
value === undefined
154+
) {
155+
unreachable('Dict.set: The "value" cannot be undefined.');
156+
}
157+
this._map[key] = value;
158+
}
159+
160+
has(key) {
161+
return this._map[key] !== undefined;
162+
}
173163

174-
Dict.empty = (function () {
164+
forEach(callback) {
165+
for (const key in this._map) {
166+
callback(key, this.get(key));
167+
}
168+
}
169+
170+
static get empty() {
175171
const emptyDict = new Dict(null);
176172

177173
emptyDict.set = (key, value) => {
178174
unreachable("Should not call `set` on the empty dictionary.");
179175
};
180-
return emptyDict;
181-
})();
176+
return shadow(this, "empty", emptyDict);
177+
}
182178

183-
Dict.merge = function ({ xref, dictArray, mergeSubDicts = false }) {
179+
static merge({ xref, dictArray, mergeSubDicts = false }) {
184180
const mergedDict = new Dict(xref);
185181

186182
if (!mergeSubDicts) {
@@ -235,41 +231,39 @@ const Dict = (function DictClosure() {
235231
properties.clear();
236232

237233
return mergedDict.size > 0 ? mergedDict : Dict.empty;
238-
};
239-
240-
return Dict;
241-
})();
234+
}
235+
}
242236

243237
const Ref = (function RefClosure() {
244238
let refCache = Object.create(null);
245239

246240
// eslint-disable-next-line no-shadow
247-
function Ref(num, gen) {
248-
this.num = num;
249-
this.gen = gen;
250-
}
241+
class Ref {
242+
constructor(num, gen) {
243+
this.num = num;
244+
this.gen = gen;
245+
}
251246

252-
Ref.prototype = {
253-
toString: function Ref_toString() {
247+
toString() {
254248
// This function is hot, so we make the string as compact as possible.
255249
// |this.gen| is almost always zero, so we treat that case specially.
256250
if (this.gen === 0) {
257251
return `${this.num}R`;
258252
}
259253
return `${this.num}R${this.gen}`;
260-
},
261-
};
262-
263-
Ref.get = function (num, gen) {
264-
const key = gen === 0 ? `${num}R` : `${num}R${gen}`;
265-
const refValue = refCache[key];
266-
// eslint-disable-next-line no-restricted-syntax
267-
return refValue ? refValue : (refCache[key] = new Ref(num, gen));
268-
};
269-
270-
Ref._clearCache = function () {
271-
refCache = Object.create(null);
272-
};
254+
}
255+
256+
static get(num, gen) {
257+
const key = gen === 0 ? `${num}R` : `${num}R${gen}`;
258+
const refValue = refCache[key];
259+
// eslint-disable-next-line no-restricted-syntax
260+
return refValue ? refValue : (refCache[key] = new Ref(num, gen));
261+
}
262+
263+
static _clearCache() {
264+
refCache = Object.create(null);
265+
}
266+
}
273267

274268
return Ref;
275269
})();

test/unit/annotation_spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1192,8 +1192,8 @@ describe("annotation", function () {
11921192
expect(data.url).toBeUndefined();
11931193
expect(data.unsafeUrl).toBeUndefined();
11941194
expect(data.dest).toEqual([
1195-
{ num: 17, gen: 0 },
1196-
{ name: "XYZ" },
1195+
Ref.get(17, 0),
1196+
Name.get("XYZ"),
11971197
0,
11981198
841.89,
11991199
null,

0 commit comments

Comments
 (0)