Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
3 /* Copyright 2012 Mozilla Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 /* globals PDFJS */
19 'use strict';
21 var FontInspector = (function FontInspectorClosure() {
22 var fonts;
23 var active = false;
24 var fontAttribute = 'data-font-name';
25 function removeSelection() {
26 var divs = document.querySelectorAll('div[' + fontAttribute + ']');
27 for (var i = 0, ii = divs.length; i < ii; ++i) {
28 var div = divs[i];
29 div.className = '';
30 }
31 }
32 function resetSelection() {
33 var divs = document.querySelectorAll('div[' + fontAttribute + ']');
34 for (var i = 0, ii = divs.length; i < ii; ++i) {
35 var div = divs[i];
36 div.className = 'debuggerHideText';
37 }
38 }
39 function selectFont(fontName, show) {
40 var divs = document.querySelectorAll('div[' + fontAttribute + '=' +
41 fontName + ']');
42 for (var i = 0, ii = divs.length; i < ii; ++i) {
43 var div = divs[i];
44 div.className = show ? 'debuggerShowText' : 'debuggerHideText';
45 }
46 }
47 function textLayerClick(e) {
48 if (!e.target.dataset.fontName ||
49 e.target.tagName.toUpperCase() !== 'DIV') {
50 return;
51 }
52 var fontName = e.target.dataset.fontName;
53 var selects = document.getElementsByTagName('input');
54 for (var i = 0; i < selects.length; ++i) {
55 var select = selects[i];
56 if (select.dataset.fontName != fontName) {
57 continue;
58 }
59 select.checked = !select.checked;
60 selectFont(fontName, select.checked);
61 select.scrollIntoView();
62 }
63 }
64 return {
65 // Properties/functions needed by PDFBug.
66 id: 'FontInspector',
67 name: 'Font Inspector',
68 panel: null,
69 manager: null,
70 init: function init() {
71 var panel = this.panel;
72 panel.setAttribute('style', 'padding: 5px;');
73 var tmp = document.createElement('button');
74 tmp.addEventListener('click', resetSelection);
75 tmp.textContent = 'Refresh';
76 panel.appendChild(tmp);
78 fonts = document.createElement('div');
79 panel.appendChild(fonts);
80 },
81 cleanup: function cleanup() {
82 fonts.textContent = '';
83 },
84 enabled: false,
85 get active() {
86 return active;
87 },
88 set active(value) {
89 active = value;
90 if (active) {
91 document.body.addEventListener('click', textLayerClick, true);
92 resetSelection();
93 } else {
94 document.body.removeEventListener('click', textLayerClick, true);
95 removeSelection();
96 }
97 },
98 // FontInspector specific functions.
99 fontAdded: function fontAdded(fontObj, url) {
100 function properties(obj, list) {
101 var moreInfo = document.createElement('table');
102 for (var i = 0; i < list.length; i++) {
103 var tr = document.createElement('tr');
104 var td1 = document.createElement('td');
105 td1.textContent = list[i];
106 tr.appendChild(td1);
107 var td2 = document.createElement('td');
108 td2.textContent = obj[list[i]].toString();
109 tr.appendChild(td2);
110 moreInfo.appendChild(tr);
111 }
112 return moreInfo;
113 }
114 var moreInfo = properties(fontObj, ['name', 'type']);
115 var m = /url\(['"]?([^\)"']+)/.exec(url);
116 var fontName = fontObj.loadedName;
117 var font = document.createElement('div');
118 var name = document.createElement('span');
119 name.textContent = fontName;
120 var download = document.createElement('a');
121 download.href = m[1];
122 download.textContent = 'Download';
123 var logIt = document.createElement('a');
124 logIt.href = '';
125 logIt.textContent = 'Log';
126 logIt.addEventListener('click', function(event) {
127 event.preventDefault();
128 console.log(fontObj);
129 });
130 var select = document.createElement('input');
131 select.setAttribute('type', 'checkbox');
132 select.dataset.fontName = fontName;
133 select.addEventListener('click', (function(select, fontName) {
134 return (function() {
135 selectFont(fontName, select.checked);
136 });
137 })(select, fontName));
138 font.appendChild(select);
139 font.appendChild(name);
140 font.appendChild(document.createTextNode(' '));
141 font.appendChild(download);
142 font.appendChild(document.createTextNode(' '));
143 font.appendChild(logIt);
144 font.appendChild(moreInfo);
145 fonts.appendChild(font);
146 // Somewhat of a hack, should probably add a hook for when the text layer
147 // is done rendering.
148 setTimeout(function() {
149 if (this.active) {
150 resetSelection();
151 }
152 }.bind(this), 2000);
153 }
154 };
155 })();
157 // Manages all the page steppers.
158 var StepperManager = (function StepperManagerClosure() {
159 var steppers = [];
160 var stepperDiv = null;
161 var stepperControls = null;
162 var stepperChooser = null;
163 var breakPoints = {};
164 return {
165 // Properties/functions needed by PDFBug.
166 id: 'Stepper',
167 name: 'Stepper',
168 panel: null,
169 manager: null,
170 init: function init() {
171 var self = this;
172 this.panel.setAttribute('style', 'padding: 5px;');
173 stepperControls = document.createElement('div');
174 stepperChooser = document.createElement('select');
175 stepperChooser.addEventListener('change', function(event) {
176 self.selectStepper(this.value);
177 });
178 stepperControls.appendChild(stepperChooser);
179 stepperDiv = document.createElement('div');
180 this.panel.appendChild(stepperControls);
181 this.panel.appendChild(stepperDiv);
182 if (sessionStorage.getItem('pdfjsBreakPoints')) {
183 breakPoints = JSON.parse(sessionStorage.getItem('pdfjsBreakPoints'));
184 }
185 },
186 cleanup: function cleanup() {
187 stepperChooser.textContent = '';
188 stepperDiv.textContent = '';
189 steppers = [];
190 },
191 enabled: false,
192 active: false,
193 // Stepper specific functions.
194 create: function create(pageIndex) {
195 var debug = document.createElement('div');
196 debug.id = 'stepper' + pageIndex;
197 debug.setAttribute('hidden', true);
198 debug.className = 'stepper';
199 stepperDiv.appendChild(debug);
200 var b = document.createElement('option');
201 b.textContent = 'Page ' + (pageIndex + 1);
202 b.value = pageIndex;
203 stepperChooser.appendChild(b);
204 var initBreakPoints = breakPoints[pageIndex] || [];
205 var stepper = new Stepper(debug, pageIndex, initBreakPoints);
206 steppers.push(stepper);
207 if (steppers.length === 1) {
208 this.selectStepper(pageIndex, false);
209 }
210 return stepper;
211 },
212 selectStepper: function selectStepper(pageIndex, selectPanel) {
213 var i;
214 if (selectPanel) {
215 this.manager.selectPanel(this);
216 }
217 for (i = 0; i < steppers.length; ++i) {
218 var stepper = steppers[i];
219 if (stepper.pageIndex == pageIndex) {
220 stepper.panel.removeAttribute('hidden');
221 } else {
222 stepper.panel.setAttribute('hidden', true);
223 }
224 }
225 var options = stepperChooser.options;
226 for (i = 0; i < options.length; ++i) {
227 var option = options[i];
228 option.selected = option.value == pageIndex;
229 }
230 },
231 saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
232 breakPoints[pageIndex] = bps;
233 sessionStorage.setItem('pdfjsBreakPoints', JSON.stringify(breakPoints));
234 }
235 };
236 })();
238 // The stepper for each page's IRQueue.
239 var Stepper = (function StepperClosure() {
240 // Shorter way to create element and optionally set textContent.
241 function c(tag, textContent) {
242 var d = document.createElement(tag);
243 if (textContent) {
244 d.textContent = textContent;
245 }
246 return d;
247 }
249 function glyphsToString(glyphs) {
250 var out = '';
251 for (var i = 0; i < glyphs.length; i++) {
252 if (glyphs[i] === null) {
253 out += ' ';
254 } else {
255 out += glyphs[i].fontChar;
256 }
257 }
258 return out;
259 }
261 var opMap = null;
263 var glyphCommands = {
264 'showText': 0,
265 'showSpacedText': 0,
266 'nextLineShowText': 0,
267 'nextLineSetSpacingShowText': 2
268 };
270 function simplifyArgs(args) {
271 if (typeof args === 'string') {
272 var MAX_STRING_LENGTH = 75;
273 return args.length <= MAX_STRING_LENGTH ? args :
274 args.substr(0, MAX_STRING_LENGTH) + '...';
275 }
276 if (typeof args !== 'object' || args === null) {
277 return args;
278 }
279 if ('length' in args) { // array
280 var simpleArgs = [], i, ii;
281 var MAX_ITEMS = 10;
282 for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
283 simpleArgs.push(simplifyArgs(args[i]));
284 }
285 if (i < args.length) {
286 simpleArgs.push('...');
287 }
288 return simpleArgs;
289 }
290 var simpleObj = {};
291 for (var key in args) {
292 simpleObj[key] = simplifyArgs(args[key]);
293 }
294 return simpleObj;
295 }
297 function Stepper(panel, pageIndex, initialBreakPoints) {
298 this.panel = panel;
299 this.breakPoint = 0;
300 this.nextBreakPoint = null;
301 this.pageIndex = pageIndex;
302 this.breakPoints = initialBreakPoints;
303 this.currentIdx = -1;
304 this.operatorListIdx = 0;
305 }
306 Stepper.prototype = {
307 init: function init() {
308 var panel = this.panel;
309 var content = c('div', 'c=continue, s=step');
310 var table = c('table');
311 content.appendChild(table);
312 table.cellSpacing = 0;
313 var headerRow = c('tr');
314 table.appendChild(headerRow);
315 headerRow.appendChild(c('th', 'Break'));
316 headerRow.appendChild(c('th', 'Idx'));
317 headerRow.appendChild(c('th', 'fn'));
318 headerRow.appendChild(c('th', 'args'));
319 panel.appendChild(content);
320 this.table = table;
321 if (!opMap) {
322 opMap = Object.create(null);
323 for (var key in PDFJS.OPS) {
324 opMap[PDFJS.OPS[key]] = key;
325 }
326 }
327 },
328 updateOperatorList: function updateOperatorList(operatorList) {
329 var self = this;
331 function cboxOnClick() {
332 var x = +this.dataset.idx;
333 if (this.checked) {
334 self.breakPoints.push(x);
335 } else {
336 self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
337 }
338 StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
339 }
341 var MAX_OPERATORS_COUNT = 15000;
342 if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
343 return;
344 }
346 var chunk = document.createDocumentFragment();
347 var operatorsToDisplay = Math.min(MAX_OPERATORS_COUNT,
348 operatorList.fnArray.length);
349 for (var i = this.operatorListIdx; i < operatorsToDisplay; i++) {
350 var line = c('tr');
351 line.className = 'line';
352 line.dataset.idx = i;
353 chunk.appendChild(line);
354 var checked = this.breakPoints.indexOf(i) != -1;
355 var args = operatorList.argsArray[i] || [];
357 var breakCell = c('td');
358 var cbox = c('input');
359 cbox.type = 'checkbox';
360 cbox.className = 'points';
361 cbox.checked = checked;
362 cbox.dataset.idx = i;
363 cbox.onclick = cboxOnClick;
365 breakCell.appendChild(cbox);
366 line.appendChild(breakCell);
367 line.appendChild(c('td', i.toString()));
368 var fn = opMap[operatorList.fnArray[i]];
369 var decArgs = args;
370 if (fn in glyphCommands) {
371 var glyphIndex = glyphCommands[fn];
372 var glyphs = args[glyphIndex];
373 decArgs = args.slice();
374 var newArg;
375 if (fn === 'showSpacedText') {
376 newArg = [];
377 for (var j = 0; j < glyphs.length; j++) {
378 if (typeof glyphs[j] === 'number') {
379 newArg.push(glyphs[j]);
380 } else {
381 newArg.push(glyphsToString(glyphs[j]));
382 }
383 }
384 } else {
385 newArg = glyphsToString(glyphs);
386 }
387 decArgs[glyphIndex] = newArg;
388 }
389 line.appendChild(c('td', fn));
390 line.appendChild(c('td', JSON.stringify(simplifyArgs(decArgs))));
391 }
392 if (operatorsToDisplay < operatorList.fnArray.length) {
393 line = c('tr');
394 var lastCell = c('td', '...');
395 lastCell.colspan = 4;
396 chunk.appendChild(lastCell);
397 }
398 this.operatorListIdx = operatorList.fnArray.length;
399 this.table.appendChild(chunk);
400 },
401 getNextBreakPoint: function getNextBreakPoint() {
402 this.breakPoints.sort(function(a, b) { return a - b; });
403 for (var i = 0; i < this.breakPoints.length; i++) {
404 if (this.breakPoints[i] > this.currentIdx) {
405 return this.breakPoints[i];
406 }
407 }
408 return null;
409 },
410 breakIt: function breakIt(idx, callback) {
411 StepperManager.selectStepper(this.pageIndex, true);
412 var self = this;
413 var dom = document;
414 self.currentIdx = idx;
415 var listener = function(e) {
416 switch (e.keyCode) {
417 case 83: // step
418 dom.removeEventListener('keydown', listener, false);
419 self.nextBreakPoint = self.currentIdx + 1;
420 self.goTo(-1);
421 callback();
422 break;
423 case 67: // continue
424 dom.removeEventListener('keydown', listener, false);
425 var breakPoint = self.getNextBreakPoint();
426 self.nextBreakPoint = breakPoint;
427 self.goTo(-1);
428 callback();
429 break;
430 }
431 };
432 dom.addEventListener('keydown', listener, false);
433 self.goTo(idx);
434 },
435 goTo: function goTo(idx) {
436 var allRows = this.panel.getElementsByClassName('line');
437 for (var x = 0, xx = allRows.length; x < xx; ++x) {
438 var row = allRows[x];
439 if (row.dataset.idx == idx) {
440 row.style.backgroundColor = 'rgb(251,250,207)';
441 row.scrollIntoView();
442 } else {
443 row.style.backgroundColor = null;
444 }
445 }
446 }
447 };
448 return Stepper;
449 })();
451 var Stats = (function Stats() {
452 var stats = [];
453 function clear(node) {
454 while (node.hasChildNodes()) {
455 node.removeChild(node.lastChild);
456 }
457 }
458 function getStatIndex(pageNumber) {
459 for (var i = 0, ii = stats.length; i < ii; ++i) {
460 if (stats[i].pageNumber === pageNumber) {
461 return i;
462 }
463 }
464 return false;
465 }
466 return {
467 // Properties/functions needed by PDFBug.
468 id: 'Stats',
469 name: 'Stats',
470 panel: null,
471 manager: null,
472 init: function init() {
473 this.panel.setAttribute('style', 'padding: 5px;');
474 PDFJS.enableStats = true;
475 },
476 enabled: false,
477 active: false,
478 // Stats specific functions.
479 add: function(pageNumber, stat) {
480 if (!stat) {
481 return;
482 }
483 var statsIndex = getStatIndex(pageNumber);
484 if (statsIndex !== false) {
485 var b = stats[statsIndex];
486 this.panel.removeChild(b.div);
487 stats.splice(statsIndex, 1);
488 }
489 var wrapper = document.createElement('div');
490 wrapper.className = 'stats';
491 var title = document.createElement('div');
492 title.className = 'title';
493 title.textContent = 'Page: ' + pageNumber;
494 var statsDiv = document.createElement('div');
495 statsDiv.textContent = stat.toString();
496 wrapper.appendChild(title);
497 wrapper.appendChild(statsDiv);
498 stats.push({ pageNumber: pageNumber, div: wrapper });
499 stats.sort(function(a, b) { return a.pageNumber - b.pageNumber; });
500 clear(this.panel);
501 for (var i = 0, ii = stats.length; i < ii; ++i) {
502 this.panel.appendChild(stats[i].div);
503 }
504 },
505 cleanup: function () {
506 stats = [];
507 clear(this.panel);
508 }
509 };
510 })();
512 // Manages all the debugging tools.
513 var PDFBug = (function PDFBugClosure() {
514 var panelWidth = 300;
515 var buttons = [];
516 var activePanel = null;
518 return {
519 tools: [
520 FontInspector,
521 StepperManager,
522 Stats
523 ],
524 enable: function(ids) {
525 var all = false, tools = this.tools;
526 if (ids.length === 1 && ids[0] === 'all') {
527 all = true;
528 }
529 for (var i = 0; i < tools.length; ++i) {
530 var tool = tools[i];
531 if (all || ids.indexOf(tool.id) !== -1) {
532 tool.enabled = true;
533 }
534 }
535 if (!all) {
536 // Sort the tools by the order they are enabled.
537 tools.sort(function(a, b) {
538 var indexA = ids.indexOf(a.id);
539 indexA = indexA < 0 ? tools.length : indexA;
540 var indexB = ids.indexOf(b.id);
541 indexB = indexB < 0 ? tools.length : indexB;
542 return indexA - indexB;
543 });
544 }
545 },
546 init: function init() {
547 /*
548 * Basic Layout:
549 * PDFBug
550 * Controls
551 * Panels
552 * Panel
553 * Panel
554 * ...
555 */
556 var ui = document.createElement('div');
557 ui.id = 'PDFBug';
559 var controls = document.createElement('div');
560 controls.setAttribute('class', 'controls');
561 ui.appendChild(controls);
563 var panels = document.createElement('div');
564 panels.setAttribute('class', 'panels');
565 ui.appendChild(panels);
567 var container = document.getElementById('viewerContainer');
568 container.appendChild(ui);
569 container.style.right = panelWidth + 'px';
571 // Initialize all the debugging tools.
572 var tools = this.tools;
573 var self = this;
574 for (var i = 0; i < tools.length; ++i) {
575 var tool = tools[i];
576 var panel = document.createElement('div');
577 var panelButton = document.createElement('button');
578 panelButton.textContent = tool.name;
579 panelButton.addEventListener('click', (function(selected) {
580 return function(event) {
581 event.preventDefault();
582 self.selectPanel(selected);
583 };
584 })(i));
585 controls.appendChild(panelButton);
586 panels.appendChild(panel);
587 tool.panel = panel;
588 tool.manager = this;
589 if (tool.enabled) {
590 tool.init();
591 } else {
592 panel.textContent = tool.name + ' is disabled. To enable add ' +
593 ' "' + tool.id + '" to the pdfBug parameter ' +
594 'and refresh (seperate multiple by commas).';
595 }
596 buttons.push(panelButton);
597 }
598 this.selectPanel(0);
599 },
600 cleanup: function cleanup() {
601 for (var i = 0, ii = this.tools.length; i < ii; i++) {
602 if (this.tools[i].enabled) {
603 this.tools[i].cleanup();
604 }
605 }
606 },
607 selectPanel: function selectPanel(index) {
608 if (typeof index !== 'number') {
609 index = this.tools.indexOf(index);
610 }
611 if (index === activePanel) {
612 return;
613 }
614 activePanel = index;
615 var tools = this.tools;
616 for (var j = 0; j < tools.length; ++j) {
617 if (j == index) {
618 buttons[j].setAttribute('class', 'active');
619 tools[j].active = true;
620 tools[j].panel.removeAttribute('hidden');
621 } else {
622 buttons[j].setAttribute('class', '');
623 tools[j].active = false;
624 tools[j].panel.setAttribute('hidden', 'true');
625 }
626 }
627 }
628 };
629 })();