mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	Original Revision: https://phabricator.services.mozilla.com/D222122 Differential Revision: https://phabricator.services.mozilla.com/D222966
		
			
				
	
	
		
			1160 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1160 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Copyright 2012 Mozilla Foundation
 | 
						|
 *
 | 
						|
 * 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.
 | 
						|
 */
 | 
						|
 | 
						|
const PDFJS_EVENT_ID = "pdf.js.message";
 | 
						|
const PDF_VIEWER_ORIGIN = "resource://pdf.js";
 | 
						|
const PDF_VIEWER_WEB_PAGE = "resource://pdf.js/web/viewer.html";
 | 
						|
const MAX_NUMBER_OF_PREFS = 50;
 | 
						|
const PDF_CONTENT_TYPE = "application/pdf";
 | 
						|
 | 
						|
// Preferences
 | 
						|
const caretBrowsingModePref = "accessibility.browsewithcaret";
 | 
						|
 | 
						|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | 
						|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
 | 
						|
  NetworkManager: "resource://pdf.js/PdfJsNetwork.sys.mjs",
 | 
						|
  PdfJs: "resource://pdf.js/PdfJs.sys.mjs",
 | 
						|
  PdfJsTelemetryContent: "resource://pdf.js/PdfJsTelemetry.sys.mjs",
 | 
						|
  PdfSandbox: "resource://pdf.js/PdfSandbox.sys.mjs",
 | 
						|
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
var Svc = {};
 | 
						|
XPCOMUtils.defineLazyServiceGetter(
 | 
						|
  Svc,
 | 
						|
  "mime",
 | 
						|
  "@mozilla.org/mime;1",
 | 
						|
  "nsIMIMEService"
 | 
						|
);
 | 
						|
XPCOMUtils.defineLazyServiceGetter(
 | 
						|
  Svc,
 | 
						|
  "handlers",
 | 
						|
  "@mozilla.org/uriloader/handler-service;1",
 | 
						|
  "nsIHandlerService"
 | 
						|
);
 | 
						|
 | 
						|
ChromeUtils.defineLazyGetter(lazy, "gOurBinary", () => {
 | 
						|
  let file = Services.dirsvc.get("XREExeF", Ci.nsIFile);
 | 
						|
  // Make sure to get the .app on macOS
 | 
						|
  if (AppConstants.platform == "macosx") {
 | 
						|
    while (file) {
 | 
						|
      if (/\.app\/?$/i.test(file.leafName)) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      file = file.parent;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return file;
 | 
						|
});
 | 
						|
 | 
						|
function log(aMsg) {
 | 
						|
  if (!Services.prefs.getBoolPref("pdfjs.pdfBugEnabled", false)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  var msg = "PdfStreamConverter.js: " + (aMsg.join ? aMsg.join("") : aMsg);
 | 
						|
  Services.console.logStringMessage(msg);
 | 
						|
  dump(msg + "\n");
 | 
						|
}
 | 
						|
 | 
						|
function getDOMWindow(aChannel, aPrincipal) {
 | 
						|
  var requestor = aChannel.notificationCallbacks
 | 
						|
    ? aChannel.notificationCallbacks
 | 
						|
    : aChannel.loadGroup.notificationCallbacks;
 | 
						|
  var win = requestor.getInterface(Ci.nsIDOMWindow);
 | 
						|
  // Ensure the window wasn't navigated to something that is not PDF.js.
 | 
						|
  if (!win.document.nodePrincipal.equals(aPrincipal)) {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
  return win;
 | 
						|
}
 | 
						|
 | 
						|
function getActor(window) {
 | 
						|
  try {
 | 
						|
    const actorName =
 | 
						|
      AppConstants.platform === "android" ? "GeckoViewPdfjs" : "Pdfjs";
 | 
						|
    return window.windowGlobalChild.getActor(actorName);
 | 
						|
  } catch (ex) {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function isValidMatchesCount(data) {
 | 
						|
  if (typeof data !== "object" || data === null) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  const { current, total } = data;
 | 
						|
  if (
 | 
						|
    typeof total !== "number" ||
 | 
						|
    total < 0 ||
 | 
						|
    typeof current !== "number" ||
 | 
						|
    current < 0 ||
 | 
						|
    current > total
 | 
						|
  ) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
// PDF data storage
 | 
						|
function PdfDataListener(length) {
 | 
						|
  this.length = length; // less than 0, if length is unknown
 | 
						|
  this.buffers = [];
 | 
						|
  this.loaded = 0;
 | 
						|
}
 | 
						|
 | 
						|
PdfDataListener.prototype = {
 | 
						|
  append: function PdfDataListener_append(chunk) {
 | 
						|
    // In most of the cases we will pass data as we receive it, but at the
 | 
						|
    // beginning of the loading we may accumulate some data.
 | 
						|
    this.buffers.push(chunk);
 | 
						|
    this.loaded += chunk.length;
 | 
						|
    if (this.length >= 0 && this.length < this.loaded) {
 | 
						|
      this.length = -1; // reset the length, server is giving incorrect one
 | 
						|
    }
 | 
						|
    this.onprogress(this.loaded, this.length >= 0 ? this.length : void 0);
 | 
						|
  },
 | 
						|
  readData: function PdfDataListener_readData() {
 | 
						|
    if (this.buffers.length === 0) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    if (this.buffers.length === 1) {
 | 
						|
      return this.buffers.pop();
 | 
						|
    }
 | 
						|
    // There are multiple buffers that need to be combined into a single
 | 
						|
    // buffer.
 | 
						|
    let combinedLength = 0;
 | 
						|
    for (let buffer of this.buffers) {
 | 
						|
      combinedLength += buffer.length;
 | 
						|
    }
 | 
						|
    let combinedArray = new Uint8Array(combinedLength);
 | 
						|
    let writeOffset = 0;
 | 
						|
    while (this.buffers.length) {
 | 
						|
      let buffer = this.buffers.shift();
 | 
						|
      combinedArray.set(buffer, writeOffset);
 | 
						|
      writeOffset += buffer.length;
 | 
						|
    }
 | 
						|
    return combinedArray;
 | 
						|
  },
 | 
						|
  get isDone() {
 | 
						|
    return !!this.isDataReady;
 | 
						|
  },
 | 
						|
  finish: function PdfDataListener_finish() {
 | 
						|
    this.isDataReady = true;
 | 
						|
    if (this.oncompleteCallback) {
 | 
						|
      this.oncompleteCallback(this.readData());
 | 
						|
    }
 | 
						|
  },
 | 
						|
  error: function PdfDataListener_error(errorCode) {
 | 
						|
    this.errorCode = errorCode;
 | 
						|
    if (this.oncompleteCallback) {
 | 
						|
      this.oncompleteCallback(null, errorCode);
 | 
						|
    }
 | 
						|
  },
 | 
						|
  onprogress() {},
 | 
						|
  get oncomplete() {
 | 
						|
    return this.oncompleteCallback;
 | 
						|
  },
 | 
						|
  set oncomplete(value) {
 | 
						|
    this.oncompleteCallback = value;
 | 
						|
    if (this.isDataReady) {
 | 
						|
      value(this.readData());
 | 
						|
    }
 | 
						|
    if (this.errorCode) {
 | 
						|
      value(null, this.errorCode);
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
class PrefObserver {
 | 
						|
  #domWindow;
 | 
						|
 | 
						|
  constructor(domWindow) {
 | 
						|
    this.#domWindow = domWindow;
 | 
						|
    this.#init();
 | 
						|
  }
 | 
						|
 | 
						|
  #init() {
 | 
						|
    Services.prefs.addObserver(
 | 
						|
      caretBrowsingModePref,
 | 
						|
      this,
 | 
						|
      /* aHoldWeak = */ true
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  observe(_aSubject, aTopic, aPrefName) {
 | 
						|
    if (aTopic != "nsPref:changed") {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    const actor = getActor(this.#domWindow);
 | 
						|
    if (!actor) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    const eventName = "updatedPreference";
 | 
						|
    switch (aPrefName) {
 | 
						|
      case caretBrowsingModePref:
 | 
						|
        actor.dispatchEvent(eventName, {
 | 
						|
          name: "supportsCaretBrowsingMode",
 | 
						|
          value: Services.prefs.getBoolPref(caretBrowsingModePref),
 | 
						|
        });
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  QueryInterface = ChromeUtils.generateQI([Ci.nsISupportsWeakReference]);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * All the privileged actions.
 | 
						|
 */
 | 
						|
class ChromeActions {
 | 
						|
  constructor(domWindow, contentDispositionFilename) {
 | 
						|
    this.domWindow = domWindow;
 | 
						|
    this.contentDispositionFilename = contentDispositionFilename;
 | 
						|
    this.sandbox = null;
 | 
						|
    this.unloadListener = null;
 | 
						|
    this.observer = new PrefObserver(domWindow);
 | 
						|
  }
 | 
						|
 | 
						|
  createSandbox(data, sendResponse) {
 | 
						|
    function sendResp(res) {
 | 
						|
      if (sendResponse) {
 | 
						|
        sendResponse(res);
 | 
						|
      }
 | 
						|
      return res;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!Services.prefs.getBoolPref("pdfjs.enableScripting", false)) {
 | 
						|
      return sendResp(false);
 | 
						|
    }
 | 
						|
 | 
						|
    if (this.sandbox !== null) {
 | 
						|
      return sendResp(true);
 | 
						|
    }
 | 
						|
 | 
						|
    try {
 | 
						|
      this.sandbox = new lazy.PdfSandbox(this.domWindow, data);
 | 
						|
    } catch (err) {
 | 
						|
      // If there's an error here, it means that something is really wrong
 | 
						|
      // on pdf.js side during sandbox initialization phase.
 | 
						|
      console.error(err);
 | 
						|
      return sendResp(false);
 | 
						|
    }
 | 
						|
 | 
						|
    this.unloadListener = () => {
 | 
						|
      this.destroySandbox();
 | 
						|
    };
 | 
						|
    this.domWindow.addEventListener("unload", this.unloadListener);
 | 
						|
 | 
						|
    return sendResp(true);
 | 
						|
  }
 | 
						|
 | 
						|
  dispatchEventInSandbox(event) {
 | 
						|
    if (this.sandbox) {
 | 
						|
      this.sandbox.dispatchEvent(event);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  dispatchAsyncEventInSandbox(event, sendResponse) {
 | 
						|
    this.dispatchEventInSandbox(event);
 | 
						|
    sendResponse();
 | 
						|
  }
 | 
						|
 | 
						|
  destroySandbox() {
 | 
						|
    if (this.sandbox) {
 | 
						|
      this.domWindow.removeEventListener("unload", this.unloadListener);
 | 
						|
      this.sandbox.destroy();
 | 
						|
      this.sandbox = null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  isInPrivateBrowsing() {
 | 
						|
    return lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.domWindow);
 | 
						|
  }
 | 
						|
 | 
						|
  getWindowOriginAttributes() {
 | 
						|
    try {
 | 
						|
      return this.domWindow.document.nodePrincipal.originAttributes;
 | 
						|
    } catch (err) {
 | 
						|
      return {};
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  download(data) {
 | 
						|
    const { originalUrl, options } = data;
 | 
						|
    const blobUrl = data.blobUrl || originalUrl;
 | 
						|
    let { filename } = data;
 | 
						|
    if (
 | 
						|
      typeof filename !== "string" ||
 | 
						|
      (!/\.pdf$/i.test(filename) && !data.isAttachment)
 | 
						|
    ) {
 | 
						|
      filename = "document.pdf";
 | 
						|
    }
 | 
						|
 | 
						|
    const actor = getActor(this.domWindow);
 | 
						|
    actor.sendAsyncMessage("PDFJS:Parent:saveURL", {
 | 
						|
      blobUrl,
 | 
						|
      originalUrl,
 | 
						|
      filename,
 | 
						|
      options: options || {},
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  getLocaleProperties(_data, sendResponse) {
 | 
						|
    const { requestedLocale, defaultLocale, isAppLocaleRTL } = Services.locale;
 | 
						|
    sendResponse({
 | 
						|
      lang: requestedLocale || defaultLocale,
 | 
						|
      isRTL: isAppLocaleRTL,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  supportsIntegratedFind() {
 | 
						|
    // Integrated find is only supported when we're not in a frame
 | 
						|
    return this.domWindow.windowGlobalChild.browsingContext.parent === null;
 | 
						|
  }
 | 
						|
 | 
						|
  getBrowserPrefs() {
 | 
						|
    return {
 | 
						|
      canvasMaxAreaInBytes: Services.prefs.getIntPref("gfx.max-alloc-size"),
 | 
						|
      isInAutomation: Cu.isInAutomation,
 | 
						|
      supportsDocumentFonts:
 | 
						|
        !!Services.prefs.getIntPref("browser.display.use_document_fonts") &&
 | 
						|
        Services.prefs.getBoolPref("gfx.downloadable_fonts.enabled"),
 | 
						|
      supportsIntegratedFind: this.supportsIntegratedFind(),
 | 
						|
      supportsMouseWheelZoomCtrlKey:
 | 
						|
        Services.prefs.getIntPref("mousewheel.with_control.action") === 3,
 | 
						|
      supportsMouseWheelZoomMetaKey:
 | 
						|
        Services.prefs.getIntPref("mousewheel.with_meta.action") === 3,
 | 
						|
      supportsPinchToZoom: Services.prefs.getBoolPref("apz.allow_zooming"),
 | 
						|
      supportsCaretBrowsingMode: Services.prefs.getBoolPref(
 | 
						|
        caretBrowsingModePref
 | 
						|
      ),
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  isMobile() {
 | 
						|
    return AppConstants.platform === "android";
 | 
						|
  }
 | 
						|
 | 
						|
  getNimbusExperimentData(_data, sendResponse) {
 | 
						|
    if (!this.isMobile()) {
 | 
						|
      sendResponse(null);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    const actor = getActor(this.domWindow);
 | 
						|
    actor.sendAsyncMessage("PDFJS:Parent:getNimbus");
 | 
						|
    Services.obs.addObserver(
 | 
						|
      {
 | 
						|
        observe(aSubject, aTopic) {
 | 
						|
          if (aTopic === "pdfjs-getNimbus") {
 | 
						|
            Services.obs.removeObserver(this, aTopic);
 | 
						|
            sendResponse(aSubject && JSON.stringify(aSubject.wrappedJSObject));
 | 
						|
          }
 | 
						|
        },
 | 
						|
      },
 | 
						|
      "pdfjs-getNimbus"
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  reportTelemetry(data) {
 | 
						|
    const actor = getActor(this.domWindow);
 | 
						|
    actor?.sendAsyncMessage("PDFJS:Parent:reportTelemetry", data);
 | 
						|
  }
 | 
						|
 | 
						|
  updateFindControlState(data) {
 | 
						|
    if (!this.supportsIntegratedFind()) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Verify what we're sending to the findbar.
 | 
						|
    var result = data.result;
 | 
						|
    var findPrevious = data.findPrevious;
 | 
						|
    var findPreviousType = typeof findPrevious;
 | 
						|
    if (
 | 
						|
      typeof result !== "number" ||
 | 
						|
      result < 0 ||
 | 
						|
      result > 3 ||
 | 
						|
      (findPreviousType !== "undefined" && findPreviousType !== "boolean")
 | 
						|
    ) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Allow the `matchesCount` property to be optional, and ensure that
 | 
						|
    // it's valid before including it in the data sent to the findbar.
 | 
						|
    let matchesCount = null;
 | 
						|
    if (isValidMatchesCount(data.matchesCount)) {
 | 
						|
      matchesCount = data.matchesCount;
 | 
						|
    }
 | 
						|
    // Same for the `rawQuery` property.
 | 
						|
    let rawQuery = null;
 | 
						|
    if (typeof data.rawQuery === "string") {
 | 
						|
      rawQuery = data.rawQuery;
 | 
						|
    }
 | 
						|
 | 
						|
    let actor = getActor(this.domWindow);
 | 
						|
    actor?.sendAsyncMessage("PDFJS:Parent:updateControlState", {
 | 
						|
      result,
 | 
						|
      findPrevious,
 | 
						|
      matchesCount,
 | 
						|
      rawQuery,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  updateFindMatchesCount(data) {
 | 
						|
    if (!this.supportsIntegratedFind()) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Verify what we're sending to the findbar.
 | 
						|
    if (!isValidMatchesCount(data)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let actor = getActor(this.domWindow);
 | 
						|
    actor?.sendAsyncMessage("PDFJS:Parent:updateMatchesCount", data);
 | 
						|
  }
 | 
						|
 | 
						|
  getPreferences(prefs, sendResponse) {
 | 
						|
    var defaultBranch = Services.prefs.getDefaultBranch("pdfjs.");
 | 
						|
    var currentPrefs = {},
 | 
						|
      numberOfPrefs = 0;
 | 
						|
    for (var key in prefs) {
 | 
						|
      if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
 | 
						|
        log(
 | 
						|
          "getPreferences - Exceeded the maximum number of preferences " +
 | 
						|
            "that is allowed to be fetched at once."
 | 
						|
        );
 | 
						|
        break;
 | 
						|
      } else if (!defaultBranch.getPrefType(key)) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      const prefName = `pdfjs.${key}`,
 | 
						|
        prefValue = prefs[key];
 | 
						|
      switch (typeof prefValue) {
 | 
						|
        case "boolean":
 | 
						|
          currentPrefs[key] = Services.prefs.getBoolPref(prefName, prefValue);
 | 
						|
          break;
 | 
						|
        case "number":
 | 
						|
          currentPrefs[key] = Services.prefs.getIntPref(prefName, prefValue);
 | 
						|
          break;
 | 
						|
        case "string":
 | 
						|
          currentPrefs[key] = Services.prefs.getStringPref(prefName, prefValue);
 | 
						|
          break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    sendResponse({
 | 
						|
      browserPrefs: this.getBrowserPrefs(),
 | 
						|
      prefs: currentPrefs,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Set the different editor states in order to be able to update the context
 | 
						|
   * menu.
 | 
						|
   * @param {Object} details
 | 
						|
   */
 | 
						|
  updateEditorStates({ details }) {
 | 
						|
    const doc = this.domWindow.document;
 | 
						|
    if (!doc.editorStates) {
 | 
						|
      doc.editorStates = {
 | 
						|
        isEditing: false,
 | 
						|
        isEmpty: true,
 | 
						|
        hasSomethingToUndo: false,
 | 
						|
        hasSomethingToRedo: false,
 | 
						|
        hasSelectedEditor: false,
 | 
						|
        hasSelectedText: false,
 | 
						|
      };
 | 
						|
    }
 | 
						|
    const { editorStates } = doc;
 | 
						|
    for (const [key, value] of Object.entries(details)) {
 | 
						|
      if (typeof value === "boolean" && key in editorStates) {
 | 
						|
        editorStates[key] = value;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * This is for range requests.
 | 
						|
 */
 | 
						|
class RangedChromeActions extends ChromeActions {
 | 
						|
  constructor(
 | 
						|
    domWindow,
 | 
						|
    contentDispositionFilename,
 | 
						|
    originalRequest,
 | 
						|
    rangeEnabled,
 | 
						|
    streamingEnabled,
 | 
						|
    dataListener
 | 
						|
  ) {
 | 
						|
    super(domWindow, contentDispositionFilename);
 | 
						|
    this.dataListener = dataListener;
 | 
						|
    this.originalRequest = originalRequest;
 | 
						|
    this.rangeEnabled = rangeEnabled;
 | 
						|
    this.streamingEnabled = streamingEnabled;
 | 
						|
 | 
						|
    this.pdfUrl = originalRequest.URI.spec;
 | 
						|
    this.contentLength = originalRequest.contentLength;
 | 
						|
 | 
						|
    // Pass all the headers from the original request through
 | 
						|
    var httpHeaderVisitor = {
 | 
						|
      headers: {},
 | 
						|
      visitHeader(aHeader, aValue) {
 | 
						|
        if (aHeader === "Range") {
 | 
						|
          // When loading the PDF from cache, firefox seems to set the Range
 | 
						|
          // request header to fetch only the unfetched portions of the file
 | 
						|
          // (e.g. 'Range: bytes=1024-'). However, we want to set this header
 | 
						|
          // manually to fetch the PDF in chunks.
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        this.headers[aHeader] = aValue;
 | 
						|
      },
 | 
						|
    };
 | 
						|
    if (originalRequest.visitRequestHeaders) {
 | 
						|
      originalRequest.visitRequestHeaders(httpHeaderVisitor);
 | 
						|
    }
 | 
						|
 | 
						|
    var self = this;
 | 
						|
    var xhr_onreadystatechange = function xhr_onreadystatechange() {
 | 
						|
      if (this.readyState === 1) {
 | 
						|
        // LOADING
 | 
						|
        var netChannel = this.channel;
 | 
						|
        // override this XMLHttpRequest's OriginAttributes with our cached parent window's
 | 
						|
        // OriginAttributes, as we are currently running under the SystemPrincipal
 | 
						|
        this.setOriginAttributes(self.getWindowOriginAttributes());
 | 
						|
        if (
 | 
						|
          "nsIPrivateBrowsingChannel" in Ci &&
 | 
						|
          netChannel instanceof Ci.nsIPrivateBrowsingChannel
 | 
						|
        ) {
 | 
						|
          var docIsPrivate = self.isInPrivateBrowsing();
 | 
						|
          netChannel.setPrivate(docIsPrivate);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
    var getXhr = function getXhr() {
 | 
						|
      var xhr = new XMLHttpRequest({ mozAnon: false });
 | 
						|
      xhr.addEventListener("readystatechange", xhr_onreadystatechange);
 | 
						|
      return xhr;
 | 
						|
    };
 | 
						|
 | 
						|
    this.networkManager = new lazy.NetworkManager(this.pdfUrl, {
 | 
						|
      httpHeaders: httpHeaderVisitor.headers,
 | 
						|
      getXhr,
 | 
						|
    });
 | 
						|
 | 
						|
    // If we are in range request mode, this means we manually issued xhr
 | 
						|
    // requests, which we need to abort when we leave the page
 | 
						|
    domWindow.addEventListener("unload", function unload(e) {
 | 
						|
      domWindow.removeEventListener(e.type, unload);
 | 
						|
      self.abortLoading();
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  initPassiveLoading() {
 | 
						|
    let data, done;
 | 
						|
    if (!this.streamingEnabled) {
 | 
						|
      this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
 | 
						|
      this.originalRequest = null;
 | 
						|
      data = this.dataListener.readData();
 | 
						|
      done = this.dataListener.isDone;
 | 
						|
      this.dataListener = null;
 | 
						|
    } else {
 | 
						|
      data = this.dataListener.readData();
 | 
						|
      done = this.dataListener.isDone;
 | 
						|
 | 
						|
      this.dataListener.onprogress = (loaded, total) => {
 | 
						|
        const chunk = this.dataListener.readData();
 | 
						|
 | 
						|
        this.domWindow.postMessage(
 | 
						|
          {
 | 
						|
            pdfjsLoadAction: "progressiveRead",
 | 
						|
            loaded,
 | 
						|
            total,
 | 
						|
            chunk,
 | 
						|
          },
 | 
						|
          PDF_VIEWER_ORIGIN,
 | 
						|
          chunk ? [chunk.buffer] : undefined
 | 
						|
        );
 | 
						|
      };
 | 
						|
      this.dataListener.oncomplete = () => {
 | 
						|
        if (!done && this.dataListener.isDone) {
 | 
						|
          this.domWindow.postMessage(
 | 
						|
            {
 | 
						|
              pdfjsLoadAction: "progressiveDone",
 | 
						|
            },
 | 
						|
            PDF_VIEWER_ORIGIN
 | 
						|
          );
 | 
						|
        }
 | 
						|
        this.dataListener = null;
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    this.domWindow.postMessage(
 | 
						|
      {
 | 
						|
        pdfjsLoadAction: "supportsRangedLoading",
 | 
						|
        rangeEnabled: this.rangeEnabled,
 | 
						|
        streamingEnabled: this.streamingEnabled,
 | 
						|
        pdfUrl: this.pdfUrl,
 | 
						|
        length: this.contentLength,
 | 
						|
        data,
 | 
						|
        done,
 | 
						|
        filename: this.contentDispositionFilename,
 | 
						|
      },
 | 
						|
      PDF_VIEWER_ORIGIN,
 | 
						|
      data ? [data.buffer] : undefined
 | 
						|
    );
 | 
						|
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  requestDataRange(args) {
 | 
						|
    if (!this.rangeEnabled) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    var begin = args.begin;
 | 
						|
    var end = args.end;
 | 
						|
    var domWindow = this.domWindow;
 | 
						|
    // TODO(mack): Support error handler. We're not currently not handling
 | 
						|
    // errors from chrome code for non-range requests, so this doesn't
 | 
						|
    // seem high-pri
 | 
						|
    this.networkManager.requestRange(begin, end, {
 | 
						|
      onDone: function RangedChromeActions_onDone({ begin, chunk }) {
 | 
						|
        domWindow.postMessage(
 | 
						|
          {
 | 
						|
            pdfjsLoadAction: "range",
 | 
						|
            begin,
 | 
						|
            chunk,
 | 
						|
          },
 | 
						|
          PDF_VIEWER_ORIGIN,
 | 
						|
          chunk ? [chunk.buffer] : undefined
 | 
						|
        );
 | 
						|
      },
 | 
						|
      onProgress: function RangedChromeActions_onProgress(evt) {
 | 
						|
        domWindow.postMessage(
 | 
						|
          {
 | 
						|
            pdfjsLoadAction: "rangeProgress",
 | 
						|
            loaded: evt.loaded,
 | 
						|
          },
 | 
						|
          PDF_VIEWER_ORIGIN
 | 
						|
        );
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  abortLoading() {
 | 
						|
    this.networkManager.abortAllRequests();
 | 
						|
    if (this.originalRequest) {
 | 
						|
      this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
 | 
						|
      this.originalRequest = null;
 | 
						|
    }
 | 
						|
    this.dataListener = null;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * This is for a single network stream.
 | 
						|
 */
 | 
						|
class StandardChromeActions extends ChromeActions {
 | 
						|
  constructor(
 | 
						|
    domWindow,
 | 
						|
    contentDispositionFilename,
 | 
						|
    originalRequest,
 | 
						|
    dataListener
 | 
						|
  ) {
 | 
						|
    super(domWindow, contentDispositionFilename);
 | 
						|
    this.originalRequest = originalRequest;
 | 
						|
    this.dataListener = dataListener;
 | 
						|
  }
 | 
						|
 | 
						|
  initPassiveLoading() {
 | 
						|
    if (!this.dataListener) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    this.dataListener.onprogress = (loaded, total) => {
 | 
						|
      this.domWindow.postMessage(
 | 
						|
        {
 | 
						|
          pdfjsLoadAction: "progress",
 | 
						|
          loaded,
 | 
						|
          total,
 | 
						|
        },
 | 
						|
        PDF_VIEWER_ORIGIN
 | 
						|
      );
 | 
						|
    };
 | 
						|
 | 
						|
    this.dataListener.oncomplete = (data, errorCode) => {
 | 
						|
      this.domWindow.postMessage(
 | 
						|
        {
 | 
						|
          pdfjsLoadAction: "complete",
 | 
						|
          data,
 | 
						|
          errorCode,
 | 
						|
          filename: this.contentDispositionFilename,
 | 
						|
        },
 | 
						|
        PDF_VIEWER_ORIGIN,
 | 
						|
        data ? [data.buffer] : undefined
 | 
						|
      );
 | 
						|
 | 
						|
      this.dataListener = null;
 | 
						|
      this.originalRequest = null;
 | 
						|
    };
 | 
						|
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  abortLoading() {
 | 
						|
    if (this.originalRequest) {
 | 
						|
      this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
 | 
						|
      this.originalRequest = null;
 | 
						|
    }
 | 
						|
    this.dataListener = null;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Event listener to trigger chrome privileged code.
 | 
						|
 */
 | 
						|
class RequestListener {
 | 
						|
  constructor(actions) {
 | 
						|
    this.actions = actions;
 | 
						|
  }
 | 
						|
 | 
						|
  // Receive an event and (optionally) asynchronously responds.
 | 
						|
  receive({ target, detail }) {
 | 
						|
    const doc = target.ownerDocument;
 | 
						|
    const { action, data, responseExpected } = detail;
 | 
						|
 | 
						|
    const actionFn = this.actions[action];
 | 
						|
    if (!actionFn) {
 | 
						|
      log("Unknown action: " + action);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    let response = null;
 | 
						|
 | 
						|
    if (!responseExpected) {
 | 
						|
      doc.documentElement.removeChild(target);
 | 
						|
    } else {
 | 
						|
      response = function (aResponse) {
 | 
						|
        try {
 | 
						|
          const listener = doc.createEvent("CustomEvent");
 | 
						|
          const detail = Cu.cloneInto({ response: aResponse }, doc.defaultView);
 | 
						|
          listener.initCustomEvent("pdf.js.response", true, false, detail);
 | 
						|
          return target.dispatchEvent(listener);
 | 
						|
        } catch (e) {
 | 
						|
          // doc is no longer accessible because the requestor is already
 | 
						|
          // gone. unloaded content cannot receive the response anyway.
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
      };
 | 
						|
    }
 | 
						|
    actionFn.call(this.actions, data, response);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export function PdfStreamConverter() {}
 | 
						|
 | 
						|
PdfStreamConverter.prototype = {
 | 
						|
  QueryInterface: ChromeUtils.generateQI([
 | 
						|
    "nsIStreamConverter",
 | 
						|
    "nsIStreamListener",
 | 
						|
    "nsIRequestObserver",
 | 
						|
  ]),
 | 
						|
 | 
						|
  /*
 | 
						|
   * This component works as such:
 | 
						|
   * 1. asyncConvertData stores the listener
 | 
						|
   * 2. onStartRequest creates a new channel, streams the viewer
 | 
						|
   * 3. If range requests are supported:
 | 
						|
   *      3.1. Leave the request open until the viewer is ready to switch to
 | 
						|
   *           range requests.
 | 
						|
   *
 | 
						|
   *    If range rquests are not supported:
 | 
						|
   *      3.1. Read the stream as it's loaded in onDataAvailable to send
 | 
						|
   *           to the viewer
 | 
						|
   *
 | 
						|
   * The convert function just returns the stream, it's just the synchronous
 | 
						|
   * version of asyncConvertData.
 | 
						|
   */
 | 
						|
 | 
						|
  // nsIStreamConverter::convert
 | 
						|
  convert() {
 | 
						|
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIStreamConverter::asyncConvertData
 | 
						|
  asyncConvertData(aFromType, aToType, aListener, aCtxt) {
 | 
						|
    if (aCtxt && aCtxt instanceof Ci.nsIChannel) {
 | 
						|
      aCtxt.QueryInterface(Ci.nsIChannel);
 | 
						|
    }
 | 
						|
    // We need to check if we're supposed to convert here, because not all
 | 
						|
    // asyncConvertData consumers will call getConvertedType first:
 | 
						|
    this.getConvertedType(aFromType, aCtxt);
 | 
						|
 | 
						|
    // Store the listener passed to us
 | 
						|
    this.listener = aListener;
 | 
						|
  },
 | 
						|
 | 
						|
  _usableHandler(handlerInfo) {
 | 
						|
    let { preferredApplicationHandler } = handlerInfo;
 | 
						|
    if (
 | 
						|
      !preferredApplicationHandler ||
 | 
						|
      !(preferredApplicationHandler instanceof Ci.nsILocalHandlerApp)
 | 
						|
    ) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    preferredApplicationHandler.QueryInterface(Ci.nsILocalHandlerApp);
 | 
						|
    // We have an app, grab the executable
 | 
						|
    let { executable } = preferredApplicationHandler;
 | 
						|
    if (!executable) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return !executable.equals(lazy.gOurBinary);
 | 
						|
  },
 | 
						|
 | 
						|
  /*
 | 
						|
   * Check if the user wants to use PDF.js. Returns true if PDF.js should
 | 
						|
   * handle PDFs, and false if not. Will always return true on non-parent
 | 
						|
   * processes.
 | 
						|
   *
 | 
						|
   * If the user has selected to open PDFs with a helper app, and we are that
 | 
						|
   * helper app, or if the user has selected the OS default, and we are that
 | 
						|
   * OS default, reset the preference back to pdf.js .
 | 
						|
   *
 | 
						|
   */
 | 
						|
  _validateAndMaybeUpdatePDFPrefs() {
 | 
						|
    let { processType, PROCESS_TYPE_DEFAULT } = Services.appinfo;
 | 
						|
    // If we're not in the parent, or are the default, then just say yes.
 | 
						|
    if (processType != PROCESS_TYPE_DEFAULT || lazy.PdfJs.cachedIsDefault()) {
 | 
						|
      return { shouldOpen: true };
 | 
						|
    }
 | 
						|
 | 
						|
    // OK, PDF.js might not be the default. Find out if we've misled the user
 | 
						|
    // into making Firefox an external handler or if we're the OS default and
 | 
						|
    // Firefox is set to use the OS default:
 | 
						|
    let mime = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf");
 | 
						|
    // The above might throw errors. We're deliberately letting those bubble
 | 
						|
    // back up, where they'll tell the stream converter not to use us.
 | 
						|
 | 
						|
    if (!mime) {
 | 
						|
      // This shouldn't happen, but we can't fix what isn't there. Assume
 | 
						|
      // we're OK to handle with PDF.js
 | 
						|
      return { shouldOpen: true };
 | 
						|
    }
 | 
						|
 | 
						|
    const { saveToDisk, useHelperApp, useSystemDefault } = Ci.nsIHandlerInfo;
 | 
						|
    let { preferredAction, alwaysAskBeforeHandling } = mime;
 | 
						|
    // return this info so getConvertedType can use it.
 | 
						|
    let rv = { alwaysAskBeforeHandling, shouldOpen: false };
 | 
						|
    // If the user has indicated they want to be asked or want to save to
 | 
						|
    // disk, we shouldn't render inline immediately:
 | 
						|
    if (alwaysAskBeforeHandling || preferredAction == saveToDisk) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    // If we have usable helper app info, don't use PDF.js
 | 
						|
    if (preferredAction == useHelperApp && this._usableHandler(mime)) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    // If we want the OS default and that's not Firefox, don't use PDF.js
 | 
						|
    if (preferredAction == useSystemDefault && !mime.isCurrentAppOSDefault()) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    rv.shouldOpen = true;
 | 
						|
    // Log that we're doing this to help debug issues if people end up being
 | 
						|
    // surprised by this behaviour.
 | 
						|
    console.error("Found unusable PDF preferences. Fixing back to PDF.js");
 | 
						|
 | 
						|
    mime.preferredAction = Ci.nsIHandlerInfo.handleInternally;
 | 
						|
    mime.alwaysAskBeforeHandling = false;
 | 
						|
    Svc.handlers.store(mime);
 | 
						|
    return true;
 | 
						|
  },
 | 
						|
 | 
						|
  getConvertedType(aFromType, aChannel) {
 | 
						|
    if (aChannel instanceof Ci.nsIMultiPartChannel) {
 | 
						|
      throw new Components.Exception(
 | 
						|
        "PDF.js doesn't support multipart responses.",
 | 
						|
        Cr.NS_ERROR_NOT_IMPLEMENTED
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    const HTML = "text/html";
 | 
						|
    let channelURI = aChannel?.URI;
 | 
						|
    // We can be invoked for application/octet-stream; check if we want the
 | 
						|
    // channel first:
 | 
						|
    if (aFromType != "application/pdf") {
 | 
						|
      // Check if the filename has a PDF extension.
 | 
						|
      let isPDF = false;
 | 
						|
      try {
 | 
						|
        isPDF = aChannel.contentDispositionFilename.endsWith(".pdf");
 | 
						|
      } catch (ex) {}
 | 
						|
      if (!isPDF) {
 | 
						|
        isPDF =
 | 
						|
          channelURI?.QueryInterface(Ci.nsIURL).fileExtension.toLowerCase() ==
 | 
						|
          "pdf";
 | 
						|
      }
 | 
						|
 | 
						|
      let browsingContext = aChannel?.loadInfo.targetBrowsingContext;
 | 
						|
      let toplevelOctetStream =
 | 
						|
        aFromType == "application/octet-stream" &&
 | 
						|
        browsingContext &&
 | 
						|
        !browsingContext.parent;
 | 
						|
      if (
 | 
						|
        !isPDF ||
 | 
						|
        !toplevelOctetStream ||
 | 
						|
        !Services.prefs.getBoolPref("pdfjs.handleOctetStream", false)
 | 
						|
      ) {
 | 
						|
        throw new Components.Exception(
 | 
						|
          "Ignore PDF.js for this download.",
 | 
						|
          Cr.NS_ERROR_FAILURE
 | 
						|
        );
 | 
						|
      }
 | 
						|
      // fall through, this appears to be a pdf.
 | 
						|
    }
 | 
						|
 | 
						|
    let { alwaysAskBeforeHandling, shouldOpen } =
 | 
						|
      this._validateAndMaybeUpdatePDFPrefs();
 | 
						|
 | 
						|
    if (shouldOpen) {
 | 
						|
      return HTML;
 | 
						|
    }
 | 
						|
    // Hm, so normally, no pdfjs. However... if this is a file: channel there
 | 
						|
    // are some edge-cases.
 | 
						|
    if (channelURI?.schemeIs("file")) {
 | 
						|
      // If we're loaded with system principal, we were likely handed the PDF
 | 
						|
      // by the OS or directly from the URL bar. Assume we should load it:
 | 
						|
      let triggeringPrincipal = aChannel.loadInfo?.triggeringPrincipal;
 | 
						|
      if (triggeringPrincipal?.isSystemPrincipal) {
 | 
						|
        return HTML;
 | 
						|
      }
 | 
						|
 | 
						|
      // If we're loading from a file: link, load it in PDF.js unless the user
 | 
						|
      // has told us they always want to open/save PDFs.
 | 
						|
      // This is because handing off the choice to open in Firefox itself
 | 
						|
      // through the dialog doesn't work properly and making it work is
 | 
						|
      // non-trivial (see https://bugzilla.mozilla.org/show_bug.cgi?id=1680147#c3 )
 | 
						|
      // - and anyway, opening the file is what we do for *all*
 | 
						|
      // other file types we handle internally (and users can then use other UI
 | 
						|
      // to save or open it with other apps from there).
 | 
						|
      if (triggeringPrincipal?.schemeIs("file") && alwaysAskBeforeHandling) {
 | 
						|
        return HTML;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    throw new Components.Exception("Can't use PDF.js", Cr.NS_ERROR_FAILURE);
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIStreamListener::onDataAvailable
 | 
						|
  onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
 | 
						|
    if (!this.dataListener) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    var binaryStream = this.binaryStream;
 | 
						|
    binaryStream.setInputStream(aInputStream);
 | 
						|
    let chunk = new ArrayBuffer(aCount);
 | 
						|
    binaryStream.readArrayBuffer(aCount, chunk);
 | 
						|
    this.dataListener.append(new Uint8Array(chunk));
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIRequestObserver::onStartRequest
 | 
						|
  onStartRequest(aRequest) {
 | 
						|
    // Setup the request so we can use it below.
 | 
						|
    var isHttpRequest = false;
 | 
						|
    try {
 | 
						|
      aRequest.QueryInterface(Ci.nsIHttpChannel);
 | 
						|
      isHttpRequest = true;
 | 
						|
    } catch (e) {}
 | 
						|
 | 
						|
    var rangeRequest = false;
 | 
						|
    var streamRequest = false;
 | 
						|
    if (isHttpRequest) {
 | 
						|
      var contentEncoding = "identity";
 | 
						|
      try {
 | 
						|
        contentEncoding = aRequest.getResponseHeader("Content-Encoding");
 | 
						|
      } catch (e) {}
 | 
						|
 | 
						|
      var acceptRanges;
 | 
						|
      try {
 | 
						|
        acceptRanges = aRequest.getResponseHeader("Accept-Ranges");
 | 
						|
      } catch (e) {}
 | 
						|
 | 
						|
      var hash = aRequest.URI.ref;
 | 
						|
      const isPDFBugEnabled = Services.prefs.getBoolPref(
 | 
						|
        "pdfjs.pdfBugEnabled",
 | 
						|
        false
 | 
						|
      );
 | 
						|
      rangeRequest =
 | 
						|
        contentEncoding === "identity" &&
 | 
						|
        acceptRanges === "bytes" &&
 | 
						|
        aRequest.contentLength >= 0 &&
 | 
						|
        !Services.prefs.getBoolPref("pdfjs.disableRange", false) &&
 | 
						|
        (!isPDFBugEnabled || !hash.toLowerCase().includes("disablerange=true"));
 | 
						|
      streamRequest =
 | 
						|
        contentEncoding === "identity" &&
 | 
						|
        aRequest.contentLength >= 0 &&
 | 
						|
        !Services.prefs.getBoolPref("pdfjs.disableStream", false) &&
 | 
						|
        (!isPDFBugEnabled ||
 | 
						|
          !hash.toLowerCase().includes("disablestream=true"));
 | 
						|
    }
 | 
						|
 | 
						|
    aRequest.QueryInterface(Ci.nsIChannel);
 | 
						|
 | 
						|
    aRequest.QueryInterface(Ci.nsIWritablePropertyBag);
 | 
						|
 | 
						|
    var contentDispositionFilename;
 | 
						|
    try {
 | 
						|
      contentDispositionFilename = aRequest.contentDispositionFilename;
 | 
						|
    } catch (e) {}
 | 
						|
 | 
						|
    if (
 | 
						|
      contentDispositionFilename &&
 | 
						|
      !/\.pdf$/i.test(contentDispositionFilename)
 | 
						|
    ) {
 | 
						|
      contentDispositionFilename += ".pdf";
 | 
						|
    }
 | 
						|
 | 
						|
    // Change the content type so we don't get stuck in a loop.
 | 
						|
    aRequest.setProperty("contentType", aRequest.contentType);
 | 
						|
    aRequest.contentType = "text/html";
 | 
						|
    if (isHttpRequest) {
 | 
						|
      // We trust PDF viewer, using no CSP
 | 
						|
      aRequest.setResponseHeader("Content-Security-Policy", "", false);
 | 
						|
      aRequest.setResponseHeader(
 | 
						|
        "Content-Security-Policy-Report-Only",
 | 
						|
        "",
 | 
						|
        false
 | 
						|
      );
 | 
						|
      // The viewer does not need to handle HTTP Refresh header.
 | 
						|
      aRequest.setResponseHeader("Refresh", "", false);
 | 
						|
    }
 | 
						|
 | 
						|
    lazy.PdfJsTelemetryContent.onViewerIsUsed();
 | 
						|
 | 
						|
    // The document will be loaded via the stream converter as html,
 | 
						|
    // but since we may have come here via a download or attachment
 | 
						|
    // that was opened directly, force the content disposition to be
 | 
						|
    // inline so that the html document will be loaded normally instead
 | 
						|
    // of going to the helper service.
 | 
						|
    aRequest.contentDisposition = Ci.nsIChannel.DISPOSITION_FORCE_INLINE;
 | 
						|
 | 
						|
    // Creating storage for PDF data
 | 
						|
    var contentLength = aRequest.contentLength;
 | 
						|
    this.dataListener = new PdfDataListener(contentLength);
 | 
						|
    this.binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
 | 
						|
      Ci.nsIBinaryInputStream
 | 
						|
    );
 | 
						|
 | 
						|
    // Create a new channel that is viewer loaded as a resource.
 | 
						|
    var channel = lazy.NetUtil.newChannel({
 | 
						|
      uri: PDF_VIEWER_WEB_PAGE,
 | 
						|
      loadUsingSystemPrincipal: true,
 | 
						|
    });
 | 
						|
 | 
						|
    var listener = this.listener;
 | 
						|
    var dataListener = this.dataListener;
 | 
						|
    // Proxy all the request observer calls, when it gets to onStopRequest
 | 
						|
    // we can get the dom window.  We also intentionally pass on the original
 | 
						|
    // request(aRequest) below so we don't overwrite the original channel and
 | 
						|
    // trigger an assertion.
 | 
						|
    var proxy = {
 | 
						|
      onStartRequest() {
 | 
						|
        listener.onStartRequest(aRequest);
 | 
						|
      },
 | 
						|
      onDataAvailable(request, inputStream, offset, count) {
 | 
						|
        listener.onDataAvailable(aRequest, inputStream, offset, count);
 | 
						|
      },
 | 
						|
      onStopRequest(request, statusCode) {
 | 
						|
        var domWindow = getDOMWindow(channel, resourcePrincipal);
 | 
						|
        if (!Components.isSuccessCode(statusCode) || !domWindow) {
 | 
						|
          // The request may have been aborted and the document may have been
 | 
						|
          // replaced with something that is not PDF.js, abort attaching.
 | 
						|
          listener.onStopRequest(aRequest, statusCode);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        var actions;
 | 
						|
        if (rangeRequest || streamRequest) {
 | 
						|
          actions = new RangedChromeActions(
 | 
						|
            domWindow,
 | 
						|
            contentDispositionFilename,
 | 
						|
            aRequest,
 | 
						|
            rangeRequest,
 | 
						|
            streamRequest,
 | 
						|
            dataListener
 | 
						|
          );
 | 
						|
        } else {
 | 
						|
          actions = new StandardChromeActions(
 | 
						|
            domWindow,
 | 
						|
            contentDispositionFilename,
 | 
						|
            aRequest,
 | 
						|
            dataListener
 | 
						|
          );
 | 
						|
        }
 | 
						|
        var requestListener = new RequestListener(actions);
 | 
						|
        domWindow.document.addEventListener(
 | 
						|
          PDFJS_EVENT_ID,
 | 
						|
          function (event) {
 | 
						|
            requestListener.receive(event);
 | 
						|
          },
 | 
						|
          false,
 | 
						|
          true
 | 
						|
        );
 | 
						|
 | 
						|
        let actor = getActor(domWindow);
 | 
						|
        actor?.init(actions.supportsIntegratedFind());
 | 
						|
        actor?.sendAsyncMessage("PDFJS:Parent:recordExposure");
 | 
						|
        listener.onStopRequest(aRequest, statusCode);
 | 
						|
      },
 | 
						|
    };
 | 
						|
 | 
						|
    // Keep the URL the same so the browser sees it as the same.
 | 
						|
    channel.originalURI = aRequest.URI;
 | 
						|
    channel.loadGroup = aRequest.loadGroup;
 | 
						|
    channel.loadInfo.originAttributes = aRequest.loadInfo.originAttributes;
 | 
						|
 | 
						|
    // We can use the resource principal when data is fetched by the chrome,
 | 
						|
    // e.g. useful for NoScript. Make make sure we reuse the origin attributes
 | 
						|
    // from the request channel to keep isolation consistent.
 | 
						|
    var uri = lazy.NetUtil.newURI(PDF_VIEWER_WEB_PAGE);
 | 
						|
    var resourcePrincipal =
 | 
						|
      Services.scriptSecurityManager.createContentPrincipal(
 | 
						|
        uri,
 | 
						|
        aRequest.loadInfo.originAttributes
 | 
						|
      );
 | 
						|
    // Remember the principal we would have had before we mess with it.
 | 
						|
    let originalPrincipal =
 | 
						|
      Services.scriptSecurityManager.getChannelResultPrincipal(aRequest);
 | 
						|
    aRequest.owner = resourcePrincipal;
 | 
						|
    aRequest.setProperty("noPDFJSPrincipal", originalPrincipal);
 | 
						|
 | 
						|
    channel.asyncOpen(proxy);
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIRequestObserver::onStopRequest
 | 
						|
  onStopRequest(aRequest, aStatusCode) {
 | 
						|
    if (!this.dataListener) {
 | 
						|
      // Do nothing
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (Components.isSuccessCode(aStatusCode)) {
 | 
						|
      this.dataListener.finish();
 | 
						|
    } else {
 | 
						|
      this.dataListener.error(aStatusCode);
 | 
						|
    }
 | 
						|
    delete this.dataListener;
 | 
						|
    delete this.binaryStream;
 | 
						|
  },
 | 
						|
};
 |