Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS -- Plug PageOpen and PageClose actions #12816

Merged
merged 1 commit into from
Jan 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1136,9 +1136,8 @@ class PDFPageProxy {
}

/**
* @param {GetAnnotationsParameters} params - Annotation parameters.
* @returns {Promise<Array<any>>} A promise that is resolved with an
* {Array} of the annotation objects.
* @returns {Promise<Object>} A promise that is resolved with an
* {Object} with JS actions.
*/
getJSActions() {
if (!this._jsActionsPromise) {
Expand Down
23 changes: 15 additions & 8 deletions src/scripting_api/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Doc extends PDFObject {
this._zoom = data.zoom || 100;
this._actions = createActionsMap(data.actions);
this._globalEval = data.globalEval;
this._pageActions = new Map();
}

_dispatchDocEvent(name) {
Expand All @@ -114,22 +115,28 @@ class Doc extends PDFObject {
}
}

_dispatchPageEvent(name, action, pageNumber) {
_dispatchPageEvent(name, actions, pageNumber) {
if (name === "PageOpen") {
if (!this._pageActions.has(pageNumber)) {
this._pageActions.set(pageNumber, createActionsMap(actions));
}
this._pageNum = pageNumber - 1;
}

this._globalEval(action);
actions = this._pageActions.get(pageNumber)?.get(name);
if (actions) {
for (const action of actions) {
this._globalEval(action);
}
}
}

_runActions(name) {
if (!this._actions.has(name)) {
return;
}

const actions = this._actions.get(name);
for (const action of actions) {
this._globalEval(action);
if (actions) {
for (const action of actions) {
this._globalEval(action);
}
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/scripting_api/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,10 @@ class EventDispatcher {
}
if (id === "doc") {
this._document.obj._dispatchDocEvent(event.name);
}
if (id === "page") {
} else if (id === "page") {
this._document.obj._dispatchPageEvent(
event.name,
baseEvent.action,
baseEvent.actions,
baseEvent.pageNumber
);
}
Expand Down
95 changes: 93 additions & 2 deletions test/integration/scripting_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
const { clearInput, closePages, loadAndWait } = require("./test_utils.js");

describe("Interaction", () => {
async function actAndWaitForInput(page, selector, action) {
await clearInput(page, selector);
async function actAndWaitForInput(page, selector, action, clear = true) {
if (clear) {
await clearInput(page, selector);
}
await action();
await page.waitForFunction(
`document.querySelector("${selector.replace("\\", "\\\\")}").value !== ""`
Expand Down Expand Up @@ -307,6 +309,18 @@ describe("Interaction", () => {
if (process.platform === "win32" && browserName === "firefox") {
pending("Disabled in Firefox on Windows, because of bug 1662471.");
}
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);

await clearInput(page, "#\\34 7R");
await page.evaluate(_ => {
window.document.activeElement.blur();
});
await page.waitForFunction(
`document.querySelector("#\\\\34 7R").value === ""`
);

let text = await actAndWaitForInput(page, "#\\34 7R", async () => {
await page.click("#print");
});
Expand Down Expand Up @@ -337,6 +351,10 @@ describe("Interaction", () => {
it("must execute WillSave and DidSave actions", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);

try {
// Disable download in chrome
// (it leads to an error in firefox so the try...)
Expand All @@ -345,6 +363,13 @@ describe("Interaction", () => {
});
} catch (_) {}
await clearInput(page, "#\\34 7R");
await page.evaluate(_ => {
window.document.activeElement.blur();
});
await page.waitForFunction(
`document.querySelector("#\\\\34 7R").value === ""`
);

let text = await actAndWaitForInput(page, "#\\34 7R", async () => {
await page.click("#download");
});
Expand All @@ -360,4 +385,70 @@ describe("Interaction", () => {
);
});
});

describe("in doc_actions.pdf for page actions", () => {
let pages;

beforeAll(async () => {
pages = await loadAndWait("doc_actions.pdf", "#\\34 7R");
});

afterAll(async () => {
await closePages(pages);
});

it("must execute PageOpen and PageClose actions", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);

let text = await page.$eval("#\\34 7R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("PageOpen 1");

for (let run = 0; run < 5; run++) {
for (const ref of [18, 19, 20, 21, 47, 50]) {
await page.evaluate(refElem => {
const element = window.document.getElementById(`${refElem}R`);
if (element) {
element.value = "";
}
}, ref);
}

for (const [refOpen, refClose, pageNumOpen, pageNumClose] of [
[18, 50, 2, 1],
[21, 19, 3, 2],
[47, 20, 1, 3],
]) {
text = await actAndWaitForInput(
page,
`#\\3${Math.floor(refOpen / 10)} ${refOpen % 10}R`,
async () => {
await page.evaluate(refElem => {
window.document
.getElementById(`${refElem}R`)
.scrollIntoView();
}, refOpen);
},
false
);
expect(text)
.withContext(`In ${browserName}`)
.toEqual(`PageOpen ${pageNumOpen}`);

text = await page.$eval(
`#\\3${Math.floor(refClose / 10)} ${refClose % 10}R`,
el => el.value
);
expect(text)
.withContext(`In ${browserName}`)
.toEqual(`PageClose ${pageNumClose}`);
}
}
})
);
});
});
});
60 changes: 60 additions & 0 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,64 @@ const PDFViewerApplication = {
};
internalEvents.set("updatefromsandbox", updateFromSandbox);

const visitedPages = new Map();
const pageOpen = ({ pageNumber }) => {
visitedPages.set(
pageNumber,
(async () => {
// Avoid sending, and thus serializing, the `actions` data
// when the same page is open several times.
let actions = null;
if (!visitedPages.has(pageNumber)) {
// visitedPages doesn't contain pageNumber: first visit.

const pageView = this.pdfViewer.getPageView(
/* index = */ pageNumber - 1
);
if (pageView?.pdfPage) {
actions = await pageView.pdfPage.getJSActions();
} else {
actions = await pdfDocument.getPage(pageNumber).getJSActions();
}

if (pdfDocument !== this.pdfDocument) {
return; // The document was closed while the actions resolved.
}
}

this._scriptingInstance?.scripting.dispatchEventInSandbox({
id: "page",
name: "PageOpen",
pageNumber,
actions,
});
})()
);
};

const pageClose = async ({ pageNumber }) => {
const promise = visitedPages.get(pageNumber);
if (!promise) {
return;
}
visitedPages.set(pageNumber, null);

// Wait for PageOpen has been sent.
await promise;

if (pdfDocument !== this.pdfDocument) {
return; // The document was closed while the actions resolved.
}

this._scriptingInstance?.scripting.dispatchEventInSandbox({
id: "page",
name: "PageClose",
pageNumber,
});
};
internalEvents.set("pageopen", pageOpen);
internalEvents.set("pageclose", pageClose);

const dispatchEventInSandbox = ({ detail }) => {
scripting.dispatchEventInSandbox(detail);
};
Expand Down Expand Up @@ -1613,6 +1671,8 @@ const PDFViewerApplication = {
name: "Open",
});

await pageOpen({ pageNumber: this.pdfViewer.currentPageNumber });

// Used together with the integration-tests, see the `scriptingReady`
// getter, to enable awaiting full initialization of the scripting/sandbox.
// (Defer this slightly, to make absolutely sure that everything is done.)
Expand Down