browser/extensions/pdfjs/content/web/debugger.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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 })();

mercurial