1 /* Copyright 2022 Mozilla Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 import { GeckoViewActorParent } from "resource://gre/modules/GeckoViewActorParent.sys.mjs";
19 ChromeUtils.defineESModuleGetters(lazy, {
20 PdfJsTelemetry: "resource://pdf.js/PdfJsTelemetry.sys.mjs",
21 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
31 constructor(aBrowser) {
32 this.#browser = aBrowser;
37 this.#callbacks = null;
40 onEvent(aEvent, aData, aCallback) {
42 this.#browser.contentPrincipal.spec !==
43 "resource://pdf.js/web/viewer.html"
48 debug`onEvent: name=${aEvent}, data=${aData}`;
51 case "GeckoView:ClearMatches":
53 this.#browser.sendMessageToActor(
54 "PDFJS:Child:handleEvent",
62 case "GeckoView:DisplayMatches":
66 this.#browser.sendMessageToActor(
67 "PDFJS:Child:handleEvent",
69 type: "findhighlightallchange",
75 case "GeckoView:FindInPage":
76 const type = this.#getFindType(aData);
77 this.#browser.sendMessageToActor(
78 "PDFJS:Child:handleEvent",
85 this.#callbacks.push([aCallback, this.#state]);
92 query: this.#state?.query,
93 caseSensitive: !!aData.matchCase,
94 entireWord: !!aData.wholeWord,
95 highlightAll: !!aData.highlightAll,
96 findPrevious: !!aData.backwards,
97 matchDiacritics: !!aData.matchDiacritics,
100 // It's a new search.
101 newState.query = aData.searchString;
102 this.#state = newState;
103 this.#callbacks = [];
107 if (aData.searchString && this.#state.query !== aData.searchString) {
108 // The searched string has changed.
109 newState.query = aData.searchString;
110 this.#state = newState;
114 for (const [key, type] of [
115 ["caseSensitive", "findcasesensitivitychange"],
116 ["entireWord", "findentirewordchange"],
117 ["matchDiacritics", "finddiacriticmatchingchange"],
119 if (this.#state[key] !== newState[key]) {
120 this.#state = newState;
125 this.#state = newState;
129 updateMatchesCount(aData, aResult) {
131 (aResult !== Ci.nsITypeAheadFind.FIND_FOUND &&
132 aResult !== Ci.nsITypeAheadFind.FIND_WRAPPED) ||
138 if (this.#callbacks.length === 0) {
139 warn`There are no callbacks to use to set the matches count.`;
143 const [callback, state] = this.#callbacks.shift();
145 aData ||= { current: 0, total: -1 };
148 found: aData.total !== 0,
150 aData.total === 0 || aResult === Ci.nsITypeAheadFind.FIND_WRAPPED,
151 current: aData.current,
153 searchString: state.query,
157 backwards: state.findPrevious,
158 matchCase: state.caseSensitive,
159 wholeWord: state.entireWord,
162 callback.onSuccess(response);
173 constructor(aBrowser, eventDispatcher) {
174 this.#browser = aBrowser;
175 this.#eventDispatcher = eventDispatcher;
179 this.#callback = null;
182 onEvent(aEvent, aData, aCallback) {
184 this.#browser.contentPrincipal.spec !==
185 "resource://pdf.js/web/viewer.html"
190 lazy.PdfJsTelemetry.onGeckoview("save_as_pdf_tapped");
192 this.#callback = aCallback;
193 this.#browser.sendMessageToActor(
194 "PDFJS:Child:handleEvent",
202 async save({ blobUrl, filename, originalUrl }) {
204 const isPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
208 if (this.#callback) {
209 // "Save as PDF" from the share menu.
210 this.#callback.onSuccess({
217 // "Download" or "Open in app" from the pdf.js toolbar.
218 this.#eventDispatcher.sendRequest({
219 type: "GeckoView:SavePdf",
224 skipConfirmation: true,
225 requestExternalApp: false,
228 lazy.PdfJsTelemetry.onGeckoview("download_succeeded");
230 lazy.PdfJsTelemetry.onGeckoview("download_failed");
231 if (this.#callback) {
232 this.#callback?.onError(`Cannot save the pdf: ${e}.`);
234 warn`Cannot save the pdf because of: ${e}`;
242 export class GeckoViewPdfjsParent extends GeckoViewActorParent {
247 receiveMessage(aMsg) {
248 debug`receiveMessage: name=${aMsg.name}, data=${aMsg.data}`;
251 case "PDFJS:Parent:updateControlState":
252 return this.#updateControlState(aMsg);
253 case "PDFJS:Parent:updateMatchesCount":
254 return this.#updateMatchesCount(aMsg);
255 case "PDFJS:Parent:addEventListener":
256 return this.#addEventListener(aMsg);
257 case "PDFJS:Parent:saveURL":
258 return this.#save(aMsg);
259 case "PDFJS:Parent:getNimbus":
260 return this.#getExperimentFeature();
261 case "PDFJS:Parent:recordExposure":
262 return this.#recordExposure();
263 case "PDFJS:Parent:reportTelemetry":
264 return this.#reportTelemetry(aMsg);
275 if (!this.#findHandler) {
279 // The eventDispatcher is null when the tab is destroyed.
280 if (this.eventDispatcher) {
281 this.eventDispatcher.unregisterListener(this.#findHandler, [
282 "GeckoView:ClearMatches",
283 "GeckoView:DisplayMatches",
284 "GeckoView:FindInPage",
286 this.eventDispatcher.unregisterListener(this.#fileSaver, [
291 this.#findHandler.cleanup();
292 this.#findHandler = null;
293 this.#fileSaver.cleanup();
294 this.#fileSaver = null;
297 #addEventListener({ data: { aSupportsFind } }) {
298 this.#fileSaver = new FileSaver(this.browser, this.eventDispatcher);
299 this.eventDispatcher.registerListener(this.#fileSaver, [
303 if (!aSupportsFind) {
307 if (this.#findHandler) {
308 this.#findHandler.cleanup();
312 this.#findHandler = new FindHandler(this.browser);
313 this.eventDispatcher.registerListener(this.#findHandler, [
314 "GeckoView:ClearMatches",
315 "GeckoView:DisplayMatches",
316 "GeckoView:FindInPage",
320 #updateMatchesCount({ data }) {
321 this.#findHandler.updateMatchesCount(data, Ci.nsITypeAheadFind.FIND_FOUND);
324 #updateControlState({ data: { matchesCount, result } }) {
325 this.#findHandler.updateMatchesCount(matchesCount, result);
329 this.#fileSaver.save(data);
332 async #getExperimentFeature() {
335 const experimentActor = this.window.moduleManager.getActor(
336 "GeckoViewExperimentDelegate"
338 result = await experimentActor.getExperimentFeature("pdfjs");
340 warn`Cannot get experiment feature: ${e}`;
342 this.sendAsyncMessage("PDFJS:Child:getNimbus", result);
345 async #recordExposure() {
347 const experimentActor = this.window.moduleManager.getActor(
348 "GeckoViewExperimentDelegate"
350 await experimentActor.recordExposure("pdfjs");
352 warn`Cannot record experiment exposure: ${e}`;
356 #reportTelemetry(aMsg) {
357 lazy.PdfJsTelemetry.report(aMsg.data);
361 const { debug, warn } = GeckoViewPdfjsParent.initLogging(
362 "GeckoViewPdfjsParent"