mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-26 10:08:06 +02:00
JS - Collect and execute actions at doc and pages level
* the goal is to execute actions like Open or OpenAction * can be tested with issue6106.pdf (auto-print) * once #12701 is merged, we can add page actions
This commit is contained in:
parent
142f131ee1
commit
1e2173f038
18 changed files with 829 additions and 125 deletions
|
@ -21,11 +21,9 @@ import {
|
|||
AnnotationReplyType,
|
||||
AnnotationType,
|
||||
assert,
|
||||
bytesToString,
|
||||
escapeString,
|
||||
getModificationDate,
|
||||
isString,
|
||||
objectSize,
|
||||
OPS,
|
||||
shadow,
|
||||
stringToPDFString,
|
||||
|
@ -34,17 +32,9 @@ import {
|
|||
warn,
|
||||
} from "../shared/util.js";
|
||||
import { Catalog, FileSpec, ObjectLoader } from "./obj.js";
|
||||
import {
|
||||
Dict,
|
||||
isDict,
|
||||
isName,
|
||||
isRef,
|
||||
isStream,
|
||||
Name,
|
||||
RefSet,
|
||||
} from "./primitives.js";
|
||||
import { collectActions, getInheritableProperty } from "./core_utils.js";
|
||||
import { Dict, isDict, isName, isRef, isStream, Name } from "./primitives.js";
|
||||
import { ColorSpace } from "./colorspace.js";
|
||||
import { getInheritableProperty } from "./core_utils.js";
|
||||
import { OperatorList } from "./operator_list.js";
|
||||
import { StringStream } from "./stream.js";
|
||||
import { writeDict } from "./writer.js";
|
||||
|
@ -977,7 +967,7 @@ class WidgetAnnotation extends Annotation {
|
|||
|
||||
data.annotationType = AnnotationType.WIDGET;
|
||||
data.fieldName = this._constructFieldName(dict);
|
||||
data.actions = this._collectActions(params.xref, dict);
|
||||
data.actions = collectActions(params.xref, dict, AnnotationActionEventType);
|
||||
|
||||
const fieldValue = getInheritableProperty({
|
||||
dict,
|
||||
|
@ -1459,78 +1449,6 @@ class WidgetAnnotation extends Annotation {
|
|||
return localResources || Dict.empty;
|
||||
}
|
||||
|
||||
_collectJS(entry, xref, list, parents) {
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = null;
|
||||
if (isRef(entry)) {
|
||||
if (parents.has(entry)) {
|
||||
// If we've already found entry then we've a cycle.
|
||||
return;
|
||||
}
|
||||
parent = entry;
|
||||
parents.put(parent);
|
||||
entry = xref.fetch(entry);
|
||||
}
|
||||
if (Array.isArray(entry)) {
|
||||
for (const element of entry) {
|
||||
this._collectJS(element, xref, list, parents);
|
||||
}
|
||||
} else if (entry instanceof Dict) {
|
||||
if (isName(entry.get("S"), "JavaScript") && entry.has("JS")) {
|
||||
const js = entry.get("JS");
|
||||
let code;
|
||||
if (isStream(js)) {
|
||||
code = bytesToString(js.getBytes());
|
||||
} else {
|
||||
code = js;
|
||||
}
|
||||
code = stringToPDFString(code);
|
||||
if (code) {
|
||||
list.push(code);
|
||||
}
|
||||
}
|
||||
this._collectJS(entry.getRaw("Next"), xref, list, parents);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
parents.remove(parent);
|
||||
}
|
||||
}
|
||||
|
||||
_collectActions(xref, dict) {
|
||||
const actions = Object.create(null);
|
||||
if (dict.has("AA")) {
|
||||
const additionalActions = dict.get("AA");
|
||||
for (const key of additionalActions.getKeys()) {
|
||||
const action = AnnotationActionEventType[key];
|
||||
if (!action) {
|
||||
continue;
|
||||
}
|
||||
const actionDict = additionalActions.getRaw(key);
|
||||
const parents = new RefSet();
|
||||
const list = [];
|
||||
this._collectJS(actionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions[action] = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect the Action if any (we may have one on pushbutton).
|
||||
if (dict.has("A")) {
|
||||
const actionDict = dict.get("A");
|
||||
const parents = new RefSet();
|
||||
const list = [];
|
||||
this._collectJS(actionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions.Action = list;
|
||||
}
|
||||
}
|
||||
return objectSize(actions) > 0 ? actions : null;
|
||||
}
|
||||
|
||||
getFieldObject() {
|
||||
if (this.data.fieldType === "Sig") {
|
||||
return {
|
||||
|
|
|
@ -13,7 +13,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert, BaseException, warn } from "../shared/util.js";
|
||||
import {
|
||||
assert,
|
||||
BaseException,
|
||||
bytesToString,
|
||||
objectSize,
|
||||
stringToPDFString,
|
||||
warn,
|
||||
} from "../shared/util.js";
|
||||
import { Dict, isName, isRef, isStream, RefSet } from "./primitives.js";
|
||||
|
||||
function getLookupTableFactory(initializer) {
|
||||
let lookup;
|
||||
|
@ -240,7 +248,80 @@ function escapePDFName(str) {
|
|||
return buffer.join("");
|
||||
}
|
||||
|
||||
function _collectJS(entry, xref, list, parents) {
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = null;
|
||||
if (isRef(entry)) {
|
||||
if (parents.has(entry)) {
|
||||
// If we've already found entry then we've a cycle.
|
||||
return;
|
||||
}
|
||||
parent = entry;
|
||||
parents.put(parent);
|
||||
entry = xref.fetch(entry);
|
||||
}
|
||||
if (Array.isArray(entry)) {
|
||||
for (const element of entry) {
|
||||
_collectJS(element, xref, list, parents);
|
||||
}
|
||||
} else if (entry instanceof Dict) {
|
||||
if (isName(entry.get("S"), "JavaScript") && entry.has("JS")) {
|
||||
const js = entry.get("JS");
|
||||
let code;
|
||||
if (isStream(js)) {
|
||||
code = bytesToString(js.getBytes());
|
||||
} else {
|
||||
code = js;
|
||||
}
|
||||
code = stringToPDFString(code);
|
||||
if (code) {
|
||||
list.push(code);
|
||||
}
|
||||
}
|
||||
_collectJS(entry.getRaw("Next"), xref, list, parents);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
parents.remove(parent);
|
||||
}
|
||||
}
|
||||
|
||||
function collectActions(xref, dict, eventType) {
|
||||
const actions = Object.create(null);
|
||||
if (dict.has("AA")) {
|
||||
const additionalActions = dict.get("AA");
|
||||
for (const key of additionalActions.getKeys()) {
|
||||
const action = eventType[key];
|
||||
if (!action) {
|
||||
continue;
|
||||
}
|
||||
const actionDict = additionalActions.getRaw(key);
|
||||
const parents = new RefSet();
|
||||
const list = [];
|
||||
_collectJS(actionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions[action] = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect the Action if any (we may have one on pushbutton).
|
||||
if (dict.has("A")) {
|
||||
const actionDict = dict.get("A");
|
||||
const parents = new RefSet();
|
||||
const list = [];
|
||||
_collectJS(actionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions.Action = list;
|
||||
}
|
||||
}
|
||||
return objectSize(actions) > 0 ? actions : null;
|
||||
}
|
||||
|
||||
export {
|
||||
collectActions,
|
||||
escapePDFName,
|
||||
getLookupTableFactory,
|
||||
getArrayLookupTableFactory,
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
isNum,
|
||||
isString,
|
||||
OPS,
|
||||
PageActionEventType,
|
||||
shadow,
|
||||
stringToBytes,
|
||||
stringToPDFString,
|
||||
|
@ -42,6 +43,7 @@ import {
|
|||
Ref,
|
||||
} from "./primitives.js";
|
||||
import {
|
||||
collectActions,
|
||||
getInheritableProperty,
|
||||
isWhiteSpace,
|
||||
MissingDataException,
|
||||
|
@ -467,6 +469,16 @@ class Page {
|
|||
|
||||
return shadow(this, "_parsedAnnotations", parsedAnnotations);
|
||||
}
|
||||
|
||||
get jsActions() {
|
||||
const actions = collectActions(
|
||||
this.xref,
|
||||
this.pageDict,
|
||||
PageActionEventType
|
||||
);
|
||||
|
||||
return shadow(this, "jsActions", actions);
|
||||
}
|
||||
}
|
||||
|
||||
const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
bytesToString,
|
||||
createPromiseCapability,
|
||||
createValidAbsoluteUrl,
|
||||
DocumentActionEventType,
|
||||
FormatError,
|
||||
info,
|
||||
InvalidPDFException,
|
||||
|
@ -47,13 +48,14 @@ import {
|
|||
RefSet,
|
||||
RefSetCache,
|
||||
} from "./primitives.js";
|
||||
import { Lexer, Parser } from "./parser.js";
|
||||
import {
|
||||
collectActions,
|
||||
MissingDataException,
|
||||
toRomanNumerals,
|
||||
XRefEntryException,
|
||||
XRefParseException,
|
||||
} from "./core_utils.js";
|
||||
import { Lexer, Parser } from "./parser.js";
|
||||
import { CipherTransformFactory } from "./crypto.js";
|
||||
import { ColorSpace } from "./colorspace.js";
|
||||
import { GlobalImageCache } from "./image_utils.js";
|
||||
|
@ -873,11 +875,11 @@ class Catalog {
|
|||
return shadow(this, "attachments", attachments);
|
||||
}
|
||||
|
||||
get javaScript() {
|
||||
_collectJavaScript() {
|
||||
const obj = this._catDict.get("Names");
|
||||
|
||||
let javaScript = null;
|
||||
function appendIfJavaScriptDict(jsDict) {
|
||||
function appendIfJavaScriptDict(name, jsDict) {
|
||||
const type = jsDict.get("S");
|
||||
if (!isName(type, "JavaScript")) {
|
||||
return;
|
||||
|
@ -890,10 +892,10 @@ class Catalog {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!javaScript) {
|
||||
javaScript = [];
|
||||
if (javaScript === null) {
|
||||
javaScript = Object.create(null);
|
||||
}
|
||||
javaScript.push(stringToPDFString(js));
|
||||
javaScript[name] = stringToPDFString(js);
|
||||
}
|
||||
|
||||
if (obj && obj.has("JavaScript")) {
|
||||
|
@ -904,7 +906,7 @@ class Catalog {
|
|||
// defensive so we don't cause errors on document load.
|
||||
const jsDict = names[name];
|
||||
if (isDict(jsDict)) {
|
||||
appendIfJavaScriptDict(jsDict);
|
||||
appendIfJavaScriptDict(name, jsDict);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -912,10 +914,43 @@ class Catalog {
|
|||
// Append OpenAction "JavaScript" actions to the JavaScript array.
|
||||
const openAction = this._catDict.get("OpenAction");
|
||||
if (isDict(openAction) && isName(openAction.get("S"), "JavaScript")) {
|
||||
appendIfJavaScriptDict(openAction);
|
||||
appendIfJavaScriptDict("OpenAction", openAction);
|
||||
}
|
||||
|
||||
return shadow(this, "javaScript", javaScript);
|
||||
return javaScript;
|
||||
}
|
||||
|
||||
get javaScript() {
|
||||
const javaScript = this._collectJavaScript();
|
||||
return shadow(
|
||||
this,
|
||||
"javaScript",
|
||||
javaScript ? Object.values(javaScript) : null
|
||||
);
|
||||
}
|
||||
|
||||
get jsActions() {
|
||||
const js = this._collectJavaScript();
|
||||
let actions = collectActions(
|
||||
this.xref,
|
||||
this._catDict,
|
||||
DocumentActionEventType
|
||||
);
|
||||
|
||||
if (!actions && js) {
|
||||
actions = Object.create(null);
|
||||
}
|
||||
if (actions && js) {
|
||||
for (const [key, val] of Object.entries(js)) {
|
||||
if (key in actions) {
|
||||
actions[key].push(val);
|
||||
} else {
|
||||
actions[key] = [val];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shadow(this, "jsActions", actions);
|
||||
}
|
||||
|
||||
fontFallback(id, handler) {
|
||||
|
|
|
@ -481,6 +481,16 @@ class WorkerMessageHandler {
|
|||
return pdfManager.ensureCatalog("javaScript");
|
||||
});
|
||||
|
||||
handler.on("GetDocJSActions", function wphSetupGetDocJSActions(data) {
|
||||
return pdfManager.ensureCatalog("jsActions");
|
||||
});
|
||||
|
||||
handler.on("GetPageJSActions", function ({ pageIndex }) {
|
||||
return pdfManager.getPage(pageIndex).then(function (page) {
|
||||
return page.jsActions;
|
||||
});
|
||||
});
|
||||
|
||||
handler.on("GetOutline", function wphSetupGetOutline(data) {
|
||||
return pdfManager.ensureCatalog("documentOutline");
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue