pdf_scripting_api.js revision a02191e04bc25c4935f804f2c080ae28663d096d
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * Create a new PDFMessagingClient. This provides a scripting interface to
7 * the PDF viewer so that it can be customized by things like print preview.
8 * @param {HTMLIFrameElement} iframe an iframe containing the PDF viewer.
9 * @param {Window} window the window of the page containing the iframe.
10 * @param {string} extensionUrl the url of the PDF extension.
11 */
12function PDFMessagingClient(iframe, window, extensionUrl) {
13  this.iframe_ = iframe;
14  this.extensionUrl_ = extensionUrl;
15  this.readyToReceive_ = false;
16  this.messageQueue_ = [];
17  window.addEventListener('message', function(event) {
18    switch (event.data.type) {
19      case 'readyToReceive':
20        this.flushPendingMessages_();
21        break;
22      case 'viewportChanged':
23        this.viewportChangedCallback_(event.data.pageX,
24                                      event.data.pageY,
25                                      event.data.pageWidth,
26                                      event.data.viewportWidth,
27                                      event.data.viewportHeight);
28        break;
29      case 'documentLoaded':
30        this.loadCallback_();
31        break;
32    }
33  }.bind(this), false);
34}
35
36PDFMessagingClient.prototype = {
37  /**
38   * @private
39   * Send a message to the extension. If we try to send messages prior to the
40   * extension being ready to receive messages (i.e. before it has finished
41   * loading) we queue up the messages and flush them later.
42   * @param {MessageObject} the message to send.
43   */
44  sendMessage_: function(message) {
45    if (!this.readyToReceive_) {
46      this.messageQueue_.push(message);
47      return;
48    }
49
50    this.iframe_.contentWindow.postMessage(message, this.extensionUrl_);
51  },
52
53  /**
54   * @private
55   * Flushes all pending messages to the extension.
56   */
57  flushPendingMessages_: function() {
58    this.readyToReceive_ = true;
59    while (this.messageQueue_.length != 0) {
60      this.iframe_.contentWindow.postMessage(this.messageQueue_.shift(),
61                                             this.extensionUrl_);
62    }
63  },
64
65  /**
66   * Sets the callback which will be run when the PDF viewport changes.
67   * @param {Function} callback the callback to be called.
68   */
69  setViewportChangedCallback: function(callback) {
70    this.viewportChangedCallback_ = callback;
71  },
72
73  /**
74   * Sets the callback which will be run when the PDF document has finished
75   * loading.
76   * @param {Function} callback the callback to be called.
77   */
78  setLoadCallback: function(callback) {
79    this.loadCallback_ = callback;
80  },
81
82  /**
83   * Resets the PDF viewer into print preview mode.
84   * @param {string} url the url of the PDF to load.
85   * @param {boolean} grayscale whether or not to display the PDF in grayscale.
86   * @param {Array.<number>} pageNumbers an array of the page numbers.
87   * @param {boolean} modifiable whether or not the document is modifiable.
88   */
89  resetPrintPreviewMode: function(url, grayscale, pageNumbers, modifiable) {
90    this.sendMessage_({
91      type: 'resetPrintPreviewMode',
92      url: url,
93      grayscale: grayscale,
94      pageNumbers: pageNumbers,
95      modifiable: modifiable
96    });
97  },
98
99  /**
100   * Load a page into the document while in print preview mode.
101   * @param {string} url the url of the pdf page to load.
102   * @param {number} index the index of the page to load.
103   */
104  loadPreviewPage: function(url, index) {
105    this.sendMessage_({
106      type: 'loadPreviewPage',
107      url: url,
108      index: index
109    });
110  },
111
112  /**
113   * Send a key event to the extension.
114   * @param {number} keyCode the key code to send to the extension.
115   */
116  sendKeyEvent: function(keyCode) {
117    // TODO(raymes): Figure out a way to do this. It's only used to do scrolling
118    // the viewport, so probably just expose viewport controls instead.
119  },
120};
121
122/**
123 * Creates a PDF viewer with a scripting interface. This is basically 1) an
124 * iframe which is navigated to the PDF viewer extension and 2) a scripting
125 * interface which provides access to various features of the viewer for use
126 * by print preview and accessbility.
127 * @param {string} src the source URL of the PDF to load initially.
128 * @return {HTMLIFrameElement} the iframe element containing the PDF viewer.
129 */
130function PDFCreateOutOfProcessPlugin(src) {
131  var EXTENSION_URL = 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai';
132  var iframe = window.document.createElement('iframe');
133  iframe.setAttribute('src', EXTENSION_URL + '/index.html?' + src);
134  var client = new PDFMessagingClient(iframe, window, EXTENSION_URL);
135
136  // Add the functions to the iframe so that they can be called directly.
137  iframe.setViewportChangedCallback =
138      client.setViewportChangedCallback.bind(client);
139  iframe.setLoadCallback = client.setLoadCallback.bind(client);
140  iframe.resetPrintPreviewMode = client.resetPrintPreviewMode.bind(client);
141  iframe.loadPreviewPage = client.loadPreviewPage.bind(client);
142  iframe.sendKeyEvent = client.sendKeyEvent.bind(client);
143  return iframe;
144}
145
146/**
147 * Create a new PDFMessagingHost. This is the extension-side of the scripting
148 * interface to the PDF viewer. It handles requests from a page which contains
149 * a PDF viewer extension and translates them into actions on the viewer.
150 * @param {Window} window the window containing the PDF extension.
151 * @param {PDFViewer} pdfViewer the object which provides access to the viewer.
152 */
153function PDFMessagingHost(window, pdfViewer) {
154  this.window_ = window;
155  this.pdfViewer_ = pdfViewer;
156  this.viewport_ = pdfViewer.viewport;
157
158  window.addEventListener('message', function(event) {
159    switch (event.data.type) {
160      case 'resetPrintPreviewMode':
161        this.pdfViewer_.resetPrintPreviewMode(
162            event.data.url,
163            event.data.grayscale,
164            event.data.pageNumbers,
165            event.data.modifiable);
166
167        break;
168      case 'loadPreviewPage':
169        this.pdfViewer_.loadPreviewPage(event.data.url, event.data.index);
170        break;
171    }
172  }.bind(this), false);
173
174  if (this.window_.parent != this.window_)
175    this.sendMessage_({type: 'readyToReceive'});
176}
177
178PDFMessagingHost.prototype = {
179  sendMessage_: function(message) {
180    if (this.window_.parent == this.window_)
181      return;
182    this.window_.parent.postMessage(message, '*');
183  },
184
185  viewportChanged: function() {
186    var visiblePage = this.viewport_.getMostVisiblePage();
187    var pageDimensions = this.viewport_.getPageScreenRect(visiblePage);
188    var size = this.viewport_.size;
189
190    this.sendMessage_({
191      type: 'viewportChanged',
192      pageX: pageDimensions.x,
193      pageY: pageDimensions.y,
194      pageWidth: pageDimensions.width,
195      viewportWidth: size.width,
196      viewportHeight: size.height,
197    });
198  },
199
200  documentLoaded: function() {
201    if (this.window_.parent == this.window_)
202      return;
203    this.sendMessage_({ type: 'documentLoaded' });
204  }
205};
206