diff --git a/.hgtags b/.hgtags index 15326cead1b..8248dcc837e 100644 --- a/.hgtags +++ b/.hgtags @@ -105,3 +105,4 @@ ba2cc1eda988a1614d8986ae145d28e1268409b9 FIREFOX_AURORA_29_BASE-m 0000000000000000000000000000000000000000 FIREFOX_AURORA_29_BASE-m ba2cc1eda988a1614d8986ae145d28e1268409b9 FIREFOX_AURORA_29_BASE 9f12a9fab080f2d363d7424e25b9ffe85ebc3414 FIREFOX_AURORA_28_BASE +83c9853e136451474dfa6d1aaa60a7fca7d2d83a FIREFOX_AURORA_30_BASE diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index ee8158c731c..1219b6f526b 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -676,6 +676,19 @@ pref("hal.processPriorityManager.gonk.BACKGROUND.Nice", 18); // Processes get this niceness when they have low CPU priority. pref("hal.processPriorityManager.gonk.LowCPUNice", 18); +// By default the compositor thread on gonk runs without real-time priority. RT +// priority can be enabled by setting this pref to a value between 1 and 99. +// Note that audio processing currently runs at RT priority 2 or 3 at most. +// +// If RT priority is disabled, then the compositor nice value is used. The +// code will default to ANDROID_PRIORITY_URGENT_DISPLAY which is -8. Per gfx +// request we are keeping the compositor at nice level 0 until we can complete +// the investigation in bug 982972. +// +// Do not change these values without gfx team review. +pref("hal.gonk.compositor.rt_priority", 0); +pref("hal.gonk.compositor.nice", 0); + // Fire a memory pressure event when the system has less than Xmb of memory // remaining. You should probably set this just above Y.KillUnderKB for // the highest priority class Y that you want to make an effort to keep alive. diff --git a/b2g/chrome/content/devtools.js b/b2g/chrome/content/devtools.js index 47ba6367d7d..136118af1b5 100644 --- a/b2g/chrome/content/devtools.js +++ b/b2g/chrome/content/devtools.js @@ -44,9 +44,10 @@ let developerHUD = { /** * This method registers a metric watcher that will watch one or more metrics - * of apps that are being tracked. A watcher must implement the trackApp(app) - * and untrackApp(app) methods, add entries to the app.metrics map, keep them - * up-to-date, and call app.display() when values were changed. + * on app frames that are being tracked. A watcher must implement the + * `trackTarget(target)` and `untrackTarget(target)` methods, register + * observed metrics with `target.register(metric)`, and keep them up-to-date + * with `target.update(metric, value, message)` when necessary. */ registerWatcher: function dwp_registerWatcher(watcher) { this._watchers.unshift(watcher); @@ -87,7 +88,7 @@ let developerHUD = { }); }); - SettingsListener.observe('hud.logging', enabled => { + SettingsListener.observe('hud.logging', this._logging, enabled => { this._logging = enabled; }); }, @@ -194,9 +195,9 @@ let developerHUD = { /** - * An App object represents all there is to know about a Firefox OS app that is - * being tracked, e.g. its manifest information, current values of watched - * metrics, and how to update these values on the front-end. + * A Target object represents all there is to know about a Firefox OS app frame + * that is being tracked, e.g. a pointer to the frame, current values of watched + * metrics, and how to notify the front-end when metrics have changed. */ function Target(frame, actor) { this.frame = frame; @@ -276,17 +277,27 @@ Target.prototype = { /** * The Console Watcher tracks the following metrics in apps: reflows, warnings, - * and errors. + * and errors, with security errors reported separately. */ let consoleWatcher = { + _client: null, _targets: new Map(), _watching: { reflows: false, warnings: false, - errors: false + errors: false, + security: false }, - _client: null, + _security: [ + 'Mixed Content Blocker', + 'Mixed Content Message', + 'CSP', + 'Invalid HSTS Headers', + 'Insecure Password Field', + 'SSL', + 'CORS' + ], init: function cw_init(client) { this._client = client; @@ -296,7 +307,7 @@ let consoleWatcher = { for (let key in watching) { let metric = key; - SettingsListener.observe('hud.' + metric, false, watch => { + SettingsListener.observe('hud.' + metric, watching[metric], watch => { // Watch or unwatch the metric. if (watching[metric] = watch) { return; @@ -319,6 +330,7 @@ let consoleWatcher = { target.register('reflows'); target.register('warnings'); target.register('errors'); + target.register('security'); this._client.request({ to: target.actor.consoleActor, @@ -357,13 +369,17 @@ let consoleWatcher = { output += 'error ('; } + if (this._security.indexOf(pageError.category) > -1) { + metric = 'security'; + } + let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError; output += category + '): "' + (errorMessage.initial || errorMessage) + '" in ' + sourceName + ':' + lineNumber + ':' + columnNumber; break; case 'consoleAPICall': - switch (packet.output.level) { + switch (packet.message.level) { case 'error': metric = 'errors'; diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 6bcdda886b1..c86519f80ec 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 8513b56522c..c951d6a392d 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 164cada018c..28f03dcbfb3 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 6bcdda886b1..c86519f80ec 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 08d4a36fdbd..67323ae215e 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "branch": "", "revision": "" }, - "revision": "fb54fbd4849b9adfdb4fc236c2cf8161545d736d", + "revision": "f5280df8a66c88c8bafc0062e19a4770ce18e08d", "repo_path": "/integration/gaia-central" } diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 110f0685762..d4081a726d6 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index b238055966d..6078aa017a2 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index a534ca86cdd..d9f711fd01a 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index d9493245586..c8283e2eab0 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 59234837cdb..069b1333a22 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index a6935f45ef5..2016ecb4077 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/confvars.sh b/b2g/confvars.sh index 543f5f7f140..f9d909bac11 100644 --- a/b2g/confvars.sh +++ b/b2g/confvars.sh @@ -5,7 +5,7 @@ MOZ_APP_BASENAME=B2G MOZ_APP_VENDOR=Mozilla -MOZ_APP_VERSION=30.0a1 +MOZ_APP_VERSION=31.0a1 MOZ_APP_UA_NAME=Firefox MOZ_UA_OS_AGNOSTIC=1 diff --git a/browser/config/version.txt b/browser/config/version.txt index 507e3256cdd..19ea970e00a 100644 --- a/browser/config/version.txt +++ b/browser/config/version.txt @@ -1 +1 @@ -30.0a1 +31.0a1 diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js index ead40897b7e..6c5b36d4765 100644 --- a/browser/devtools/debugger/debugger-panes.js +++ b/browser/devtools/debugger/debugger-panes.js @@ -7,8 +7,9 @@ "use strict"; // Used to detect minification for automatic pretty printing -const SAMPLE_SIZE = 30; // no of lines -const INDENT_COUNT_THRESHOLD = 20; // percentage +const SAMPLE_SIZE = 50; // no of lines +const INDENT_COUNT_THRESHOLD = 5; // percentage +const CHARACTER_LIMIT = 250; // line character limit /** * Functions handling the sources UI. @@ -1500,6 +1501,7 @@ let SourceUtils = { let lineStartIndex = 0; let lines = 0; let indentCount = 0; + let overCharLimit = false; // Strip comments. aText = aText.replace(/\/\*[\S\s]*?\*\/|\/\/(.+|\n)/g, ""); @@ -1512,9 +1514,15 @@ let SourceUtils = { if (/^\s+/.test(aText.slice(lineStartIndex, lineEndIndex))) { indentCount++; } + // For files with no indents but are not minified. + if ((lineEndIndex - lineStartIndex) > CHARACTER_LIMIT) { + overCharLimit = true; + break; + } lineStartIndex = lineEndIndex + 1; } - isMinified = ((indentCount / lines ) * 100) < INDENT_COUNT_THRESHOLD; + isMinified = ((indentCount / lines ) * 100) < INDENT_COUNT_THRESHOLD || + overCharLimit; this._minifiedCache.set(sourceClient, isMinified); return isMinified; diff --git a/browser/extensions/pdfjs/README.mozilla b/browser/extensions/pdfjs/README.mozilla index e6a95141510..7058e039b93 100644 --- a/browser/extensions/pdfjs/README.mozilla +++ b/browser/extensions/pdfjs/README.mozilla @@ -1,4 +1,4 @@ This is the pdf.js project output, https://github.com/mozilla/pdf.js -Current extension version is: 0.8.1114 +Current extension version is: 0.8.1181 diff --git a/browser/extensions/pdfjs/content/PdfJs.jsm b/browser/extensions/pdfjs/content/PdfJs.jsm index 89800546bc2..96ec430f0b7 100644 --- a/browser/extensions/pdfjs/content/PdfJs.jsm +++ b/browser/extensions/pdfjs/content/PdfJs.jsm @@ -61,6 +61,27 @@ function getIntPref(aPref, aDefaultValue) { } } +function initializeDefaultPreferences() { + Cu.import('resource://pdf.js/default_preferences.js'); + + var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.'); + var defaultValue; + for (var key in DEFAULT_PREFERENCES) { + defaultValue = DEFAULT_PREFERENCES[key]; + switch (typeof defaultValue) { + case 'boolean': + defaultBranch.setBoolPref(key, defaultValue); + break; + case 'number': + defaultBranch.setIntPref(key, defaultValue); + break; + case 'string': + defaultBranch.setCharPref(key, defaultValue); + break; + } + } +} + // Register/unregister a constructor as a factory. function Factory() {} Factory.prototype = { @@ -104,6 +125,8 @@ let PdfJs = { Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false); Services.obs.addObserver(this, TOPIC_PLUGINS_LIST_UPDATED, false); Services.obs.addObserver(this, TOPIC_PLUGIN_INFO_UPDATED, false); + + initializeDefaultPreferences(); }, _migrate: function migrate() { diff --git a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm index c4d8aba678f..fcbc56ed89d 100644 --- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm +++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm @@ -16,7 +16,7 @@ */ /* jshint esnext:true */ /* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils, - dump, NetworkManager, PdfJsTelemetry, DEFAULT_PREFERENCES */ + dump, NetworkManager, PdfJsTelemetry */ 'use strict'; @@ -33,16 +33,14 @@ const PDF_CONTENT_TYPE = 'application/pdf'; const PREF_PREFIX = 'pdfjs'; const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html'; const MAX_DATABASE_LENGTH = 4096; -const MAX_STRING_PREF_LENGTH = 4096; +const MAX_NUMBER_OF_PREFS = 50; +const MAX_STRING_PREF_LENGTH = 128; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/NetUtil.jsm'); Cu.import('resource://pdf.js/network.js'); -// Load the default preferences. -Cu.import('resource://pdf.js/default_preferences.js'); - XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils', 'resource://gre/modules/PrivateBrowsingUtils.jsm'); @@ -453,56 +451,60 @@ ChromeActions.prototype = { .updateControlState(result, findPrevious); }, setPreferences: function(prefs) { - var prefValue, defaultValue, prefName, prefType, defaultType; - - for (var key in DEFAULT_PREFERENCES) { + var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.'); + var numberOfPrefs = 0; + var prefValue, prefName; + for (var key in prefs) { + if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) { + log('setPreferences - Exceeded the maximum number of preferences ' + + 'that is allowed to be set at once.'); + break; + } else if (!defaultBranch.getPrefType(key)) { + continue; + } prefValue = prefs[key]; - defaultValue = DEFAULT_PREFERENCES[key]; prefName = (PREF_PREFIX + '.' + key); - - if (prefValue === undefined || prefValue === defaultValue) { - Services.prefs.clearUserPref(prefName); - } else { - prefType = typeof prefValue; - defaultType = typeof defaultValue; - - if (prefType !== defaultType) { - continue; - } - switch (defaultType) { - case 'boolean': - setBoolPref(prefName, prefValue); - break; - case 'number': - setIntPref(prefName, prefValue); - break; - case 'string': - // Protect against adding arbitrarily long strings in about:config. - if (prefValue.length <= MAX_STRING_PREF_LENGTH) { - setStringPref(prefName, prefValue); - } - break; - } + switch (typeof prefValue) { + case 'boolean': + setBoolPref(prefName, prefValue); + break; + case 'number': + setIntPref(prefName, prefValue); + break; + case 'string': + if (prefValue.length > MAX_STRING_PREF_LENGTH) { + log('setPreferences - Exceeded the maximum allowed length ' + + 'for a string preference.'); + } else { + setStringPref(prefName, prefValue); + } + break; } } }, - getPreferences: function() { - var currentPrefs = {}; - var defaultValue, prefName; - - for (var key in DEFAULT_PREFERENCES) { - defaultValue = DEFAULT_PREFERENCES[key]; + getPreferences: function(prefs) { + var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.'); + var currentPrefs = {}, numberOfPrefs = 0; + var prefValue, prefName; + 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; + } + prefValue = prefs[key]; prefName = (PREF_PREFIX + '.' + key); - - switch (typeof defaultValue) { + switch (typeof prefValue) { case 'boolean': - currentPrefs[key] = getBoolPref(prefName, defaultValue); + currentPrefs[key] = getBoolPref(prefName, prefValue); break; case 'number': - currentPrefs[key] = getIntPref(prefName, defaultValue); + currentPrefs[key] = getIntPref(prefName, prefValue); break; case 'string': - currentPrefs[key] = getStringPref(prefName, defaultValue); + currentPrefs[key] = getStringPref(prefName, prefValue); break; } } diff --git a/browser/extensions/pdfjs/content/build/pdf.js b/browser/extensions/pdfjs/content/build/pdf.js index 82dfe2d5e9b..d4f23f1787f 100644 --- a/browser/extensions/pdfjs/content/build/pdf.js +++ b/browser/extensions/pdfjs/content/build/pdf.js @@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '0.8.1114'; -PDFJS.build = 'ea9c8f8'; +PDFJS.version = '0.8.1181'; +PDFJS.build = '31ea4e0'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -225,8 +225,9 @@ function backtrace() { } function assert(cond, msg) { - if (!cond) + if (!cond) { error(msg); + } } var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = { @@ -257,10 +258,12 @@ var UnsupportedManager = PDFJS.UnsupportedManager = // Combines two URLs. The baseUrl shall be absolute URL. If the url is an // absolute URL, it will be returned as is. function combineUrl(baseUrl, url) { - if (!url) + if (!url) { return baseUrl; - if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) + } + if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) { return url; + } if (url.charAt(0) == '/') { // absolute path var i = baseUrl.indexOf('://'); @@ -309,8 +312,9 @@ PDFJS.isValidUrl = isValidUrl; // In a well-formed PDF, |cond| holds. If it doesn't, subsequent // behavior is undefined. function assertWellFormed(cond, msg) { - if (!cond) + if (!cond) { error(msg); + } } function shadow(obj, prop, value) { @@ -427,8 +431,9 @@ function bytesToString(bytes) { function stringToBytes(str) { var length = str.length; var bytes = new Uint8Array(length); - for (var n = 0; n < length; ++n) + for (var n = 0; n < length; ++n) { bytes[n] = str.charCodeAt(n) & 0xFF; + } return bytes; } @@ -832,14 +837,15 @@ function isRef(v) { function isPDFFunction(v) { var fnDict; - if (typeof v != 'object') + if (typeof v != 'object') { return false; - else if (isDict(v)) + } else if (isDict(v)) { fnDict = v; - else if (isStream(v)) + } else if (isStream(v)) { fnDict = v.dict; - else + } else { return false; + } return fnDict.has('FunctionType'); } @@ -908,8 +914,9 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { var StatTimer = (function StatTimerClosure() { function rpad(str, pad, length) { - while (str.length < length) + while (str.length < length) { str += pad; + } return str; } function StatTimer() { @@ -919,17 +926,21 @@ var StatTimer = (function StatTimerClosure() { } StatTimer.prototype = { time: function StatTimer_time(name) { - if (!this.enabled) + if (!this.enabled) { return; - if (name in this.started) + } + if (name in this.started) { warn('Timer is already running for ' + name); + } this.started[name] = Date.now(); }, timeEnd: function StatTimer_timeEnd(name) { - if (!this.enabled) + if (!this.enabled) { return; - if (!(name in this.started)) + } + if (!(name in this.started)) { warn('Timer has not been started for ' + name); + } this.times.push({ 'name': name, 'start': this.started[name], @@ -945,8 +956,9 @@ var StatTimer = (function StatTimerClosure() { var longest = 0; for (var i = 0, ii = times.length; i < ii; ++i) { var name = times[i]['name']; - if (name.length > longest) + if (name.length > longest) { longest = name.length; + } } for (var i = 0, ii = times.length; i < ii; ++i) { var span = times[i]; @@ -960,8 +972,9 @@ var StatTimer = (function StatTimerClosure() { })(); PDFJS.createBlob = function createBlob(data, contentType) { - if (typeof Blob !== 'undefined') + if (typeof Blob !== 'undefined') { return new Blob([data], { type: contentType }); + } // Blob builder is deprecated in FF14 and removed in FF18. var bb = new MozBlobBuilder(); bb.append(data); @@ -1238,9 +1251,9 @@ var ColorSpace = (function ColorSpaceClosure() { ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { var IR = ColorSpace.parseToIR(cs, xref, res); - if (IR instanceof AlternateCS) + if (IR instanceof AlternateCS) { return IR; - + } return ColorSpace.fromIR(IR); }; @@ -1261,8 +1274,9 @@ var ColorSpace = (function ColorSpaceClosure() { return new CalGrayCS(whitePoint, blackPoint, gamma); case 'PatternCS': var basePatternCS = IR[1]; - if (basePatternCS) + if (basePatternCS) { basePatternCS = ColorSpace.fromIR(basePatternCS); + } return new PatternCS(basePatternCS); case 'IndexedCS': var baseIndexedCS = IR[1]; @@ -1292,8 +1306,9 @@ var ColorSpace = (function ColorSpaceClosure() { var colorSpaces = res.get('ColorSpace'); if (isDict(colorSpaces)) { var refcs = colorSpaces.get(cs.name); - if (refcs) + if (refcs) { cs = refcs; + } } } @@ -1342,17 +1357,19 @@ var ColorSpace = (function ColorSpaceClosure() { var stream = xref.fetchIfRef(cs[1]); var dict = stream.dict; var numComps = dict.get('N'); - if (numComps == 1) + if (numComps == 1) { return 'DeviceGrayCS'; - if (numComps == 3) + } else if (numComps == 3) { return 'DeviceRgbCS'; - if (numComps == 4) + } else if (numComps == 4) { return 'DeviceCmykCS'; + } break; case 'Pattern': var basePatternCS = cs[1]; - if (basePatternCS) + if (basePatternCS) { basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); + } return ['PatternCS', basePatternCS]; case 'Indexed': case 'I': @@ -1367,10 +1384,11 @@ var ColorSpace = (function ColorSpaceClosure() { case 'DeviceN': var name = cs[1]; var numComps = 1; - if (isName(name)) + if (isName(name)) { numComps = 1; - else if (isArray(name)) + } else if (isArray(name)) { numComps = name.length; + } var alt = ColorSpace.parseToIR(cs[2], xref, res); var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); return ['AlternateCS', numComps, alt, tintFnIR]; @@ -1395,16 +1413,18 @@ var ColorSpace = (function ColorSpaceClosure() { * @param {Number} n Number of components the color space has. */ ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { - if (!decode) + if (!decode) { return true; + } if (n * 2 !== decode.length) { warn('The decode map is not the correct length'); return true; } for (var i = 0, ii = decode.length; i < ii; i += 2) { - if (decode[i] !== 0 || decode[i + 1] != 1) + if (decode[i] !== 0 || decode[i + 1] != 1) { return false; + } } return true; }; @@ -1531,8 +1551,9 @@ var IndexedCS = (function IndexedCSClosure() { lookupArray.set(bytes); } else if (isString(lookup)) { lookupArray = new Uint8Array(length); - for (var i = 0; i < length; ++i) + for (var i = 0; i < length; ++i) { lookupArray[i] = lookup.charCodeAt(i); + } } else if (lookup instanceof Uint8Array || lookup instanceof Array) { lookupArray = lookup; } else { @@ -1865,8 +1886,9 @@ var LabCS = (function LabCSClosure() { this.numComps = 3; this.defaultColor = new Float32Array([0, 0, 0]); - if (!whitePoint) + if (!whitePoint) { error('WhitePoint missing - required for color space Lab'); + } blackPoint = blackPoint || [0, 0, 0]; range = range || [-100, 100, -100, 100]; @@ -1886,8 +1908,9 @@ var LabCS = (function LabCSClosure() { this.ZB = blackPoint[2]; // Validate vars as per spec - if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { error('Invalid WhitePoint components, no fallback available'); + } if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { info('Invalid BlackPoint, falling back to default'); @@ -1905,10 +1928,11 @@ var LabCS = (function LabCSClosure() { // Function g(x) from spec function fn_g(x) { - if (x >= 6 / 29) + if (x >= 6 / 29) { return x * x * x; - else + } else { return (108 / 841) * (x - 4 / 29); + } } function decode(value, high1, low2, high2) { @@ -2006,8 +2030,9 @@ var PDFFunction = (function PDFFunctionClosure() { getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, str) { var length = 1; - for (var i = 0, ii = size.length; i < ii; i++) + for (var i = 0, ii = size.length; i < ii; i++) { length *= size[i]; + } length *= outputSize; var array = []; @@ -2033,8 +2058,9 @@ var PDFFunction = (function PDFFunctionClosure() { getIR: function PDFFunction_getIR(xref, fn) { var dict = fn.dict; - if (!dict) + if (!dict) { dict = fn; + } var types = [this.constructSampled, null, @@ -2044,8 +2070,9 @@ var PDFFunction = (function PDFFunctionClosure() { var typeNum = dict.get('FunctionType'); var typeFn = types[typeNum]; - if (!typeFn) + if (!typeFn) { error('Unknown type of function'); + } return typeFn.call(this, fn, dict, xref); }, @@ -2085,8 +2112,9 @@ var PDFFunction = (function PDFFunctionClosure() { var domain = dict.get('Domain'); var range = dict.get('Range'); - if (!domain || !range) + if (!domain || !range) { error('No domain or range'); + } var inputSize = domain.length / 2; var outputSize = range.length / 2; @@ -2114,10 +2142,11 @@ var PDFFunction = (function PDFFunctionClosure() { encode = toMultiArray(encode); var decode = dict.get('Decode'); - if (!decode) + if (!decode) { decode = range; - else + } else { decode = toMultiArray(decode); + } var samples = this.getSampleArray(size, outputSize, bps, str); @@ -2145,9 +2174,10 @@ var PDFFunction = (function PDFFunctionClosure() { var mask = IR[8]; var range = IR[9]; - if (m != args.length) + if (m != args.length) { error('Incorrect number of arguments: ' + m + ' != ' + args.length); + } var x = args; @@ -2156,8 +2186,9 @@ var PDFFunction = (function PDFFunctionClosure() { var cubeVertices = 1 << m; var cubeN = new Float64Array(cubeVertices); var cubeVertex = new Uint32Array(cubeVertices); - for (var j = 0; j < cubeVertices; j++) + for (var j = 0; j < cubeVertices; j++) { cubeN[j] = 1; + } var k = n, pos = 1; // Map x_i to y_j for 0 <= i < m using the sampled function. @@ -2200,8 +2231,9 @@ var PDFFunction = (function PDFFunctionClosure() { for (var j = 0; j < n; ++j) { // Sum all cube vertices' samples portions var rj = 0; - for (var i = 0; i < cubeVertices; i++) + for (var i = 0; i < cubeVertices; i++) { rj += samples[cubeVertex[i] + j] * cubeN[i]; + } // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, // Decode_2j, Decode_2j+1) @@ -2221,13 +2253,15 @@ var PDFFunction = (function PDFFunctionClosure() { var c1 = dict.get('C1') || [1]; var n = dict.get('N'); - if (!isArray(c0) || !isArray(c1)) + if (!isArray(c0) || !isArray(c1)) { error('Illegal dictionary for interpolated function'); + } var length = c0.length; var diff = []; - for (var i = 0; i < length; ++i) + for (var i = 0; i < length; ++i) { diff.push(c1[i] - c0[i]); + } return [CONSTRUCT_INTERPOLATED, c0, diff, n]; }, @@ -2244,8 +2278,9 @@ var PDFFunction = (function PDFFunctionClosure() { var x = n == 1 ? args[0] : Math.pow(args[0], n); var out = []; - for (var j = 0; j < length; ++j) + for (var j = 0; j < length; ++j) { out.push(c0[j] + (x * diff[j])); + } return out; @@ -2255,17 +2290,20 @@ var PDFFunction = (function PDFFunctionClosure() { constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { var domain = dict.get('Domain'); - if (!domain) + if (!domain) { error('No domain'); + } var inputSize = domain.length / 2; - if (inputSize != 1) + if (inputSize != 1) { error('Bad domain for stiched function'); + } var fnRefs = dict.get('Functions'); var fns = []; - for (var i = 0, ii = fnRefs.length; i < ii; ++i) + for (var i = 0, ii = fnRefs.length; i < ii; ++i) { fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); + } var bounds = dict.get('Bounds'); var encode = dict.get('Encode'); @@ -2286,10 +2324,11 @@ var PDFFunction = (function PDFFunctionClosure() { return function constructStichedFromIRResult(args) { var clip = function constructStichedFromIRClip(v, min, max) { - if (v > max) + if (v > max) { v = max; - else if (v < min) + } else if (v < min) { v = min; + } return v; }; @@ -2297,24 +2336,27 @@ var PDFFunction = (function PDFFunctionClosure() { var v = clip(args[0], domain[0], domain[1]); // calulate which bound the value is in for (var i = 0, ii = bounds.length; i < ii; ++i) { - if (v < bounds[i]) + if (v < bounds[i]) { break; + } } // encode value into domain of function var dmin = domain[0]; - if (i > 0) + if (i > 0) { dmin = bounds[i - 1]; + } var dmax = domain[1]; - if (i < bounds.length) + if (i < bounds.length) { dmax = bounds[i]; + } var rmin = encode[2 * i]; var rmax = encode[2 * i + 1]; var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); - // call the appropropriate function + // call the appropriate function return fns[i]([v2]); }; }, @@ -2324,11 +2366,13 @@ var PDFFunction = (function PDFFunctionClosure() { var domain = dict.get('Domain'); var range = dict.get('Range'); - if (!domain) + if (!domain) { error('No domain.'); + } - if (!range) + if (!range) { error('No range.'); + } var lexer = new PostScriptLexer(fn); var parser = new PostScriptParser(lexer); @@ -2354,18 +2398,20 @@ var PDFFunction = (function PDFFunctionClosure() { } var key = initialStack.join('_'); - if (cache.has(key)) + if (cache.has(key)) { return cache.get(key); + } var stack = evaluator.execute(initialStack); var transformed = []; for (i = numOutputs - 1; i >= 0; --i) { var out = stack.pop(); var rangeIndex = 2 * i; - if (out < range[rangeIndex]) + if (out < range[rangeIndex]) { out = range[rangeIndex]; - else if (out > range[rangeIndex + 1]) + } else if (out > range[rangeIndex + 1]) { out = range[rangeIndex + 1]; + } transformed[i] = out; } cache.set(key, transformed); @@ -2408,21 +2454,25 @@ var PostScriptStack = (function PostScriptStackClosure() { PostScriptStack.prototype = { push: function PostScriptStack_push(value) { - if (this.stack.length >= MAX_STACK_SIZE) + if (this.stack.length >= MAX_STACK_SIZE) { error('PostScript function stack overflow.'); + } this.stack.push(value); }, pop: function PostScriptStack_pop() { - if (this.stack.length <= 0) + if (this.stack.length <= 0) { error('PostScript function stack underflow.'); + } return this.stack.pop(); }, copy: function PostScriptStack_copy(n) { - if (this.stack.length + n >= MAX_STACK_SIZE) + if (this.stack.length + n >= MAX_STACK_SIZE) { error('PostScript function stack overflow.'); + } var stack = this.stack; - for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) + for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) { stack.push(stack[i]); + } }, index: function PostScriptStack_index(n) { this.push(this.stack[this.stack.length - n - 1]); @@ -2468,8 +2518,9 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'jz': // jump if false b = stack.pop(); a = stack.pop(); - if (!a) + if (!a) { counter = b; + } break; case 'j': // jump a = stack.pop(); @@ -2489,10 +2540,11 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'and': b = stack.pop(); a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a && b); - else + } else { stack.push(a & b); + } break; case 'atan': a = stack.pop(); @@ -2501,10 +2553,11 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'bitshift': b = stack.pop(); a = stack.pop(); - if (a > 0) + if (a > 0) { stack.push(a << b); - else + } else { stack.push(a >> b); + } break; case 'ceiling': a = stack.pop(); @@ -2611,18 +2664,20 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { break; case 'not': a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a && b); - else + } else { stack.push(a & b); + } break; case 'or': b = stack.pop(); a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a || b); - else + } else { stack.push(a | b); + } break; case 'pop': stack.pop(); @@ -2660,10 +2715,11 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'xor': b = stack.pop(); a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a != b); - else + } else { stack.push(a ^ b); + } break; default: error('Unknown operator ' + operator); @@ -2677,6 +2733,9 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { })(); +var HIGHLIGHT_OFFSET = 4; // px +var SUPPORTED_TYPES = ['Link', 'Text', 'Widget']; + var Annotation = (function AnnotationClosure() { // 12.5.5: Algorithm: Appearance streams function getTransformMatrix(rect, bbox, matrix) { @@ -2799,25 +2858,50 @@ var Annotation = (function AnnotationClosure() { }, // TODO(mack): Remove this, it's not really that helpful. - getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect) { + getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect, + borderWidth) { assert(!isWorker, 'getEmptyContainer() should be called from main thread'); + var bWidth = borderWidth || 0; + rect = rect || this.data.rect; var element = document.createElement(tagName); - element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; - element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; + element.style.borderWidth = bWidth + 'px'; + var width = rect[2] - rect[0] - 2 * bWidth; + var height = rect[3] - rect[1] - 2 * bWidth; + element.style.width = width + 'px'; + element.style.height = height + 'px'; return element; }, + isInvisible: function Annotation_isInvisible() { + var data = this.data; + if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) { + return false; + } else { + return !!(data && + data.annotationFlags && // Default: not invisible + data.annotationFlags & 0x1); // Invisible + } + }, + isViewable: function Annotation_isViewable() { var data = this.data; - return !!( - data && - (!data.annotationFlags || - !(data.annotationFlags & 0x22)) && // Hidden or NoView - data.rect // rectangle is nessessary - ); + return !!(!this.isInvisible() && + data && + (!data.annotationFlags || + !(data.annotationFlags & 0x22)) && // Hidden or NoView + data.rect); // rectangle is nessessary + }, + + isPrintable: function Annotation_isPrintable() { + var data = this.data; + return !!(!this.isInvisible() && + data && + data.annotationFlags && // Default: not printable + data.annotationFlags & 0x4 && // Print + data.rect); // rectangle is nessessary }, loadResources: function(keys) { @@ -2838,7 +2922,7 @@ var Annotation = (function AnnotationClosure() { return promise; }, - getOperatorList: function Annotation_getToOperatorList(evaluator) { + getOperatorList: function Annotation_getOperatorList(evaluator) { var promise = new LegacyPromise(); @@ -2945,7 +3029,7 @@ var Annotation = (function AnnotationClosure() { var annotation = new Constructor(params); - if (annotation.isViewable()) { + if (annotation.isViewable() || annotation.isPrintable()) { return annotation; } else { warn('unimplemented annotation type: ' + subtype); @@ -2953,7 +3037,7 @@ var Annotation = (function AnnotationClosure() { }; Annotation.appendToOperatorList = function Annotation_appendToOperatorList( - annotations, opList, pdfManager, partialEvaluator) { + annotations, opList, pdfManager, partialEvaluator, intent) { function reject(e) { annotationsReadyPromise.reject(e); @@ -2963,7 +3047,11 @@ var Annotation = (function AnnotationClosure() { var annotationPromises = []; for (var i = 0, n = annotations.length; i < n; ++i) { - annotationPromises.push(annotations[i].getOperatorList(partialEvaluator)); + if (intent === 'display' && annotations[i].isViewable() || + intent === 'print' && annotations[i].isPrintable()) { + annotationPromises.push( + annotations[i].getOperatorList(partialEvaluator)); + } } Promise.all(annotationPromises).then(function(datas) { opList.addOp(OPS.beginAnnotations, []); @@ -3025,8 +3113,9 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { var j, jj; for (j = 0, jj = kids.length; j < jj; j++) { var kidRef = kids[j]; - if (kidRef.num == ref.num && kidRef.gen == ref.gen) + if (kidRef.num == ref.num && kidRef.gen == ref.gen) { break; + } } fieldName.unshift('`' + j); } @@ -3175,9 +3264,64 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { return TextWidgetAnnotation; })(); +var InteractiveAnnotation = (function InteractiveAnnotationClosure() { + function InteractiveAnnotation(params) { + Annotation.call(this, params); + } + + Util.inherit(InteractiveAnnotation, Annotation, { + hasHtml: function InteractiveAnnotation_hasHtml() { + return true; + }, + + highlight: function InteractiveAnnotation_highlight() { + if (this.highlightElement && + this.highlightElement.hasAttribute('hidden')) { + this.highlightElement.removeAttribute('hidden'); + } + }, + + unhighlight: function InteractiveAnnotation_unhighlight() { + if (this.highlightElement && + !this.highlightElement.hasAttribute('hidden')) { + this.highlightElement.setAttribute('hidden', true); + } + }, + + initContainer: function InteractiveAnnotation_initContainer() { + + var item = this.data; + var rect = item.rect; + + var container = this.getEmptyContainer('section', rect, item.borderWidth); + container.style.backgroundColor = item.color; + + var color = item.color; + var rgb = []; + for (var i = 0; i < 3; ++i) { + rgb[i] = Math.round(color[i] * 255); + } + item.colorCssRgb = Util.makeCssRgb(rgb); + + var highlight = document.createElement('div'); + highlight.className = 'annotationHighlight'; + highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px'; + highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px'; + highlight.setAttribute('hidden', true); + + this.highlightElement = highlight; + container.appendChild(this.highlightElement); + + return container; + } + }); + + return InteractiveAnnotation; +})(); + var TextAnnotation = (function TextAnnotationClosure() { function TextAnnotation(params) { - Annotation.call(this, params); + InteractiveAnnotation.call(this, params); if (params.data) { return; @@ -3190,22 +3334,21 @@ var TextAnnotation = (function TextAnnotationClosure() { var title = dict.get('T'); data.content = stringToPDFString(content || ''); data.title = stringToPDFString(title || ''); - data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name; + + if (data.hasAppearance) { + data.name = 'NoIcon'; + } else { + data.name = dict.has('Name') ? dict.get('Name').name : 'Note'; + } + + if (dict.has('C')) { + data.hasBgColor = true; + } } var ANNOT_MIN_SIZE = 10; - Util.inherit(TextAnnotation, Annotation, { - - getOperatorList: function TextAnnotation_getOperatorList(evaluator) { - var promise = new LegacyPromise(); - promise.resolve(new OperatorList()); - return promise; - }, - - hasHtml: function TextAnnotation_hasHtml() { - return true; - }, + Util.inherit(TextAnnotation, InteractiveAnnotation, { getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) { assert(!isWorker, 'getHtmlElement() shall be called from main thread'); @@ -3221,23 +3364,40 @@ var TextAnnotation = (function TextAnnotationClosure() { rect[2] = rect[0] + (rect[3] - rect[1]); // make it square } - var container = this.getEmptyContainer('section', rect); + var container = this.initContainer(); container.className = 'annotText'; - var image = document.createElement('img'); + var image = document.createElement('img'); image.style.height = container.style.height; + image.style.width = container.style.width; var iconName = item.name; image.src = PDFJS.imageResourcesPath + 'annotation-' + iconName.toLowerCase() + '.svg'; image.alt = '[{{type}} Annotation]'; image.dataset.l10nId = 'text_annotation_type'; image.dataset.l10nArgs = JSON.stringify({type: iconName}); + + var contentWrapper = document.createElement('div'); + contentWrapper.className = 'annotTextContentWrapper'; + contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px'; + contentWrapper.style.top = '-10px'; + var content = document.createElement('div'); + content.className = 'annotTextContent'; content.setAttribute('hidden', true); + if (item.hasBgColor) { + var color = item.color; + var rgb = []; + for (var i = 0; i < 3; ++i) { + // Enlighten the color (70%) + var c = Math.round(color[i] * 255); + rgb[i] = Math.round((255 - c) * 0.7) + c; + } + content.style.backgroundColor = Util.makeCssRgb(rgb); + } + var title = document.createElement('h1'); var text = document.createElement('p'); - content.style.left = Math.floor(rect[2] - rect[0]) + 'px'; - content.style.top = '0px'; title.textContent = item.title; if (!item.content && !item.title) { @@ -3248,33 +3408,63 @@ var TextAnnotation = (function TextAnnotationClosure() { for (var i = 0, ii = lines.length; i < ii; ++i) { var line = lines[i]; e.appendChild(document.createTextNode(line)); - if (i < (ii - 1)) + if (i < (ii - 1)) { e.appendChild(document.createElement('br')); + } } text.appendChild(e); - var showAnnotation = function showAnnotation() { - container.style.zIndex += 1; - content.removeAttribute('hidden'); + var pinned = false; + + var showAnnotation = function showAnnotation(pin) { + if (pin) { + pinned = true; + } + if (content.hasAttribute('hidden')) { + container.style.zIndex += 1; + content.removeAttribute('hidden'); + } }; - var hideAnnotation = function hideAnnotation(e) { - if (e.toElement || e.relatedTarget) { // No context menu is used + var hideAnnotation = function hideAnnotation(unpin) { + if (unpin) { + pinned = false; + } + if (!content.hasAttribute('hidden') && !pinned) { container.style.zIndex -= 1; content.setAttribute('hidden', true); } }; - content.addEventListener('mouseover', showAnnotation, false); - content.addEventListener('mouseout', hideAnnotation, false); - image.addEventListener('mouseover', showAnnotation, false); - image.addEventListener('mouseout', hideAnnotation, false); + var toggleAnnotation = function toggleAnnotation() { + if (pinned) { + hideAnnotation(true); + } else { + showAnnotation(true); + } + }; + + var self = this; + image.addEventListener('click', function image_clickHandler() { + toggleAnnotation(); + }, false); + image.addEventListener('mouseover', function image_mouseOverHandler() { + showAnnotation(); + }, false); + image.addEventListener('mouseout', function image_mouseOutHandler() { + hideAnnotation(); + }, false); + + content.addEventListener('click', function content_clickHandler() { + hideAnnotation(true); + }, false); } content.appendChild(title); content.appendChild(text); + contentWrapper.appendChild(content); container.appendChild(image); - container.appendChild(content); + container.appendChild(contentWrapper); return container; } @@ -3285,7 +3475,7 @@ var TextAnnotation = (function TextAnnotationClosure() { var LinkAnnotation = (function LinkAnnotationClosure() { function LinkAnnotation(params) { - Annotation.call(this, params); + InteractiveAnnotation.call(this, params); if (params.data) { return; @@ -3348,36 +3538,28 @@ var LinkAnnotation = (function LinkAnnotationClosure() { return url; } - Util.inherit(LinkAnnotation, Annotation, { + Util.inherit(LinkAnnotation, InteractiveAnnotation, { hasOperatorList: function LinkAnnotation_hasOperatorList() { return false; }, - hasHtml: function LinkAnnotation_hasHtml() { - return true; - }, - getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) { - var rect = this.data.rect; - var element = document.createElement('a'); - var borderWidth = this.data.borderWidth; - element.style.borderWidth = borderWidth + 'px'; - var color = this.data.color; - var rgb = []; - for (var i = 0; i < 3; ++i) { - rgb[i] = Math.round(color[i] * 255); - } - element.style.borderColor = Util.makeCssRgb(rgb); - element.style.borderStyle = 'solid'; + var container = this.initContainer(); + container.className = 'annotLink'; - var width = rect[2] - rect[0] - 2 * borderWidth; - var height = rect[3] - rect[1] - 2 * borderWidth; - element.style.width = width + 'px'; - element.style.height = height + 'px'; + var item = this.data; + var rect = item.rect; - element.href = this.data.url || ''; - return element; + container.style.borderColor = item.colorCssRgb; + container.style.borderStyle = 'solid'; + + var link = document.createElement('a'); + link.href = link.title = this.data.url || ''; + + container.appendChild(link); + + return container; } }); @@ -3704,10 +3886,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { this.stats.enabled = !!globalScope.PDFJS.enableStats; this.commonObjs = transport.commonObjs; this.objs = new PDFObjects(); - this.receivingOperatorList = false; this.cleanupAfterRender = false; this.pendingDestroy = false; - this.renderTasks = []; + this.intentStates = {}; } PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ { /** @@ -3786,12 +3967,21 @@ var PDFPageProxy = (function PDFPageProxyClosure() { // this call to render. this.pendingDestroy = false; + var renderingIntent = 'intent' in params ? + (params.intent == 'print' ? 'print' : 'display') : + 'display'; + + if (!this.intentStates[renderingIntent]) { + this.intentStates[renderingIntent] = {}; + } + var intentState = this.intentStates[renderingIntent]; + // If there is no displayReadyPromise yet, then the operatorList was never // requested before. Make the request and create the promise. - if (!this.displayReadyPromise) { - this.receivingOperatorList = true; - this.displayReadyPromise = new LegacyPromise(); - this.operatorList = { + if (!intentState.displayReadyPromise) { + intentState.receivingOperatorList = true; + intentState.displayReadyPromise = new LegacyPromise(); + intentState.operatorList = { fnArray: [], argsArray: [], lastChunk: false @@ -3799,18 +3989,23 @@ var PDFPageProxy = (function PDFPageProxyClosure() { this.stats.time('Page Request'); this.transport.messageHandler.send('RenderPageRequest', { - pageIndex: this.pageNumber - 1 + pageIndex: this.pageNumber - 1, + intent: renderingIntent }); } var internalRenderTask = new InternalRenderTask(complete, params, this.objs, this.commonObjs, - this.operatorList, this.pageNumber); - this.renderTasks.push(internalRenderTask); + intentState.operatorList, + this.pageNumber); + if (!intentState.renderTasks) { + intentState.renderTasks = []; + } + intentState.renderTasks.push(internalRenderTask); var renderTask = new RenderTask(internalRenderTask); var self = this; - this.displayReadyPromise.then( + intentState.displayReadyPromise.then( function pageDisplayReadyPromise(transparency) { if (self.pendingDestroy) { complete(); @@ -3826,9 +4021,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { ); function complete(error) { - var i = self.renderTasks.indexOf(internalRenderTask); + var i = intentState.renderTasks.indexOf(internalRenderTask); if (i >= 0) { - self.renderTasks.splice(i, 1); + intentState.renderTasks.splice(i, 1); } if (self.cleanupAfterRender) { @@ -3876,14 +4071,17 @@ var PDFPageProxy = (function PDFPageProxyClosure() { */ _tryDestroy: function PDFPageProxy__destroy() { if (!this.pendingDestroy || - this.renderTasks.length !== 0 || - this.receivingOperatorList) { + Object.keys(this.intentStates).some(function(intent) { + var intentState = this.intentStates[intent]; + return intentState.renderTasks.length !== 0 || + intentState.receivingOperatorList; + }, this)) { return; } - delete this.operatorList; - delete this.displayReadyPromise; - delete this.annotationsPromise; + Object.keys(this.intentStates).forEach(function(intent) { + delete this.intentStates[intent]; + }, this); this.objs.clear(); this.pendingDestroy = false; }, @@ -3891,28 +4089,33 @@ var PDFPageProxy = (function PDFPageProxyClosure() { * For internal use only. * @ignore */ - _startRenderPage: function PDFPageProxy_startRenderPage(transparency) { - this.displayReadyPromise.resolve(transparency); + _startRenderPage: function PDFPageProxy_startRenderPage(transparency, + intent) { + var intentState = this.intentStates[intent]; + intentState.displayReadyPromise.resolve(transparency); }, /** * For internal use only. * @ignore */ - _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) { + _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk, + intent) { + var intentState = this.intentStates[intent]; // Add the new chunk to the current operator list. for (var i = 0, ii = operatorListChunk.length; i < ii; i++) { - this.operatorList.fnArray.push(operatorListChunk.fnArray[i]); - this.operatorList.argsArray.push(operatorListChunk.argsArray[i]); + intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); + intentState.operatorList.argsArray.push( + operatorListChunk.argsArray[i]); } - this.operatorList.lastChunk = operatorListChunk.lastChunk; + intentState.operatorList.lastChunk = operatorListChunk.lastChunk; // Notify all the rendering tasks there are more operators to be consumed. - for (var i = 0; i < this.renderTasks.length; i++) { - this.renderTasks[i].operatorListChanged(); + for (var i = 0; i < intentState.renderTasks.length; i++) { + intentState.renderTasks[i].operatorListChanged(); } if (operatorListChunk.lastChunk) { - this.receivingOperatorList = false; + intentState.receivingOperatorList = false; this._tryDestroy(); } } @@ -4128,13 +4331,13 @@ var WorkerTransport = (function WorkerTransportClosure() { var page = this.pageCache[data.pageIndex]; page.stats.timeEnd('Page Request'); - page._startRenderPage(data.transparency); + page._startRenderPage(data.transparency, data.intent); }, this); messageHandler.on('RenderPageChunk', function transportRender(data) { var page = this.pageCache[data.pageIndex]; - page._renderPageChunk(data.operatorList); + page._renderPageChunk(data.operatorList, data.intent); }, this); messageHandler.on('commonobj', function transportObj(data) { @@ -4177,8 +4380,9 @@ var WorkerTransport = (function WorkerTransportClosure() { var pageIndex = data[1]; var type = data[2]; var pageProxy = this.pageCache[pageIndex]; - if (pageProxy.objs.hasData(id)) + if (pageProxy.objs.hasData(id)) { return; + } switch (type) { case 'JpegStream': @@ -4214,10 +4418,11 @@ var WorkerTransport = (function WorkerTransportClosure() { this.workerReadyPromise.reject(data); }, this); - messageHandler.on('PageError', function transportError(data) { + messageHandler.on('PageError', function transportError(data, intent) { var page = this.pageCache[data.pageNum - 1]; - if (page.displayReadyPromise) - page.displayReadyPromise.reject(data.error); + var intentState = page.intentStates[intent]; + if (intentState.displayReadyPromise) + intentState.displayReadyPromise.reject(data.error); else error(data.error); }, this); @@ -4619,20 +4824,22 @@ var Metadata = PDFJS.Metadata = (function MetadataClosure() { if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in rdf = rdf.firstChild; - while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') + while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') { rdf = rdf.nextSibling; + } } var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null; - if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) + if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) { return; + } var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength; - for (i = 0, length = children.length; i < length; i++) { desc = children[i]; - if (desc.nodeName.toLowerCase() !== 'rdf:description') + if (desc.nodeName.toLowerCase() !== 'rdf:description') { continue; + } for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) { if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') { @@ -5143,20 +5350,15 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } else if (imgData.kind === ImageKind.RGBA_32BPP) { // RGBA, 32-bits per pixel. - var haveSetAndSubarray = 'set' in dest && 'subarray' in src; for (var i = 0; i < totalChunks; i++) { var thisChunkHeight = (i < fullChunks) ? fullChunkHeight : partialChunkHeight; var elemsInThisChunk = imgData.width * thisChunkHeight * 4; - if (haveSetAndSubarray) { - dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); - srcPos += elemsInThisChunk; - } else { - for (var j = 0; j < elemsInThisChunk; j++) { - dest[j] = src[srcPos++]; - } - } + + dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); + srcPos += elemsInThisChunk; + ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); } @@ -7180,15 +7382,16 @@ var FontLoader = { insertRule: function fontLoaderInsertRule(rule) { var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); if (!styleElement) { - styleElement = document.createElement('style'); - styleElement.id = 'PDFJS_FONT_STYLE_TAG'; - document.documentElement.getElementsByTagName('head')[0].appendChild( - styleElement); + styleElement = document.createElement('style'); + styleElement.id = 'PDFJS_FONT_STYLE_TAG'; + document.documentElement.getElementsByTagName('head')[0].appendChild( + styleElement); } var styleSheet = styleElement.sheet; styleSheet.insertRule(rule, styleSheet.cssRules.length); }, + clear: function fontLoaderClear() { var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); if (styleElement) { @@ -7200,8 +7403,9 @@ var FontLoader = { for (var i = 0, ii = fonts.length; i < ii; i++) { var font = fonts[i]; - if (font.attached) + if (font.attached) { continue; + } font.attached = true; font.bindDOM() @@ -7225,8 +7429,9 @@ var FontFace = (function FontFaceClosure() { } FontFace.prototype = { bindDOM: function FontFace_bindDOM() { - if (!this.data) + if (!this.data) { return null; + } if (PDFJS.disableFontFace) { this.disableFontFace = true; @@ -7240,15 +7445,16 @@ var FontFace = (function FontFaceClosure() { var url = ('url(data:' + this.mimetype + ';base64,' + window.btoa(data) + ');'); var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}'; - FontLoader.insertRule(rule); if (PDFJS.pdfBug && 'FontInspector' in globalScope && - globalScope['FontInspector'].enabled) + globalScope['FontInspector'].enabled) { globalScope['FontInspector'].fontAdded(this, url); + } return rule; }, + getPathGenerator: function (objs, character) { if (!(character in this.compiledGlyphs)) { var js = objs.get(this.loadedName + '_path_' + character); diff --git a/browser/extensions/pdfjs/content/build/pdf.worker.js b/browser/extensions/pdfjs/content/build/pdf.worker.js index bc3d6cff026..927388405e8 100644 --- a/browser/extensions/pdfjs/content/build/pdf.worker.js +++ b/browser/extensions/pdfjs/content/build/pdf.worker.js @@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '0.8.1114'; -PDFJS.build = 'ea9c8f8'; +PDFJS.version = '0.8.1181'; +PDFJS.build = '31ea4e0'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -225,8 +225,9 @@ function backtrace() { } function assert(cond, msg) { - if (!cond) + if (!cond) { error(msg); + } } var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = { @@ -257,10 +258,12 @@ var UnsupportedManager = PDFJS.UnsupportedManager = // Combines two URLs. The baseUrl shall be absolute URL. If the url is an // absolute URL, it will be returned as is. function combineUrl(baseUrl, url) { - if (!url) + if (!url) { return baseUrl; - if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) + } + if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) { return url; + } if (url.charAt(0) == '/') { // absolute path var i = baseUrl.indexOf('://'); @@ -309,8 +312,9 @@ PDFJS.isValidUrl = isValidUrl; // In a well-formed PDF, |cond| holds. If it doesn't, subsequent // behavior is undefined. function assertWellFormed(cond, msg) { - if (!cond) + if (!cond) { error(msg); + } } function shadow(obj, prop, value) { @@ -427,8 +431,9 @@ function bytesToString(bytes) { function stringToBytes(str) { var length = str.length; var bytes = new Uint8Array(length); - for (var n = 0; n < length; ++n) + for (var n = 0; n < length; ++n) { bytes[n] = str.charCodeAt(n) & 0xFF; + } return bytes; } @@ -832,14 +837,15 @@ function isRef(v) { function isPDFFunction(v) { var fnDict; - if (typeof v != 'object') + if (typeof v != 'object') { return false; - else if (isDict(v)) + } else if (isDict(v)) { fnDict = v; - else if (isStream(v)) + } else if (isStream(v)) { fnDict = v.dict; - else + } else { return false; + } return fnDict.has('FunctionType'); } @@ -908,8 +914,9 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { var StatTimer = (function StatTimerClosure() { function rpad(str, pad, length) { - while (str.length < length) + while (str.length < length) { str += pad; + } return str; } function StatTimer() { @@ -919,17 +926,21 @@ var StatTimer = (function StatTimerClosure() { } StatTimer.prototype = { time: function StatTimer_time(name) { - if (!this.enabled) + if (!this.enabled) { return; - if (name in this.started) + } + if (name in this.started) { warn('Timer is already running for ' + name); + } this.started[name] = Date.now(); }, timeEnd: function StatTimer_timeEnd(name) { - if (!this.enabled) + if (!this.enabled) { return; - if (!(name in this.started)) + } + if (!(name in this.started)) { warn('Timer has not been started for ' + name); + } this.times.push({ 'name': name, 'start': this.started[name], @@ -945,8 +956,9 @@ var StatTimer = (function StatTimerClosure() { var longest = 0; for (var i = 0, ii = times.length; i < ii; ++i) { var name = times[i]['name']; - if (name.length > longest) + if (name.length > longest) { longest = name.length; + } } for (var i = 0, ii = times.length; i < ii; ++i) { var span = times[i]; @@ -960,8 +972,9 @@ var StatTimer = (function StatTimerClosure() { })(); PDFJS.createBlob = function createBlob(data, contentType) { - if (typeof Blob !== 'undefined') + if (typeof Blob !== 'undefined') { return new Blob([data], { type: contentType }); + } // Blob builder is deprecated in FF14 and removed in FF18. var bb = new MozBlobBuilder(); bb.append(data); @@ -1238,9 +1251,9 @@ var ColorSpace = (function ColorSpaceClosure() { ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { var IR = ColorSpace.parseToIR(cs, xref, res); - if (IR instanceof AlternateCS) + if (IR instanceof AlternateCS) { return IR; - + } return ColorSpace.fromIR(IR); }; @@ -1261,8 +1274,9 @@ var ColorSpace = (function ColorSpaceClosure() { return new CalGrayCS(whitePoint, blackPoint, gamma); case 'PatternCS': var basePatternCS = IR[1]; - if (basePatternCS) + if (basePatternCS) { basePatternCS = ColorSpace.fromIR(basePatternCS); + } return new PatternCS(basePatternCS); case 'IndexedCS': var baseIndexedCS = IR[1]; @@ -1292,8 +1306,9 @@ var ColorSpace = (function ColorSpaceClosure() { var colorSpaces = res.get('ColorSpace'); if (isDict(colorSpaces)) { var refcs = colorSpaces.get(cs.name); - if (refcs) + if (refcs) { cs = refcs; + } } } @@ -1342,17 +1357,19 @@ var ColorSpace = (function ColorSpaceClosure() { var stream = xref.fetchIfRef(cs[1]); var dict = stream.dict; var numComps = dict.get('N'); - if (numComps == 1) + if (numComps == 1) { return 'DeviceGrayCS'; - if (numComps == 3) + } else if (numComps == 3) { return 'DeviceRgbCS'; - if (numComps == 4) + } else if (numComps == 4) { return 'DeviceCmykCS'; + } break; case 'Pattern': var basePatternCS = cs[1]; - if (basePatternCS) + if (basePatternCS) { basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); + } return ['PatternCS', basePatternCS]; case 'Indexed': case 'I': @@ -1367,10 +1384,11 @@ var ColorSpace = (function ColorSpaceClosure() { case 'DeviceN': var name = cs[1]; var numComps = 1; - if (isName(name)) + if (isName(name)) { numComps = 1; - else if (isArray(name)) + } else if (isArray(name)) { numComps = name.length; + } var alt = ColorSpace.parseToIR(cs[2], xref, res); var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); return ['AlternateCS', numComps, alt, tintFnIR]; @@ -1395,16 +1413,18 @@ var ColorSpace = (function ColorSpaceClosure() { * @param {Number} n Number of components the color space has. */ ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { - if (!decode) + if (!decode) { return true; + } if (n * 2 !== decode.length) { warn('The decode map is not the correct length'); return true; } for (var i = 0, ii = decode.length; i < ii; i += 2) { - if (decode[i] !== 0 || decode[i + 1] != 1) + if (decode[i] !== 0 || decode[i + 1] != 1) { return false; + } } return true; }; @@ -1531,8 +1551,9 @@ var IndexedCS = (function IndexedCSClosure() { lookupArray.set(bytes); } else if (isString(lookup)) { lookupArray = new Uint8Array(length); - for (var i = 0; i < length; ++i) + for (var i = 0; i < length; ++i) { lookupArray[i] = lookup.charCodeAt(i); + } } else if (lookup instanceof Uint8Array || lookup instanceof Array) { lookupArray = lookup; } else { @@ -1865,8 +1886,9 @@ var LabCS = (function LabCSClosure() { this.numComps = 3; this.defaultColor = new Float32Array([0, 0, 0]); - if (!whitePoint) + if (!whitePoint) { error('WhitePoint missing - required for color space Lab'); + } blackPoint = blackPoint || [0, 0, 0]; range = range || [-100, 100, -100, 100]; @@ -1886,8 +1908,9 @@ var LabCS = (function LabCSClosure() { this.ZB = blackPoint[2]; // Validate vars as per spec - if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { error('Invalid WhitePoint components, no fallback available'); + } if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { info('Invalid BlackPoint, falling back to default'); @@ -1905,10 +1928,11 @@ var LabCS = (function LabCSClosure() { // Function g(x) from spec function fn_g(x) { - if (x >= 6 / 29) + if (x >= 6 / 29) { return x * x * x; - else + } else { return (108 / 841) * (x - 4 / 29); + } } function decode(value, high1, low2, high2) { @@ -2006,8 +2030,9 @@ var PDFFunction = (function PDFFunctionClosure() { getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, str) { var length = 1; - for (var i = 0, ii = size.length; i < ii; i++) + for (var i = 0, ii = size.length; i < ii; i++) { length *= size[i]; + } length *= outputSize; var array = []; @@ -2033,8 +2058,9 @@ var PDFFunction = (function PDFFunctionClosure() { getIR: function PDFFunction_getIR(xref, fn) { var dict = fn.dict; - if (!dict) + if (!dict) { dict = fn; + } var types = [this.constructSampled, null, @@ -2044,8 +2070,9 @@ var PDFFunction = (function PDFFunctionClosure() { var typeNum = dict.get('FunctionType'); var typeFn = types[typeNum]; - if (!typeFn) + if (!typeFn) { error('Unknown type of function'); + } return typeFn.call(this, fn, dict, xref); }, @@ -2085,8 +2112,9 @@ var PDFFunction = (function PDFFunctionClosure() { var domain = dict.get('Domain'); var range = dict.get('Range'); - if (!domain || !range) + if (!domain || !range) { error('No domain or range'); + } var inputSize = domain.length / 2; var outputSize = range.length / 2; @@ -2114,10 +2142,11 @@ var PDFFunction = (function PDFFunctionClosure() { encode = toMultiArray(encode); var decode = dict.get('Decode'); - if (!decode) + if (!decode) { decode = range; - else + } else { decode = toMultiArray(decode); + } var samples = this.getSampleArray(size, outputSize, bps, str); @@ -2145,9 +2174,10 @@ var PDFFunction = (function PDFFunctionClosure() { var mask = IR[8]; var range = IR[9]; - if (m != args.length) + if (m != args.length) { error('Incorrect number of arguments: ' + m + ' != ' + args.length); + } var x = args; @@ -2156,8 +2186,9 @@ var PDFFunction = (function PDFFunctionClosure() { var cubeVertices = 1 << m; var cubeN = new Float64Array(cubeVertices); var cubeVertex = new Uint32Array(cubeVertices); - for (var j = 0; j < cubeVertices; j++) + for (var j = 0; j < cubeVertices; j++) { cubeN[j] = 1; + } var k = n, pos = 1; // Map x_i to y_j for 0 <= i < m using the sampled function. @@ -2200,8 +2231,9 @@ var PDFFunction = (function PDFFunctionClosure() { for (var j = 0; j < n; ++j) { // Sum all cube vertices' samples portions var rj = 0; - for (var i = 0; i < cubeVertices; i++) + for (var i = 0; i < cubeVertices; i++) { rj += samples[cubeVertex[i] + j] * cubeN[i]; + } // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, // Decode_2j, Decode_2j+1) @@ -2221,13 +2253,15 @@ var PDFFunction = (function PDFFunctionClosure() { var c1 = dict.get('C1') || [1]; var n = dict.get('N'); - if (!isArray(c0) || !isArray(c1)) + if (!isArray(c0) || !isArray(c1)) { error('Illegal dictionary for interpolated function'); + } var length = c0.length; var diff = []; - for (var i = 0; i < length; ++i) + for (var i = 0; i < length; ++i) { diff.push(c1[i] - c0[i]); + } return [CONSTRUCT_INTERPOLATED, c0, diff, n]; }, @@ -2244,8 +2278,9 @@ var PDFFunction = (function PDFFunctionClosure() { var x = n == 1 ? args[0] : Math.pow(args[0], n); var out = []; - for (var j = 0; j < length; ++j) + for (var j = 0; j < length; ++j) { out.push(c0[j] + (x * diff[j])); + } return out; @@ -2255,17 +2290,20 @@ var PDFFunction = (function PDFFunctionClosure() { constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { var domain = dict.get('Domain'); - if (!domain) + if (!domain) { error('No domain'); + } var inputSize = domain.length / 2; - if (inputSize != 1) + if (inputSize != 1) { error('Bad domain for stiched function'); + } var fnRefs = dict.get('Functions'); var fns = []; - for (var i = 0, ii = fnRefs.length; i < ii; ++i) + for (var i = 0, ii = fnRefs.length; i < ii; ++i) { fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); + } var bounds = dict.get('Bounds'); var encode = dict.get('Encode'); @@ -2286,10 +2324,11 @@ var PDFFunction = (function PDFFunctionClosure() { return function constructStichedFromIRResult(args) { var clip = function constructStichedFromIRClip(v, min, max) { - if (v > max) + if (v > max) { v = max; - else if (v < min) + } else if (v < min) { v = min; + } return v; }; @@ -2297,24 +2336,27 @@ var PDFFunction = (function PDFFunctionClosure() { var v = clip(args[0], domain[0], domain[1]); // calulate which bound the value is in for (var i = 0, ii = bounds.length; i < ii; ++i) { - if (v < bounds[i]) + if (v < bounds[i]) { break; + } } // encode value into domain of function var dmin = domain[0]; - if (i > 0) + if (i > 0) { dmin = bounds[i - 1]; + } var dmax = domain[1]; - if (i < bounds.length) + if (i < bounds.length) { dmax = bounds[i]; + } var rmin = encode[2 * i]; var rmax = encode[2 * i + 1]; var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); - // call the appropropriate function + // call the appropriate function return fns[i]([v2]); }; }, @@ -2324,11 +2366,13 @@ var PDFFunction = (function PDFFunctionClosure() { var domain = dict.get('Domain'); var range = dict.get('Range'); - if (!domain) + if (!domain) { error('No domain.'); + } - if (!range) + if (!range) { error('No range.'); + } var lexer = new PostScriptLexer(fn); var parser = new PostScriptParser(lexer); @@ -2354,18 +2398,20 @@ var PDFFunction = (function PDFFunctionClosure() { } var key = initialStack.join('_'); - if (cache.has(key)) + if (cache.has(key)) { return cache.get(key); + } var stack = evaluator.execute(initialStack); var transformed = []; for (i = numOutputs - 1; i >= 0; --i) { var out = stack.pop(); var rangeIndex = 2 * i; - if (out < range[rangeIndex]) + if (out < range[rangeIndex]) { out = range[rangeIndex]; - else if (out > range[rangeIndex + 1]) + } else if (out > range[rangeIndex + 1]) { out = range[rangeIndex + 1]; + } transformed[i] = out; } cache.set(key, transformed); @@ -2408,21 +2454,25 @@ var PostScriptStack = (function PostScriptStackClosure() { PostScriptStack.prototype = { push: function PostScriptStack_push(value) { - if (this.stack.length >= MAX_STACK_SIZE) + if (this.stack.length >= MAX_STACK_SIZE) { error('PostScript function stack overflow.'); + } this.stack.push(value); }, pop: function PostScriptStack_pop() { - if (this.stack.length <= 0) + if (this.stack.length <= 0) { error('PostScript function stack underflow.'); + } return this.stack.pop(); }, copy: function PostScriptStack_copy(n) { - if (this.stack.length + n >= MAX_STACK_SIZE) + if (this.stack.length + n >= MAX_STACK_SIZE) { error('PostScript function stack overflow.'); + } var stack = this.stack; - for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) + for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) { stack.push(stack[i]); + } }, index: function PostScriptStack_index(n) { this.push(this.stack[this.stack.length - n - 1]); @@ -2468,8 +2518,9 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'jz': // jump if false b = stack.pop(); a = stack.pop(); - if (!a) + if (!a) { counter = b; + } break; case 'j': // jump a = stack.pop(); @@ -2489,10 +2540,11 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'and': b = stack.pop(); a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a && b); - else + } else { stack.push(a & b); + } break; case 'atan': a = stack.pop(); @@ -2501,10 +2553,11 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'bitshift': b = stack.pop(); a = stack.pop(); - if (a > 0) + if (a > 0) { stack.push(a << b); - else + } else { stack.push(a >> b); + } break; case 'ceiling': a = stack.pop(); @@ -2611,18 +2664,20 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { break; case 'not': a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a && b); - else + } else { stack.push(a & b); + } break; case 'or': b = stack.pop(); a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a || b); - else + } else { stack.push(a | b); + } break; case 'pop': stack.pop(); @@ -2660,10 +2715,11 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { case 'xor': b = stack.pop(); a = stack.pop(); - if (isBool(a) && isBool(b)) + if (isBool(a) && isBool(b)) { stack.push(a != b); - else + } else { stack.push(a ^ b); + } break; default: error('Unknown operator ' + operator); @@ -2677,6 +2733,9 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { })(); +var HIGHLIGHT_OFFSET = 4; // px +var SUPPORTED_TYPES = ['Link', 'Text', 'Widget']; + var Annotation = (function AnnotationClosure() { // 12.5.5: Algorithm: Appearance streams function getTransformMatrix(rect, bbox, matrix) { @@ -2799,25 +2858,50 @@ var Annotation = (function AnnotationClosure() { }, // TODO(mack): Remove this, it's not really that helpful. - getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect) { + getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect, + borderWidth) { assert(!isWorker, 'getEmptyContainer() should be called from main thread'); + var bWidth = borderWidth || 0; + rect = rect || this.data.rect; var element = document.createElement(tagName); - element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; - element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; + element.style.borderWidth = bWidth + 'px'; + var width = rect[2] - rect[0] - 2 * bWidth; + var height = rect[3] - rect[1] - 2 * bWidth; + element.style.width = width + 'px'; + element.style.height = height + 'px'; return element; }, + isInvisible: function Annotation_isInvisible() { + var data = this.data; + if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) { + return false; + } else { + return !!(data && + data.annotationFlags && // Default: not invisible + data.annotationFlags & 0x1); // Invisible + } + }, + isViewable: function Annotation_isViewable() { var data = this.data; - return !!( - data && - (!data.annotationFlags || - !(data.annotationFlags & 0x22)) && // Hidden or NoView - data.rect // rectangle is nessessary - ); + return !!(!this.isInvisible() && + data && + (!data.annotationFlags || + !(data.annotationFlags & 0x22)) && // Hidden or NoView + data.rect); // rectangle is nessessary + }, + + isPrintable: function Annotation_isPrintable() { + var data = this.data; + return !!(!this.isInvisible() && + data && + data.annotationFlags && // Default: not printable + data.annotationFlags & 0x4 && // Print + data.rect); // rectangle is nessessary }, loadResources: function(keys) { @@ -2838,7 +2922,7 @@ var Annotation = (function AnnotationClosure() { return promise; }, - getOperatorList: function Annotation_getToOperatorList(evaluator) { + getOperatorList: function Annotation_getOperatorList(evaluator) { var promise = new LegacyPromise(); @@ -2945,7 +3029,7 @@ var Annotation = (function AnnotationClosure() { var annotation = new Constructor(params); - if (annotation.isViewable()) { + if (annotation.isViewable() || annotation.isPrintable()) { return annotation; } else { warn('unimplemented annotation type: ' + subtype); @@ -2953,7 +3037,7 @@ var Annotation = (function AnnotationClosure() { }; Annotation.appendToOperatorList = function Annotation_appendToOperatorList( - annotations, opList, pdfManager, partialEvaluator) { + annotations, opList, pdfManager, partialEvaluator, intent) { function reject(e) { annotationsReadyPromise.reject(e); @@ -2963,7 +3047,11 @@ var Annotation = (function AnnotationClosure() { var annotationPromises = []; for (var i = 0, n = annotations.length; i < n; ++i) { - annotationPromises.push(annotations[i].getOperatorList(partialEvaluator)); + if (intent === 'display' && annotations[i].isViewable() || + intent === 'print' && annotations[i].isPrintable()) { + annotationPromises.push( + annotations[i].getOperatorList(partialEvaluator)); + } } Promise.all(annotationPromises).then(function(datas) { opList.addOp(OPS.beginAnnotations, []); @@ -3025,8 +3113,9 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { var j, jj; for (j = 0, jj = kids.length; j < jj; j++) { var kidRef = kids[j]; - if (kidRef.num == ref.num && kidRef.gen == ref.gen) + if (kidRef.num == ref.num && kidRef.gen == ref.gen) { break; + } } fieldName.unshift('`' + j); } @@ -3175,9 +3264,64 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { return TextWidgetAnnotation; })(); +var InteractiveAnnotation = (function InteractiveAnnotationClosure() { + function InteractiveAnnotation(params) { + Annotation.call(this, params); + } + + Util.inherit(InteractiveAnnotation, Annotation, { + hasHtml: function InteractiveAnnotation_hasHtml() { + return true; + }, + + highlight: function InteractiveAnnotation_highlight() { + if (this.highlightElement && + this.highlightElement.hasAttribute('hidden')) { + this.highlightElement.removeAttribute('hidden'); + } + }, + + unhighlight: function InteractiveAnnotation_unhighlight() { + if (this.highlightElement && + !this.highlightElement.hasAttribute('hidden')) { + this.highlightElement.setAttribute('hidden', true); + } + }, + + initContainer: function InteractiveAnnotation_initContainer() { + + var item = this.data; + var rect = item.rect; + + var container = this.getEmptyContainer('section', rect, item.borderWidth); + container.style.backgroundColor = item.color; + + var color = item.color; + var rgb = []; + for (var i = 0; i < 3; ++i) { + rgb[i] = Math.round(color[i] * 255); + } + item.colorCssRgb = Util.makeCssRgb(rgb); + + var highlight = document.createElement('div'); + highlight.className = 'annotationHighlight'; + highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px'; + highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px'; + highlight.setAttribute('hidden', true); + + this.highlightElement = highlight; + container.appendChild(this.highlightElement); + + return container; + } + }); + + return InteractiveAnnotation; +})(); + var TextAnnotation = (function TextAnnotationClosure() { function TextAnnotation(params) { - Annotation.call(this, params); + InteractiveAnnotation.call(this, params); if (params.data) { return; @@ -3190,22 +3334,21 @@ var TextAnnotation = (function TextAnnotationClosure() { var title = dict.get('T'); data.content = stringToPDFString(content || ''); data.title = stringToPDFString(title || ''); - data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name; + + if (data.hasAppearance) { + data.name = 'NoIcon'; + } else { + data.name = dict.has('Name') ? dict.get('Name').name : 'Note'; + } + + if (dict.has('C')) { + data.hasBgColor = true; + } } var ANNOT_MIN_SIZE = 10; - Util.inherit(TextAnnotation, Annotation, { - - getOperatorList: function TextAnnotation_getOperatorList(evaluator) { - var promise = new LegacyPromise(); - promise.resolve(new OperatorList()); - return promise; - }, - - hasHtml: function TextAnnotation_hasHtml() { - return true; - }, + Util.inherit(TextAnnotation, InteractiveAnnotation, { getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) { assert(!isWorker, 'getHtmlElement() shall be called from main thread'); @@ -3221,23 +3364,40 @@ var TextAnnotation = (function TextAnnotationClosure() { rect[2] = rect[0] + (rect[3] - rect[1]); // make it square } - var container = this.getEmptyContainer('section', rect); + var container = this.initContainer(); container.className = 'annotText'; - var image = document.createElement('img'); + var image = document.createElement('img'); image.style.height = container.style.height; + image.style.width = container.style.width; var iconName = item.name; image.src = PDFJS.imageResourcesPath + 'annotation-' + iconName.toLowerCase() + '.svg'; image.alt = '[{{type}} Annotation]'; image.dataset.l10nId = 'text_annotation_type'; image.dataset.l10nArgs = JSON.stringify({type: iconName}); + + var contentWrapper = document.createElement('div'); + contentWrapper.className = 'annotTextContentWrapper'; + contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px'; + contentWrapper.style.top = '-10px'; + var content = document.createElement('div'); + content.className = 'annotTextContent'; content.setAttribute('hidden', true); + if (item.hasBgColor) { + var color = item.color; + var rgb = []; + for (var i = 0; i < 3; ++i) { + // Enlighten the color (70%) + var c = Math.round(color[i] * 255); + rgb[i] = Math.round((255 - c) * 0.7) + c; + } + content.style.backgroundColor = Util.makeCssRgb(rgb); + } + var title = document.createElement('h1'); var text = document.createElement('p'); - content.style.left = Math.floor(rect[2] - rect[0]) + 'px'; - content.style.top = '0px'; title.textContent = item.title; if (!item.content && !item.title) { @@ -3248,33 +3408,63 @@ var TextAnnotation = (function TextAnnotationClosure() { for (var i = 0, ii = lines.length; i < ii; ++i) { var line = lines[i]; e.appendChild(document.createTextNode(line)); - if (i < (ii - 1)) + if (i < (ii - 1)) { e.appendChild(document.createElement('br')); + } } text.appendChild(e); - var showAnnotation = function showAnnotation() { - container.style.zIndex += 1; - content.removeAttribute('hidden'); + var pinned = false; + + var showAnnotation = function showAnnotation(pin) { + if (pin) { + pinned = true; + } + if (content.hasAttribute('hidden')) { + container.style.zIndex += 1; + content.removeAttribute('hidden'); + } }; - var hideAnnotation = function hideAnnotation(e) { - if (e.toElement || e.relatedTarget) { // No context menu is used + var hideAnnotation = function hideAnnotation(unpin) { + if (unpin) { + pinned = false; + } + if (!content.hasAttribute('hidden') && !pinned) { container.style.zIndex -= 1; content.setAttribute('hidden', true); } }; - content.addEventListener('mouseover', showAnnotation, false); - content.addEventListener('mouseout', hideAnnotation, false); - image.addEventListener('mouseover', showAnnotation, false); - image.addEventListener('mouseout', hideAnnotation, false); + var toggleAnnotation = function toggleAnnotation() { + if (pinned) { + hideAnnotation(true); + } else { + showAnnotation(true); + } + }; + + var self = this; + image.addEventListener('click', function image_clickHandler() { + toggleAnnotation(); + }, false); + image.addEventListener('mouseover', function image_mouseOverHandler() { + showAnnotation(); + }, false); + image.addEventListener('mouseout', function image_mouseOutHandler() { + hideAnnotation(); + }, false); + + content.addEventListener('click', function content_clickHandler() { + hideAnnotation(true); + }, false); } content.appendChild(title); content.appendChild(text); + contentWrapper.appendChild(content); container.appendChild(image); - container.appendChild(content); + container.appendChild(contentWrapper); return container; } @@ -3285,7 +3475,7 @@ var TextAnnotation = (function TextAnnotationClosure() { var LinkAnnotation = (function LinkAnnotationClosure() { function LinkAnnotation(params) { - Annotation.call(this, params); + InteractiveAnnotation.call(this, params); if (params.data) { return; @@ -3348,36 +3538,28 @@ var LinkAnnotation = (function LinkAnnotationClosure() { return url; } - Util.inherit(LinkAnnotation, Annotation, { + Util.inherit(LinkAnnotation, InteractiveAnnotation, { hasOperatorList: function LinkAnnotation_hasOperatorList() { return false; }, - hasHtml: function LinkAnnotation_hasHtml() { - return true; - }, - getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) { - var rect = this.data.rect; - var element = document.createElement('a'); - var borderWidth = this.data.borderWidth; - element.style.borderWidth = borderWidth + 'px'; - var color = this.data.color; - var rgb = []; - for (var i = 0; i < 3; ++i) { - rgb[i] = Math.round(color[i] * 255); - } - element.style.borderColor = Util.makeCssRgb(rgb); - element.style.borderStyle = 'solid'; + var container = this.initContainer(); + container.className = 'annotLink'; - var width = rect[2] - rect[0] - 2 * borderWidth; - var height = rect[3] - rect[1] - 2 * borderWidth; - element.style.width = width + 'px'; - element.style.height = height + 'px'; + var item = this.data; + var rect = item.rect; - element.href = this.data.url || ''; - return element; + container.style.borderColor = item.colorCssRgb; + container.style.borderStyle = 'solid'; + + var link = document.createElement('a'); + link.href = link.title = this.data.url || ''; + + container.appendChild(link); + + return container; } }); @@ -3553,6 +3735,8 @@ var ChunkedStream = (function ChunkedStreamClosure() { }, makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) { + this.ensureRange(start, start + length); + function ChunkedStreamSubstream() {} ChunkedStreamSubstream.prototype = Object.create(this); ChunkedStreamSubstream.prototype.getMissingChunks = function() { @@ -4179,7 +4363,7 @@ var Page = (function PageClosure() { }.bind(this)); return promise; }, - getOperatorList: function Page_getOperatorList(handler) { + getOperatorList: function Page_getOperatorList(handler, intent) { var self = this; var promise = new LegacyPromise(); @@ -4214,11 +4398,12 @@ var Page = (function PageClosure() { var contentStream = data[0]; - var opList = new OperatorList(handler, self.pageIndex); + var opList = new OperatorList(intent, handler, self.pageIndex); handler.send('StartRenderPage', { transparency: partialEvaluator.hasBlendModes(self.resources), - pageIndex: self.pageIndex + pageIndex: self.pageIndex, + intent: intent }); partialEvaluator.getOperatorList(contentStream, self.resources, opList); pageListPromise.resolve(opList); @@ -4236,7 +4421,7 @@ var Page = (function PageClosure() { } var annotationsReadyPromise = Annotation.appendToOperatorList( - annotations, pageOpList, pdfManager, partialEvaluator); + annotations, pageOpList, pdfManager, partialEvaluator, intent); annotationsReadyPromise.then(function () { pageOpList.flush(true); promise.resolve(pageOpList); @@ -12963,8 +13148,9 @@ var ARCFourCipher = (function ARCFourCipherClosure() { this.b = 0; var s = new Uint8Array(256); var i, j = 0, tmp, keyLength = key.length; - for (i = 0; i < 256; ++i) + for (i = 0; i < 256; ++i) { s[i] = i; + } for (i = 0; i < 256; ++i) { tmp = s[i]; j = (j + tmp + key[i % keyLength]) & 0xFF; @@ -13024,12 +13210,14 @@ var calculateMD5 = (function calculateMD5Closure() { var paddedLength = (length + 72) & ~63; // data + 9 extra bytes var padded = new Uint8Array(paddedLength); var i, j, n; - for (i = 0; i < length; ++i) + for (i = 0; i < length; ++i) { padded[i] = data[offset++]; + } padded[i++] = 0x80; n = paddedLength - 8; - while (i < n) + while (i < n) { padded[i++] = 0; + } padded[i++] = (length << 3) & 0xFF; padded[i++] = (length >> 5) & 0xFF; padded[i++] = (length >> 13) & 0xFF; @@ -13073,18 +13261,17 @@ var calculateMD5 = (function calculateMD5Closure() { h3 = (h3 + d) | 0; } return new Uint8Array([ - h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF, - h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF, - h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF, - h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF + h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF, + h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF, + h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF, + h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF ]); } return hash; })(); var NullCipher = (function NullCipherClosure() { - function NullCipher() { - } + function NullCipher() {} NullCipher.prototype = { decryptBlock: function NullCipher_decryptBlock(data) { @@ -13240,8 +13427,9 @@ var AES128Cipher = (function AES128CipherClosure() { var i, j, k; var t, u, v; // AddRoundKey - for (j = 0, k = 160; j < 16; ++j, ++k) + for (j = 0, k = 160; j < 16; ++j, ++k) { state[j] ^= key[k]; + } for (i = 9; i >= 1; --i) { // InvShiftRows t = state[13]; state[13] = state[9]; state[9] = state[5]; @@ -13251,11 +13439,13 @@ var AES128Cipher = (function AES128CipherClosure() { t = state[15]; u = state[11]; v = state[7]; state[15] = state[3]; state[11] = t; state[7] = u; state[3] = v; // InvSubBytes - for (j = 0; j < 16; ++j) + for (j = 0; j < 16; ++j) { state[j] = inv_s[state[j]]; + } // AddRoundKey - for (j = 0, k = i * 16; j < 16; ++j, ++k) + for (j = 0, k = i * 16; j < 16; ++j, ++k) { state[j] ^= key[k]; + } // InvMixColumns for (j = 0; j < 16; j += 4) { var s0 = mix[state[j]], s1 = mix[state[j + 1]], @@ -13297,13 +13487,15 @@ var AES128Cipher = (function AES128CipherClosure() { for (i = 0; i < sourceLength; ++i) { buffer[bufferLength] = data[i]; ++bufferLength; - if (bufferLength < 16) + if (bufferLength < 16) { continue; + } // buffer is full, decrypting var plain = decrypt128(buffer, this.key); // xor-ing the IV vector to get plain text - for (j = 0; j < 16; ++j) + for (j = 0; j < 16; ++j) { plain[j] ^= iv[j]; + } iv = buffer; result.push(plain); buffer = new Uint8Array(16); @@ -13325,8 +13517,9 @@ var AES128Cipher = (function AES128CipherClosure() { result[result.length - 1] = lastBlock.subarray(0, 16 - lastBlock[15]); } var output = new Uint8Array(outputLength); - for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) + for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) { output.set(result[i], j); + } return output; } @@ -13335,8 +13528,9 @@ var AES128Cipher = (function AES128CipherClosure() { var i, sourceLength = data.length; var buffer = this.buffer, bufferLength = this.bufferPosition; // waiting for IV values -- they are at the start of the stream - for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) + for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) { buffer[bufferLength] = data[i]; + } if (bufferLength < 16) { // need more data this.bufferLength = bufferLength; @@ -13391,22 +13585,25 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var hashData = new Uint8Array(hashDataSize), i = 0, j, n; if (password) { n = Math.min(32, password.length); - for (; i < n; ++i) + for (; i < n; ++i) { hashData[i] = password[i]; + } } j = 0; while (i < 32) { hashData[i++] = defaultPasswordBytes[j++]; } // as now the padded password in the hashData[0..i] - for (j = 0, n = ownerPassword.length; j < n; ++j) + for (j = 0, n = ownerPassword.length; j < n; ++j) { hashData[i++] = ownerPassword[j]; + } hashData[i++] = flags & 0xFF; hashData[i++] = (flags >> 8) & 0xFF; hashData[i++] = (flags >> 16) & 0xFF; hashData[i++] = (flags >>> 24) & 0xFF; - for (j = 0, n = fileId.length; j < n; ++j) + for (j = 0, n = fileId.length; j < n; ++j) { hashData[i++] = fileId[j]; + } if (revision >= 4 && !encryptMetadata) { hashData[i++] = 0xFF; hashData[i++] = 0xFF; @@ -13417,46 +13614,53 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var keyLengthInBytes = keyLength >> 3; if (revision >= 3) { for (j = 0; j < 50; ++j) { - hash = calculateMD5(hash, 0, keyLengthInBytes); + hash = calculateMD5(hash, 0, keyLengthInBytes); } } var encryptionKey = hash.subarray(0, keyLengthInBytes); var cipher, checkData; if (revision >= 3) { - for (i = 0; i < 32; ++i) + for (i = 0; i < 32; ++i) { hashData[i] = defaultPasswordBytes[i]; - for (j = 0, n = fileId.length; j < n; ++j) + } + for (j = 0, n = fileId.length; j < n; ++j) { hashData[i++] = fileId[j]; + } cipher = new ARCFourCipher(encryptionKey); var checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i)); n = encryptionKey.length; var derivedKey = new Uint8Array(n), k; for (j = 1; j <= 19; ++j) { - for (k = 0; k < n; ++k) + for (k = 0; k < n; ++k) { derivedKey[k] = encryptionKey[k] ^ j; + } cipher = new ARCFourCipher(derivedKey); checkData = cipher.encryptBlock(checkData); } for (j = 0, n = checkData.length; j < n; ++j) { - if (userPassword[j] != checkData[j]) + if (userPassword[j] != checkData[j]) { return null; + } } } else { cipher = new ARCFourCipher(encryptionKey); checkData = cipher.encryptBlock(defaultPasswordBytes); for (j = 0, n = checkData.length; j < n; ++j) { - if (userPassword[j] != checkData[j]) + if (userPassword[j] != checkData[j]) { return null; + } } } return encryptionKey; } + function decodeUserPassword(password, ownerPassword, revision, keyLength) { var hashData = new Uint8Array(32), i = 0, j, n; n = Math.min(32, password.length); - for (; i < n; ++i) + for (; i < n; ++i) { hashData[i] = password[i]; + } j = 0; while (i < 32) { hashData[i++] = defaultPasswordBytes[j++]; @@ -13465,7 +13669,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var keyLengthInBytes = keyLength >> 3; if (revision >= 3) { for (j = 0; j < 50; ++j) { - hash = calculateMD5(hash, 0, hash.length); + hash = calculateMD5(hash, 0, hash.length); } } @@ -13474,8 +13678,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { userPassword = ownerPassword; var derivedKey = new Uint8Array(keyLengthInBytes), k; for (j = 19; j >= 0; j--) { - for (k = 0; k < keyLengthInBytes; ++k) + for (k = 0; k < keyLengthInBytes; ++k) { derivedKey[k] = hash[k] ^ j; + } cipher = new ARCFourCipher(derivedKey); userPassword = cipher.encryptBlock(userPassword); } @@ -13490,31 +13695,36 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { function CipherTransformFactory(dict, fileId, password) { var filter = dict.get('Filter'); - if (!isName(filter) || filter.name != 'Standard') + if (!isName(filter) || filter.name != 'Standard') { error('unknown encryption method'); + } this.dict = dict; var algorithm = dict.get('V'); if (!isInt(algorithm) || - (algorithm != 1 && algorithm != 2 && algorithm != 4)) + (algorithm != 1 && algorithm != 2 && algorithm != 4)) { error('unsupported encryption algorithm'); + } this.algorithm = algorithm; var keyLength = dict.get('Length') || 40; if (!isInt(keyLength) || - keyLength < 40 || (keyLength % 8) !== 0) + keyLength < 40 || (keyLength % 8) !== 0) { error('invalid key length'); + } + // prepare keys var ownerPassword = stringToBytes(dict.get('O')).subarray(0, 32); var userPassword = stringToBytes(dict.get('U')).subarray(0, 32); var flags = dict.get('P'); var revision = dict.get('R'); - var encryptMetadata = algorithm == 4 && // meaningful when V is 4 - dict.get('EncryptMetadata') !== false; // makes true as default value + var encryptMetadata = (algorithm == 4 && // meaningful when V is 4 + dict.get('EncryptMetadata') !== false); // makes true as default value this.encryptMetadata = encryptMetadata; var fileIdBytes = stringToBytes(fileId); var passwordBytes; - if (password) + if (password) { passwordBytes = stringToBytes(password); + } var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, @@ -13531,9 +13741,10 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { revision, keyLength, encryptMetadata); } - if (!encryptionKey) + if (!encryptionKey) { throw new PasswordException('Incorrect Password', PasswordResponses.INCORRECT_PASSWORD); + } this.encryptionKey = encryptionKey; @@ -13547,8 +13758,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { function buildObjectKey(num, gen, encryptionKey, isAes) { var key = new Uint8Array(encryptionKey.length + 9), i, n; - for (i = 0, n = encryptionKey.length; i < n; ++i) + for (i = 0, n = encryptionKey.length; i < n; ++i) { key[i] = encryptionKey[i]; + } key[i++] = num & 0xFF; key[i++] = (num >> 8) & 0xFF; key[i++] = (num >> 16) & 0xFF; @@ -13567,8 +13779,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { function buildCipherConstructor(cf, name, num, gen, key) { var cryptFilter = cf.get(name.name); var cfm; - if (cryptFilter !== null && cryptFilter !== undefined) + if (cryptFilter !== null && cryptFilter !== undefined) { cfm = cryptFilter.get('CFM'); + } if (!cfm || cfm.name == 'None') { return function cipherTransformFactoryBuildCipherConstructorNone() { return new NullCipher(); @@ -13576,14 +13789,12 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { } if ('V2' == cfm.name) { return function cipherTransformFactoryBuildCipherConstructorV2() { - return new ARCFourCipher( - buildObjectKey(num, gen, key, false)); + return new ARCFourCipher(buildObjectKey(num, gen, key, false)); }; } if ('AESV2' == cfm.name) { return function cipherTransformFactoryBuildCipherConstructorAESV2() { - return new AES128Cipher( - buildObjectKey(num, gen, key, true)); + return new AES128Cipher(buildObjectKey(num, gen, key, true)); }; } error('Unknown crypto method'); @@ -13595,9 +13806,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { if (this.algorithm == 4) { return new CipherTransform( buildCipherConstructor(this.cf, this.stmf, - num, gen, this.encryptionKey), + num, gen, this.encryptionKey), buildCipherConstructor(this.cf, this.strf, - num, gen, this.encryptionKey)); + num, gen, this.encryptionKey)); } // algorithms 1 and 2 var key = buildObjectKey(num, gen, this.encryptionKey, false); @@ -14535,10 +14746,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var bitStrideLength = (width + 7) >> 3; var imgArray = image.getBytes(bitStrideLength * height); var decode = dict.get('Decode', 'D'); + var canTransfer = image instanceof DecodeStream; var inverseDecode = !!decode && decode[0] > 0; operatorList.addOp(OPS.paintImageMaskXObject, - [PDFImage.createMask(imgArray, width, height, inverseDecode)] + [PDFImage.createMask(imgArray, width, height, canTransfer, + inverseDecode)] ); return; } @@ -15765,7 +15978,7 @@ var OperatorList = (function OperatorListClosure() { } - function OperatorList(messageHandler, pageIndex) { + function OperatorList(intent, messageHandler, pageIndex) { this.messageHandler = messageHandler; // When there isn't a message handler the fn array needs to be able to grow // since we can't flush the operators. @@ -15778,6 +15991,7 @@ var OperatorList = (function OperatorListClosure() { this.dependencies = {}; this.pageIndex = pageIndex; this.fnIndex = 0; + this.intent = intent; } OperatorList.prototype = { @@ -15838,7 +16052,8 @@ var OperatorList = (function OperatorListClosure() { lastChunk: lastChunk, length: this.length }, - pageIndex: this.pageIndex + pageIndex: this.pageIndex, + intent: this.intent }, null, transfers); this.dependencies = []; this.fnIndex = 0; @@ -19787,9 +20002,11 @@ var Font = (function FontClosure() { } --ifLevel; } else if (op === 0x1C) { // JMPR - var offset = stack[stack.length - 1]; - // only jumping forward to prevent infinite loop - if (offset > 0) { i += offset - 1; } + if (!inFDEF && !inELSE) { + var offset = stack[stack.length - 1]; + // only jumping forward to prevent infinite loop + if (offset > 0) { i += offset - 1; } + } } // Adjusting stack not extactly, but just enough to get function id if (!inFDEF && !inELSE) { @@ -28386,19 +28603,25 @@ var PDFImage = (function PDFImageClosure() { return temp; }; - PDFImage.createMask = function PDFImage_createMask(imgArray, width, height, - inverseDecode) { - // Copy imgArray into a typed array (inverting if necessary) so it can be - // transferred to the main thread. - var length = ((width + 7) >> 3) * height; - var data = new Uint8Array(length); - if (inverseDecode) { - for (var i = 0; i < length; i++) { - data[i] = ~imgArray[i]; - } + PDFImage.createMask = + function PDFImage_createMask(imgArray, width, height, canTransfer, + inverseDecode) { + // If imgArray came from a DecodeStream, we're safe to transfer it. + // Otherwise, copy it. + var actualLength = imgArray.byteLength; + var data; + if (canTransfer) { + data = imgArray; } else { - for (var i = 0; i < length; i++) { - data[i] = imgArray[i]; + data = new Uint8Array(actualLength); + data.set(imgArray); + } + // Invert if necessary. It's safe to modify the array -- whether it's the + // original or a copy, we're about to transfer it anyway, so nothing else + // in this thread can be relying on its contents. + if (inverseDecode) { + for (var i = 0; i < actualLength; i++) { + data[i] = ~data[i]; } } @@ -28407,14 +28630,14 @@ var PDFImage = (function PDFImageClosure() { PDFImage.prototype = { get drawWidth() { - if (!this.smask) - return this.width; - return Math.max(this.width, this.smask.width); + return Math.max(this.width, + this.smask && this.smask.width || 0, + this.mask && this.mask.width || 0); }, get drawHeight() { - if (!this.smask) - return this.height; - return Math.max(this.height, this.smask.height); + return Math.max(this.height, + this.smask && this.smask.height || 0, + this.mask && this.mask.height || 0); }, decodeBuffer: function PDFImage_decodeBuffer(buffer) { var bpc = this.bpc; @@ -31887,7 +32110,7 @@ var Parser = (function ParserClosure() { while (stream.pos < stream.end) { var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE); var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH; - var found = false, i, ii, j; + var found = false, i, j; for (i = 0, j = 0; i < scanLength; i++) { var b = scanBytes[i]; if (b !== ENDSTREAM_SIGNATURE[j]) { @@ -31896,6 +32119,7 @@ var Parser = (function ParserClosure() { } else { j++; if (j >= ENDSTREAM_SIGNATURE_LENGTH) { + i++; found = true; break; } @@ -31971,12 +32195,10 @@ var Parser = (function ParserClosure() { return new LZWStream(stream, earlyChange); } if (name == 'DCTDecode' || name == 'DCT') { - var bytes = stream.getBytes(length); - return new JpegStream(bytes, stream.dict, this.xref); + return new JpegStream(stream, length, stream.dict, this.xref); } if (name == 'JPXDecode' || name == 'JPX') { - var bytes = stream.getBytes(length); - return new JpxStream(bytes, stream.dict); + return new JpxStream(stream, length, stream.dict); } if (name == 'ASCII85Decode' || name == 'A85') { return new Ascii85Stream(stream); @@ -31991,8 +32213,7 @@ var Parser = (function ParserClosure() { return new RunLengthStream(stream); } if (name == 'JBIG2Decode') { - var bytes = stream.getBytes(length); - return new Jbig2Stream(bytes, stream.dict); + return new Jbig2Stream(stream, length, stream.dict); } warn('filter "' + name + '" not supported yet'); return stream; @@ -32507,8 +32728,9 @@ var PostScriptParser = (function PostScriptParserClosure() { return false; }, expect: function PostScriptParser_expect(type) { - if (this.accept(type)) + if (this.accept(type)) { return true; + } error('Unexpected symbol: found ' + this.token.type + ' expected ' + type + '.'); }, @@ -32585,9 +32807,9 @@ var PostScriptToken = (function PostScriptTokenClosure() { PostScriptToken.getOperator = function PostScriptToken_getOperator(op) { var opValue = opCache[op]; - if (opValue) + if (opValue) { return opValue; - + } return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op); }; @@ -32636,8 +32858,8 @@ var PostScriptLexer = (function PostScriptLexerClosure() { case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4' case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9' case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.' - return new PostScriptToken(PostScriptTokenTypes.NUMBER, - this.getNumber()); + return new PostScriptToken(PostScriptTokenTypes.NUMBER, + this.getNumber()); case 0x7B: // '{' this.nextChar(); return PostScriptToken.LBRACE; @@ -32648,7 +32870,7 @@ var PostScriptLexer = (function PostScriptLexerClosure() { // operator var str = String.fromCharCode(ch); while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z' - ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) { + ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) { str += String.fromCharCode(ch); } switch (str.toLowerCase()) { @@ -32665,15 +32887,16 @@ var PostScriptLexer = (function PostScriptLexerClosure() { var str = String.fromCharCode(ch); while ((ch = this.nextChar()) >= 0) { if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9' - ch === 0x2D || ch === 0x2E) { // '-', '.' + ch === 0x2D || ch === 0x2E) { // '-', '.' str += String.fromCharCode(ch); } else { break; } } var value = parseFloat(str); - if (isNaN(value)) + if (isNaN(value)) { error('Invalid floating point number: ' + value); + } return value; } }; @@ -32780,11 +33003,13 @@ var DecodeStream = (function DecodeStreamClosure() { current = 0; } var size = 512; - while (size < requested) - size <<= 1; + while (size < requested) { + size *= 2; + } var buffer2 = new Uint8Array(size); - for (var i = 0; i < current; ++i) + for (var i = 0; i < current; ++i) { buffer2[i] = buffer[i]; + } return (this.buffer = buffer2); }, getByte: function DecodeStream_getByte() { @@ -32988,13 +33213,12 @@ var FlateStream = (function FlateStreamClosure() { 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 ]), 5]; - function FlateStream(stream) { - var bytes = stream.getBytes(); - var bytesPos = 0; + function FlateStream(str) { + this.str = str; + this.dict = str.dict; - this.dict = stream.dict; - var cmf = bytes[bytesPos++]; - var flg = bytes[bytesPos++]; + var cmf = str.getByte(); + var flg = str.getByte(); if (cmf == -1 || flg == -1) error('Invalid header in flate stream: ' + cmf + ', ' + flg); if ((cmf & 0x0f) != 0x08) @@ -33004,9 +33228,6 @@ var FlateStream = (function FlateStreamClosure() { if (flg & 0x20) error('FDICT bit set in flate stream: ' + cmf + ', ' + flg); - this.bytes = bytes; - this.bytesPos = bytesPos; - this.codeSize = 0; this.codeBuf = 0; @@ -33016,37 +33237,37 @@ var FlateStream = (function FlateStreamClosure() { FlateStream.prototype = Object.create(DecodeStream.prototype); FlateStream.prototype.getBits = function FlateStream_getBits(bits) { + var str = this.str; var codeSize = this.codeSize; var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; var b; while (codeSize < bits) { - if (typeof (b = bytes[bytesPos++]) == 'undefined') + if ((b = str.getByte()) === -1) { error('Bad encoding in flate stream'); + } codeBuf |= b << codeSize; codeSize += 8; } b = codeBuf & ((1 << bits) - 1); this.codeBuf = codeBuf >> bits; this.codeSize = codeSize -= bits; - this.bytesPos = bytesPos; + return b; }; FlateStream.prototype.getCode = function FlateStream_getCode(table) { + var str = this.str; var codes = table[0]; var maxLen = table[1]; var codeSize = this.codeSize; var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; while (codeSize < maxLen) { var b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') + if ((b = str.getByte()) === -1) { error('Bad encoding in flate stream'); + } codeBuf |= (b << codeSize); codeSize += 8; } @@ -33057,7 +33278,6 @@ var FlateStream = (function FlateStreamClosure() { error('Bad encoding in flate stream'); this.codeBuf = (codeBuf >> codeLen); this.codeSize = (codeSize - codeLen); - this.bytesPos = bytesPos; return codeVal; }; @@ -33101,6 +33321,7 @@ var FlateStream = (function FlateStreamClosure() { }; FlateStream.prototype.readBlock = function FlateStream_readBlock() { + var str = this.str; // read block header var hdr = this.getBits(3); if (hdr & 1) @@ -33108,21 +33329,23 @@ var FlateStream = (function FlateStreamClosure() { hdr >>= 1; if (hdr === 0) { // uncompressed block - var bytes = this.bytes; - var bytesPos = this.bytesPos; var b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') + if ((b = str.getByte()) === -1) { error('Bad block header in flate stream'); + } var blockLen = b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') + if ((b = str.getByte()) === -1) { error('Bad block header in flate stream'); + } blockLen |= (b << 8); - if (typeof (b = bytes[bytesPos++]) == 'undefined') + if ((b = str.getByte()) === -1) { error('Bad block header in flate stream'); + } var check = b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') + if ((b = str.getByte()) === -1) { error('Bad block header in flate stream'); + } check |= (b << 8); if (check != (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) { @@ -33138,19 +33361,18 @@ var FlateStream = (function FlateStreamClosure() { var end = bufferLength + blockLen; this.bufferLength = end; if (blockLen === 0) { - if (typeof bytes[bytesPos] == 'undefined') { + if (str.peekBytes(1).length === 0) { this.eof = true; } } else { for (var n = bufferLength; n < end; ++n) { - if (typeof (b = bytes[bytesPos++]) == 'undefined') { + if ((b = str.getByte()) === -1) { this.eof = true; break; } buffer[n] = b; } } - this.bytesPos = bytesPos; return; } @@ -33437,17 +33659,25 @@ var PredictorStream = (function PredictorStreamClosure() { * DecodeStreams. */ var JpegStream = (function JpegStreamClosure() { - function JpegStream(bytes, dict, xref) { + function JpegStream(stream, length, dict, xref) { // TODO: per poppler, some images may have 'junk' before that // need to be removed + this.stream = stream; + this.length = length; this.dict = dict; - this.bytes = bytes; DecodeStream.call(this); } JpegStream.prototype = Object.create(DecodeStream.prototype); + Object.defineProperty(JpegStream.prototype, 'bytes', { + get: function JpegStream_bytes() { + return shadow(this, 'bytes', this.stream.getBytes(this.length)); + }, + configurable: true + }); + JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) { if (this.bufferLength) return; @@ -33496,15 +33726,23 @@ var JpegStream = (function JpegStreamClosure() { * the stream behaves like all the other DecodeStreams. */ var JpxStream = (function JpxStreamClosure() { - function JpxStream(bytes, dict) { + function JpxStream(stream, length, dict) { + this.stream = stream; + this.length = length; this.dict = dict; - this.bytes = bytes; DecodeStream.call(this); } JpxStream.prototype = Object.create(DecodeStream.prototype); + Object.defineProperty(JpxStream.prototype, 'bytes', { + get: function JpxStream_bytes() { + return shadow(this, 'bytes', this.stream.getBytes(this.length)); + }, + configurable: true + }); + JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) { if (this.bufferLength) return; @@ -33595,15 +33833,23 @@ var JpxStream = (function JpxStreamClosure() { * the stream behaves like all the other DecodeStreams. */ var Jbig2Stream = (function Jbig2StreamClosure() { - function Jbig2Stream(bytes, dict) { + function Jbig2Stream(stream, length, dict) { + this.stream = stream; + this.length = length; this.dict = dict; - this.bytes = bytes; DecodeStream.call(this); } Jbig2Stream.prototype = Object.create(DecodeStream.prototype); + Object.defineProperty(Jbig2Stream.prototype, 'bytes', { + get: function Jbig2Stream_bytes() { + return shadow(this, 'bytes', this.stream.getBytes(this.length)); + }, + configurable: true + }); + Jbig2Stream.prototype.ensureBuffer = function Jbig2Stream_ensureBuffer(req) { if (this.bufferLength) return; @@ -35276,7 +35522,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { var pageNum = data.pageIndex + 1; var start = Date.now(); // Pre compile the pdf page and fetch the fonts/images. - page.getOperatorList(handler).then(function(operatorList) { + page.getOperatorList(handler, data.intent).then(function(operatorList) { info('page=' + pageNum + ' - getOperatorList: time=' + (Date.now() - start) + 'ms, len=' + operatorList.fnArray.length); @@ -35308,7 +35554,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { handler.send('PageError', { pageNum: pageNum, - error: wrappedException + error: wrappedException, + intent: data.intent }); }); }); @@ -35394,6 +35641,175 @@ if (typeof window === 'undefined') { } +/* This class implements the QM Coder decoding as defined in + * JPEG 2000 Part I Final Committee Draft Version 1.0 + * Annex C.3 Arithmetic decoding procedure + * available at http://www.jpeg.org/public/fcd15444-1.pdf + * + * The arithmetic decoder is used in conjunction with context models to decode + * JPEG2000 and JBIG2 streams. + */ +var ArithmeticDecoder = (function ArithmeticDecoderClosure() { + // Table C-2 + var QeTable = [ + {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1}, + {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0}, + {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0}, + {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0}, + {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0}, + {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0}, + {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1}, + {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0}, + {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0}, + {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0}, + {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0}, + {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0}, + {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0}, + {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0}, + {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1}, + {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0}, + {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0}, + {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0}, + {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0}, + {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0}, + {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0}, + {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0}, + {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0}, + {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0}, + {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0}, + {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0}, + {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0}, + {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0}, + {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0}, + {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0}, + {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0}, + {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0}, + {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0}, + {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0}, + {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0}, + {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0}, + {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0}, + {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0}, + {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0}, + {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0}, + {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0}, + {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0}, + {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0}, + {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0}, + {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0}, + {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0}, + {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0} + ]; + + // C.3.5 Initialisation of the decoder (INITDEC) + function ArithmeticDecoder(data, start, end) { + this.data = data; + this.bp = start; + this.dataEnd = end; + + this.chigh = data[start]; + this.clow = 0; + + this.byteIn(); + + this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F); + this.clow = (this.clow << 7) & 0xFFFF; + this.ct -= 7; + this.a = 0x8000; + } + + ArithmeticDecoder.prototype = { + // C.3.4 Compressed data input (BYTEIN) + byteIn: function ArithmeticDecoder_byteIn() { + var data = this.data; + var bp = this.bp; + if (data[bp] == 0xFF) { + var b1 = data[bp + 1]; + if (b1 > 0x8F) { + this.clow += 0xFF00; + this.ct = 8; + } else { + bp++; + this.clow += (data[bp] << 9); + this.ct = 7; + this.bp = bp; + } + } else { + bp++; + this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00; + this.ct = 8; + this.bp = bp; + } + if (this.clow > 0xFFFF) { + this.chigh += (this.clow >> 16); + this.clow &= 0xFFFF; + } + }, + // C.3.2 Decoding a decision (DECODE) + readBit: function ArithmeticDecoder_readBit(contexts, pos) { + // contexts are packed into 1 byte: + // highest 7 bits carry cx.index, lowest bit carries cx.mps + var cx_index = contexts[pos] >> 1, cx_mps = contexts[pos] & 1; + var qeTableIcx = QeTable[cx_index]; + var qeIcx = qeTableIcx.qe; + var nmpsIcx = qeTableIcx.nmps; + var nlpsIcx = qeTableIcx.nlps; + var switchIcx = qeTableIcx.switchFlag; + var d; + this.a -= qeIcx; + + if (this.chigh < qeIcx) { + // exchangeLps + if (this.a < qeIcx) { + this.a = qeIcx; + d = cx_mps; + cx_index = nmpsIcx; + } else { + this.a = qeIcx; + d = 1 - cx_mps; + if (switchIcx) { + cx_mps = d; + } + cx_index = nlpsIcx; + } + } else { + this.chigh -= qeIcx; + if ((this.a & 0x8000) !== 0) { + return cx_mps; + } + // exchangeMps + if (this.a < qeIcx) { + d = 1 - cx_mps; + if (switchIcx) { + cx_mps = d; + } + cx_index = nlpsIcx; + } else { + d = cx_mps; + cx_index = nmpsIcx; + } + } + // C.3.3 renormD; + do { + if (this.ct === 0) { + this.byteIn(); + } + + this.a <<= 1; + this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1); + this.clow = (this.clow << 1) & 0xFFFF; + this.ct--; + } while ((this.a & 0x8000) === 0); + + contexts[pos] = cx_index << 1 | cx_mps; + return d; + } + }; + + return ArithmeticDecoder; +})(); + + var JpxImage = (function JpxImageClosure() { // Table E.1 var SubbandsGainLog2 = { @@ -35402,6 +35818,10 @@ var JpxImage = (function JpxImageClosure() { 'HL': 1, 'HH': 2 }; + var TransformType = { + IRREVERSIBLE: 0, + REVERSIBLE: 1 + }; function JpxImage() { this.failOnCorruptedImage = false; } @@ -35414,16 +35834,18 @@ var JpxImage = (function JpxImageClosure() { // TODO catch parse error var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); this.parse(data); - if (this.onload) + if (this.onload) { this.onload(); + } }).bind(this); xhr.send(null); }, parse: function JpxImage_parse(data) { function readUint(data, offset, bytes) { var n = 0; - for (var i = 0; i < bytes; i++) + for (var i = 0; i < bytes; i++) { n = n * 256 + (data[offset + i] & 0xFF); + } return n; } @@ -35445,10 +35867,12 @@ var JpxImage = (function JpxImageClosure() { position += 8; headerSize += 8; } - if (lbox === 0) + if (lbox === 0) { lbox = length - position + headerSize; - if (lbox < headerSize) + } + if (lbox < headerSize) { error('JPX error: Invalid box field size'); + } var dataLength = lbox - headerSize; var jumpDataLength = true; switch (tbox) { @@ -35465,8 +35889,9 @@ var JpxImage = (function JpxImageClosure() { this.parseCodestream(data, position, position + dataLength); break; } - if (jumpDataLength) + if (jumpDataLength) { position += dataLength; + } } }, parseCodestream: function JpxImage_parseCodestream(data, start, end) { @@ -35537,7 +35962,7 @@ var JpxImage = (function JpxImageClosure() { default: throw 'Invalid SQcd value ' + sqcd; } - qcd.noQuantization = spqcdSize == 8; + qcd.noQuantization = (spqcdSize == 8); qcd.scalarExpounded = scalarExpounded; qcd.guardBits = sqcd >> 5; var spqcds = []; @@ -35554,9 +35979,9 @@ var JpxImage = (function JpxImageClosure() { spqcds.push(spqcd); } qcd.SPqcds = spqcds; - if (context.mainHeader) + if (context.mainHeader) { context.QCD = qcd; - else { + } else { context.currentTile.QCD = qcd; context.currentTile.QCC = []; } @@ -35566,9 +35991,9 @@ var JpxImage = (function JpxImageClosure() { var qcc = {}; j = position + 2; var cqcc; - if (context.SIZ.Csiz < 257) + if (context.SIZ.Csiz < 257) { cqcc = data[j++]; - else { + } else { cqcc = readUint16(data, j); j += 2; } @@ -35590,11 +36015,11 @@ var JpxImage = (function JpxImageClosure() { default: throw 'Invalid SQcd value ' + sqcd; } - qcc.noQuantization = spqcdSize == 8; + qcc.noQuantization = (spqcdSize == 8); qcc.scalarExpounded = scalarExpounded; qcc.guardBits = sqcd >> 5; var spqcds = []; - while (j < length + position) { + while (j < (length + position)) { var spqcd = {}; if (spqcdSize == 8) { spqcd.epsilon = data[j++] >> 3; @@ -35607,10 +36032,11 @@ var JpxImage = (function JpxImageClosure() { spqcds.push(spqcd); } qcc.SPqcds = spqcds; - if (context.mainHeader) + if (context.mainHeader) { context.QCC[cqcc] = qcc; - else + } else { context.currentTile.QCC[cqcc] = qcc; + } break; case 0xFF52: // Coding style default (COD) length = readUint16(data, position); @@ -35653,13 +36079,14 @@ var JpxImage = (function JpxImageClosure() { cod.selectiveArithmeticCodingBypass || cod.resetContextProbabilities || cod.terminationOnEachCodingPass || - cod.verticalyStripe || cod.predictableTermination) + cod.verticalyStripe || cod.predictableTermination) { throw 'Unsupported COD options: ' + globalScope.JSON.stringify(cod); + } - if (context.mainHeader) + if (context.mainHeader) { context.COD = cod; - else { + } else { context.currentTile.COD = cod; context.currentTile.COC = []; } @@ -35692,7 +36119,6 @@ var JpxImage = (function JpxImageClosure() { // moving to the end of the data length = tile.dataEnd - position; - parseTilePackets(context, data, position, length); break; case 0xFF64: // Comment (COM) @@ -35705,10 +36131,11 @@ var JpxImage = (function JpxImageClosure() { position += length; } } catch (e) { - if (this.failOnCorruptedImage) + if (this.failOnCorruptedImage) { error('JPX error: ' + e); - else + } else { warn('JPX error: ' + e + '. Trying to recover'); + } } this.tiles = transformComponents(context); this.width = context.SIZ.Xsiz - context.SIZ.XOsiz; @@ -35718,7 +36145,7 @@ var JpxImage = (function JpxImageClosure() { }; function readUint32(data, offset) { return (data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]; + (data[offset + 2] << 8) | data[offset + 3]; } function readUint16(data, offset) { return (data[offset] << 8) | data[offset + 1]; @@ -35788,27 +36215,27 @@ var JpxImage = (function JpxImageClosure() { result.PPy = codOrCoc.precinctsSizes[r].PPy; } // calculate codeblock size as described in section B.7 - result.xcb_ = r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) : - Math.min(codOrCoc.xcb, result.PPx); - result.ycb_ = r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) : - Math.min(codOrCoc.ycb, result.PPy); + result.xcb_ = (r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) : + Math.min(codOrCoc.xcb, result.PPx)); + result.ycb_ = (r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) : + Math.min(codOrCoc.ycb, result.PPy)); return result; } function buildPrecincts(context, resolution, dimensions) { // Section B.6 Division resolution to precincts var precinctWidth = 1 << dimensions.PPx; var precinctHeight = 1 << dimensions.PPy; - var numprecinctswide = resolution.trx1 > resolution.trx0 ? + var numprecinctswide = (resolution.trx1 > resolution.trx0 ? Math.ceil(resolution.trx1 / precinctWidth) - - Math.floor(resolution.trx0 / precinctWidth) : 0; - var numprecinctshigh = resolution.try1 > resolution.try0 ? + Math.floor(resolution.trx0 / precinctWidth) : 0); + var numprecinctshigh = (resolution.try1 > resolution.try0 ? Math.ceil(resolution.try1 / precinctHeight) - - Math.floor(resolution.try0 / precinctHeight) : 0; + Math.floor(resolution.try0 / precinctHeight) : 0); var numprecincts = numprecinctswide * numprecinctshigh; var precinctXOffset = Math.floor(resolution.trx0 / precinctWidth) * - precinctWidth; + precinctWidth; var precinctYOffset = Math.floor(resolution.try0 / precinctHeight) * - precinctHeight; + precinctHeight; resolution.precinctParameters = { precinctXOffset: precinctXOffset, precinctYOffset: precinctYOffset, @@ -35844,13 +36271,13 @@ var JpxImage = (function JpxImageClosure() { }; // calculate precinct number var pi = Math.floor((codeblock.tbx0 - - precinctParameters.precinctXOffset) / - precinctParameters.precinctWidth); + precinctParameters.precinctXOffset) / + precinctParameters.precinctWidth); var pj = Math.floor((codeblock.tby0 - - precinctParameters.precinctYOffset) / - precinctParameters.precinctHeight); + precinctParameters.precinctYOffset) / + precinctParameters.precinctHeight); var precinctNumber = pj + - pi * precinctParameters.numprecinctswide; + pi * precinctParameters.numprecinctswide; codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0); codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0); codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1); @@ -35858,7 +36285,7 @@ var JpxImage = (function JpxImageClosure() { codeblock.precinctNumber = precinctNumber; codeblock.subbandType = subband.type; var coefficientsLength = (codeblock.tbx1_ - codeblock.tbx0_) * - (codeblock.tby1_ - codeblock.tby0_); + (codeblock.tby1_ - codeblock.tby0_); codeblock.Lblock = 3; codeblocks.push(codeblock); // building precinct for the sub-band @@ -35934,8 +36361,9 @@ var JpxImage = (function JpxImageClosure() { for (; r <= maxDecompositionLevelsCount; r++) { for (; i < componentsCount; i++) { var component = tile.components[i]; - if (r > component.codingStyleParameters.decompositionLevelsCount) + if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; + } var resolution = component.resolutions[r]; var numprecincts = resolution.precinctParameters.numprecincts; @@ -35973,8 +36401,9 @@ var JpxImage = (function JpxImageClosure() { for (; l < layersCount; l++) { for (; i < componentsCount; i++) { var component = tile.components[i]; - if (r > component.codingStyleParameters.decompositionLevelsCount) + if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; + } var resolution = component.resolutions[r]; var numprecincts = resolution.precinctParameters.numprecincts; @@ -36243,7 +36672,6 @@ var JpxImage = (function JpxImageClosure() { function copyCoefficients(coefficients, x0, y0, width, height, delta, mb, codeblocks, transformation, segmentationSymbolUsed) { - var r = 0.5; // formula (E-6) for (var i = 0, ii = codeblocks.length; i < ii; ++i) { var codeblock = codeblocks[i]; var blockWidth = codeblock.tbx1_ - codeblock.tbx0_; @@ -36257,7 +36685,7 @@ var JpxImage = (function JpxImageClosure() { var bitModel, currentCodingpassType; bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType, - codeblock.zeroBitPlanes); + codeblock.zeroBitPlanes); currentCodingpassType = 2; // first bit plane starts from cleanup // collect data @@ -36297,20 +36725,16 @@ var JpxImage = (function JpxImageClosure() { } var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width; - var position = 0; + var n, nb, correction, position = 0; + var irreversible = (transformation === TransformType.IRREVERSIBLE); + var sign = bitModel.coefficentsSign; + var magnitude = bitModel.coefficentsMagnitude; + var bitsDecoded = bitModel.bitsDecoded; for (var j = 0; j < blockHeight; j++) { for (var k = 0; k < blockWidth; k++) { - var n = (bitModel.coefficentsSign[position] ? -1 : 1) * - bitModel.coefficentsMagnitude[position]; - var nb = bitModel.bitsDecoded[position], correction; - if (transformation === 0 || mb > nb) { - // use r only if transformation is irreversible or - // not all bitplanes were decoded for reversible transformation - n += n < 0 ? n - r : n > 0 ? n + r : 0; - correction = 1 << (mb - nb); - } else { - correction = 1; - } + n = (sign[position] ? -1 : 1) * magnitude[position]; + nb = bitsDecoded[position]; + correction = (irreversible || mb > nb) ? 1 << (mb - nb) : 1; coefficients[offset++] = n * correction * delta; position++; } @@ -36331,6 +36755,10 @@ var JpxImage = (function JpxImageClosure() { var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; var precision = context.components[c].precision; + var transformation = codingStyleParameters.transformation; + var transform = (transformation === TransformType.IRREVERSIBLE ? + new IrreversibleTransform() : new ReversibleTransform()); + var subbandCoefficients = []; var k = 0, b = 0; for (var i = 0; i <= decompositionLevelsCount; i++) { @@ -36353,8 +36781,8 @@ var JpxImage = (function JpxImageClosure() { var gainLog2 = SubbandsGainLog2[subband.type]; // calulate quantization coefficient (Section E.1.1.1) - var delta = Math.pow(2, (precision + gainLog2) - epsilon) * - (1 + mu / 2048); + var delta = (transformation === TransformType.IRREVERSIBLE ? + Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048) : 1); var mb = (guardBits + epsilon - 1); var coefficients = new Float32Array(width * height); @@ -36372,11 +36800,8 @@ var JpxImage = (function JpxImageClosure() { } } - var transformation = codingStyleParameters.transformation; - var transform = transformation === 0 ? new IrreversibleTransform() : - new ReversibleTransform(); var result = transform.calculate(subbandCoefficients, - component.tcx0, component.tcy0); + component.tcx0, component.tcy0); return { left: component.tcx0, top: component.tcy0, @@ -36400,15 +36825,31 @@ var JpxImage = (function JpxImageClosure() { // Section G.2.2 Inverse multi component transform if (tile.codingStyleDefaultParameters.multipleComponentTransform) { - var y0items = result[0].items; - var y1items = result[1].items; - var y2items = result[2].items; - for (var j = 0, jj = y0items.length; j < jj; j++) { - var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j]; - var i1 = y0 - ((y2 + y1) >> 2); - y1items[j] = i1; - y0items[j] = y2 + i1; - y2items[j] = y1 + i1; + var component0 = tile.components[0]; + var transformation = component0.codingStyleParameters.transformation; + if (transformation === TransformType.IRREVERSIBLE) { + // inverse irreversible multiple component transform + var y0items = result[0].items; + var y1items = result[1].items; + var y2items = result[2].items; + for (var j = 0, jj = y0items.length; j < jj; ++j) { + var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j]; + y0items[j] = y0 + 1.402 * y2 + 0.5; + y1items[j] = y0 - 0.34413 * y1 - 0.71414 * y2 + 0.5; + y2items[j] = y0 + 1.772 * y1 + 0.5; + } + } else { + // inverse reversible multiple component transform + var y0items = result[0].items; + var y1items = result[1].items; + var y2items = result[2].items; + for (var j = 0, jj = y0items.length; j < jj; ++j) { + var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j]; + var i1 = y0 - ((y2 + y1) >> 2); + y1items[j] = i1; + y0items[j] = y2 + i1; + y2items[j] = y1 + i1; + } } } @@ -36453,11 +36894,11 @@ var JpxImage = (function JpxImageClosure() { var resultTiles = []; for (var c = 0; c < componentsCount; c++) { var component = tile.components[c]; - var qcdOrQcc = c in context.currentTile.QCC ? - context.currentTile.QCC[c] : context.currentTile.QCD; + var qcdOrQcc = (c in context.currentTile.QCC ? + context.currentTile.QCC[c] : context.currentTile.QCD); component.quantizationParameters = qcdOrQcc; - var codOrCoc = c in context.currentTile.COC ? - context.currentTile.COC[c] : context.currentTile.COD; + var codOrCoc = (c in context.currentTile.COC ? + context.currentTile.COC[c] : context.currentTile.COD); component.codingStyleParameters = codOrCoc; } tile.codingStyleDefaultParameters = context.currentTile.COD; @@ -36604,172 +37045,10 @@ var JpxImage = (function JpxImageClosure() { return InclusionTree; })(); - // Implements C.3. Arithmetic decoding procedures - var ArithmeticDecoder = (function ArithmeticDecoderClosure() { - var QeTable = [ - {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1}, - {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0}, - {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0}, - {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0}, - {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0}, - {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0}, - {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1}, - {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0}, - {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0}, - {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0}, - {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0}, - {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0}, - {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0}, - {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0}, - {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1}, - {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0}, - {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0}, - {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0}, - {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0}, - {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0}, - {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0}, - {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0}, - {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0}, - {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0}, - {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0}, - {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0}, - {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0}, - {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0}, - {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0}, - {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0}, - {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0}, - {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0}, - {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0}, - {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0}, - {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0}, - {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0}, - {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0}, - {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0}, - {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0}, - {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0}, - {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0}, - {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0}, - {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0}, - {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0}, - {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0}, - {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0}, - {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0} - ]; - - function ArithmeticDecoder(data, start, end) { - this.data = data; - this.bp = start; - this.dataEnd = end; - - this.chigh = data[start]; - this.clow = 0; - - this.byteIn(); - - this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F); - this.clow = (this.clow << 7) & 0xFFFF; - this.ct -= 7; - this.a = 0x8000; - } - - ArithmeticDecoder.prototype = { - byteIn: function ArithmeticDecoder_byteIn() { - var data = this.data; - var bp = this.bp; - if (data[bp] == 0xFF) { - var b1 = data[bp + 1]; - if (b1 > 0x8F) { - this.clow += 0xFF00; - this.ct = 8; - } else { - bp++; - this.clow += (data[bp] << 9); - this.ct = 7; - this.bp = bp; - } - } else { - bp++; - this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00; - this.ct = 8; - this.bp = bp; - } - if (this.clow > 0xFFFF) { - this.chigh += (this.clow >> 16); - this.clow &= 0xFFFF; - } - }, - readBit: function ArithmeticDecoder_readBit(cx) { - var qeIcx = QeTable[cx.index].qe; - this.a -= qeIcx; - - if (this.chigh < qeIcx) { - var d = this.exchangeLps(cx); - this.renormD(); - return d; - } else { - this.chigh -= qeIcx; - if ((this.a & 0x8000) === 0) { - var d = this.exchangeMps(cx); - this.renormD(); - return d; - } else { - return cx.mps; - } - } - }, - renormD: function ArithmeticDecoder_renormD() { - do { - if (this.ct === 0) { - this.byteIn(); - } - - this.a <<= 1; - this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1); - this.clow = (this.clow << 1) & 0xFFFF; - this.ct--; - } while ((this.a & 0x8000) === 0); - }, - exchangeMps: function ArithmeticDecoder_exchangeMps(cx) { - var d; - var qeTableIcx = QeTable[cx.index]; - if (this.a < qeTableIcx.qe) { - d = 1 - cx.mps; - - if (qeTableIcx.switchFlag == 1) { - cx.mps = 1 - cx.mps; - } - cx.index = qeTableIcx.nlps; - } else { - d = cx.mps; - cx.index = qeTableIcx.nmps; - } - return d; - }, - exchangeLps: function ArithmeticDecoder_exchangeLps(cx) { - var d; - var qeTableIcx = QeTable[cx.index]; - if (this.a < qeTableIcx.qe) { - this.a = qeTableIcx.qe; - d = cx.mps; - cx.index = qeTableIcx.nmps; - } else { - this.a = qeTableIcx.qe; - d = 1 - cx.mps; - - if (qeTableIcx.switchFlag == 1) { - cx.mps = 1 - cx.mps; - } - cx.index = qeTableIcx.nlps; - } - return d; - } - }; - - return ArithmeticDecoder; - })(); - // Section D. Coefficient bit modeling var BitModel = (function BitModelClosure() { + var UNIFORM_CONTEXT = 17; + var RUNLENGTH_CONTEXT = 18; // Table D-1 // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4), // vv - sum of Vi (0..2), and hh - sum of Hi (0..2) @@ -36818,8 +37097,8 @@ var JpxImage = (function JpxImageClosure() { this.width = width; this.height = height; - this.contextLabelTable = subband == 'HH' ? HHContextLabel : - subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel; + this.contextLabelTable = (subband == 'HH' ? HHContextLabel : + (subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel)); var coefficientCount = width * height; @@ -36831,8 +37110,9 @@ var JpxImage = (function JpxImageClosure() { this.processingFlags = new Uint8Array(coefficientCount); var bitsDecoded = new Uint8Array(this.width * this.height); - for (var i = 0, ii = bitsDecoded.length; i < ii; i++) + for (var i = 0, ii = bitsDecoded.length; i < ii; i++) { bitsDecoded[i] = zeroBitPlanes; + } this.bitsDecoded = bitsDecoded; this.reset(); @@ -36843,13 +37123,15 @@ var JpxImage = (function JpxImageClosure() { this.decoder = decoder; }, reset: function BitModel_reset() { - this.uniformContext = {index: 46, mps: 0}; - this.runLengthContext = {index: 3, mps: 0}; - this.contexts = []; - this.contexts.push({index: 4, mps: 0}); - for (var i = 1; i <= 16; i++) { - this.contexts.push({index: 0, mps: 0}); - } + // We have 17 contexts that are accessed via context labels, + // plus the uniform and runlength context. + this.contexts = new Int8Array(19); + + // Contexts are packed into 1 byte: + // highest 7 bits carry the index, lowest bit carries mps + this.contexts[0] = (4 << 1) | 0; + this.contexts[UNIFORM_CONTEXT] = (46 << 1) | 0; + this.contexts[RUNLENGTH_CONTEXT] = (3 << 1) | 0; }, setNeighborsSignificance: function BitModel_setNeighborsSignificance(row, column) { @@ -36911,12 +37193,13 @@ var JpxImage = (function JpxImageClosure() { break; } - if (coefficentsMagnitude[index] || !neighborsSignificance[index]) + if (coefficentsMagnitude[index] || + !neighborsSignificance[index]) { continue; + } var contextLabel = labels[neighborsSignificance[index]]; - var cx = contexts[contextLabel]; - var decision = decoder.readBit(cx); + var decision = decoder.readBit(contexts, contextLabel); if (decision) { var sign = this.decodeSignBit(i, j); coefficentsSign[index] = sign; @@ -36949,8 +37232,7 @@ var JpxImage = (function JpxImageClosure() { var contextLabelAndXor = SignContextLabels[ 3 * (1 - horizontalContribution) + (1 - verticalContribution)]; var contextLabel = contextLabelAndXor.contextLabel; - var cx = this.contexts[contextLabel]; - var decoded = this.decoder.readBit(cx); + var decoded = this.decoder.readBit(this.contexts, contextLabel); return decoded ^ contextLabelAndXor.xorBit; }, runMagnitudeRefinementPass: @@ -36980,8 +37262,7 @@ var JpxImage = (function JpxImageClosure() { } var contextLabel = 16; - if ((processingFlags[index] & - firstMagnitudeBitMask) !== 0) { + if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) { processingFlags[i * width + j] ^= firstMagnitudeBitMask; // first refinement var significance = neighborsSignificance[index]; @@ -36990,8 +37271,7 @@ var JpxImage = (function JpxImageClosure() { contextLabel = sumOfSignificance >= 1 ? 15 : 14; } - var cx = contexts[contextLabel]; - var bit = decoder.readBit(cx); + var bit = decoder.readBit(contexts, contextLabel); coefficentsMagnitude[index] = (coefficentsMagnitude[index] << 1) | bit; bitsDecoded[index]++; @@ -37021,7 +37301,7 @@ var JpxImage = (function JpxImageClosure() { var index0 = i0 * width + j; // using the property: labels[neighborsSignificance[index]] == 0 // when neighborsSignificance[index] == 0 - var allEmpty = i0 + 3 < height && + var allEmpty = (i0 + 3 < height && processingFlags[index0] === 0 && processingFlags[index0 + oneRowDown] === 0 && processingFlags[index0 + twoRowsDown] === 0 && @@ -37029,12 +37309,12 @@ var JpxImage = (function JpxImageClosure() { neighborsSignificance[index0] === 0 && neighborsSignificance[index0 + oneRowDown] === 0 && neighborsSignificance[index0 + twoRowsDown] === 0 && - neighborsSignificance[index0 + threeRowsDown] === 0; + neighborsSignificance[index0 + threeRowsDown] === 0); var i1 = 0, index = index0; - var cx, i; + var i; if (allEmpty) { - cx = this.runLengthContext; - var hasSignificantCoefficent = decoder.readBit(cx); + var hasSignificantCoefficent = + decoder.readBit(contexts, RUNLENGTH_CONTEXT); if (!hasSignificantCoefficent) { bitsDecoded[index0]++; bitsDecoded[index0 + oneRowDown]++; @@ -37042,8 +37322,8 @@ var JpxImage = (function JpxImageClosure() { bitsDecoded[index0 + threeRowsDown]++; continue; // next column } - cx = this.uniformContext; - i1 = (decoder.readBit(cx) << 1) | decoder.readBit(cx); + i1 = (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | + decoder.readBit(contexts, UNIFORM_CONTEXT); i = i0 + i1; index += i1 * width; @@ -37072,8 +37352,7 @@ var JpxImage = (function JpxImageClosure() { } var contextLabel = labels[neighborsSignificance[index]]; - cx = contexts[contextLabel]; - var decision = decoder.readBit(cx); + var decision = decoder.readBit(contexts, contextLabel); if (decision == 1) { var sign = this.decodeSignBit(i, j); coefficentsSign[index] = sign; @@ -37088,9 +37367,11 @@ var JpxImage = (function JpxImageClosure() { }, checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() { var decoder = this.decoder; - var cx = this.uniformContext; - var symbol = (decoder.readBit(cx) << 3) | (decoder.readBit(cx) << 2) | - (decoder.readBit(cx) << 1) | decoder.readBit(cx); + var contexts = this.contexts; + var symbol = (decoder.readBit(contexts, UNIFORM_CONTEXT) << 3) | + (decoder.readBit(contexts, UNIFORM_CONTEXT) << 2) | + (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | + decoder.readBit(contexts, UNIFORM_CONTEXT); if (symbol != 0xA) { throw 'Invalid segmentation symbol'; } @@ -37100,10 +37381,10 @@ var JpxImage = (function JpxImageClosure() { return BitModel; })(); - // Section F, Discrete wavelet transofrmation + // Section F, Discrete wavelet transformation var Transform = (function TransformClosure() { - function Transform() { - } + function Transform() {} + Transform.prototype.calculate = function transformCalculate(subbands, u0, v0) { var ll = subbands[0]; @@ -37114,17 +37395,17 @@ var JpxImage = (function JpxImageClosure() { return ll; }; Transform.prototype.extend = function extend(buffer, offset, size) { - // Section F.3.7 extending... using max extension of 4 - var i1 = offset - 1, j1 = offset + 1; - var i2 = offset + size - 2, j2 = offset + size; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1] = buffer[j1]; - buffer[j2] = buffer[i2]; + // Section F.3.7 extending... using max extension of 4 + var i1 = offset - 1, j1 = offset + 1; + var i2 = offset + size - 2, j2 = offset + size; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1] = buffer[j1]; + buffer[j2] = buffer[i2]; }; Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh, u0, v0) { @@ -37266,33 +37547,39 @@ var JpxImage = (function JpxImageClosure() { // step 1 var j = offset_ - 2; - for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) + for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) { x[j] = K * y[j]; + } // step 2 var j = offset_ - 3; - for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2) + for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2) { x[j] = K_ * y[j]; + } // step 3 var j = offset_ - 2; - for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) + for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) { x[j] -= delta * (x[j - 1] + x[j + 1]); + } // step 4 var j = offset_ - 1; - for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2) + for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2) { x[j] -= gamma * (x[j - 1] + x[j + 1]); + } // step 5 var j = offset_; - for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2) + for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2) { x[j] -= beta * (x[j - 1] + x[j + 1]); + } // step 6 var j = offset_ + 1; - for (var n = i0_, nn = i1_; n < nn; n++, j += 2) + for (var n = i0_, nn = i1_; n < nn; n++, j += 2) { x[j] -= alpha * (x[j - 1] + x[j + 1]); + } }; return IrreversibleTransform; @@ -37311,11 +37598,13 @@ var JpxImage = (function JpxImageClosure() { var i1_ = Math.floor((i0 + length) / 2); var offset_ = offset - (i0 % 1); - for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2) + for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2) { x[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + 2) / 4); + } - for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2) + for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2) { x[j] = y[j] + Math.floor((x[j - 1] + x[j + 1]) / 2); + } }; return ReversibleTransform; @@ -37327,170 +37616,14 @@ var JpxImage = (function JpxImageClosure() { var Jbig2Image = (function Jbig2ImageClosure() { - - // Annex E. Arithmetic Coding - var ArithmeticDecoder = (function ArithmeticDecoderClosure() { - var QeTable = [ - {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1}, - {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0}, - {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0}, - {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0}, - {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0}, - {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0}, - {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1}, - {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0}, - {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0}, - {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0}, - {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0}, - {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0}, - {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0}, - {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0}, - {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1}, - {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0}, - {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0}, - {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0}, - {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0}, - {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0}, - {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0}, - {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0}, - {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0}, - {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0}, - {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0}, - {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0}, - {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0}, - {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0}, - {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0}, - {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0}, - {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0}, - {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0}, - {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0}, - {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0}, - {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0}, - {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0}, - {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0}, - {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0}, - {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0}, - {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0}, - {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0}, - {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0}, - {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0}, - {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0}, - {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0}, - {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0}, - {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0} - ]; - - function ArithmeticDecoder(data, start, end) { - this.data = data; - this.bp = start; - this.dataEnd = end; - - this.chigh = data[start]; - this.clow = 0; - - this.byteIn(); - - this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F); - this.clow = (this.clow << 7) & 0xFFFF; - this.ct -= 7; - this.a = 0x8000; - } - - ArithmeticDecoder.prototype = { - byteIn: function ArithmeticDecoder_byteIn() { - var data = this.data; - var bp = this.bp; - if (data[bp] == 0xFF) { - var b1 = data[bp + 1]; - if (b1 > 0x8F) { - this.clow += 0xFF00; - this.ct = 8; - } else { - bp++; - this.clow += (data[bp] << 9); - this.ct = 7; - this.bp = bp; - } - } else { - bp++; - this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00; - this.ct = 8; - this.bp = bp; - } - if (this.clow > 0xFFFF) { - this.chigh += (this.clow >> 16); - this.clow &= 0xFFFF; - } - }, - readBit: function ArithmeticDecoder_readBit(contexts, pos) { - // contexts are packed into 1 byte: - // highest 7 bits carry cx.index, lowest bit carries cx.mps - var cx_index = contexts[pos] >> 1, cx_mps = contexts[pos] & 1; - var qeTableIcx = QeTable[cx_index]; - var qeIcx = qeTableIcx.qe; - var nmpsIcx = qeTableIcx.nmps; - var nlpsIcx = qeTableIcx.nlps; - var switchIcx = qeTableIcx.switchFlag; - var d; - this.a -= qeIcx; - - if (this.chigh < qeIcx) { - // exchangeLps - if (this.a < qeIcx) { - this.a = qeIcx; - d = cx_mps; - cx_index = nmpsIcx; - } else { - this.a = qeIcx; - d = 1 - cx_mps; - if (switchIcx) { - cx_mps = d; - } - cx_index = nlpsIcx; - } - } else { - this.chigh -= qeIcx; - if ((this.a & 0x8000) !== 0) { - return cx_mps; - } - // exchangeMps - if (this.a < qeIcx) { - d = 1 - cx_mps; - if (switchIcx) { - cx_mps = d; - } - cx_index = nlpsIcx; - } else { - d = cx_mps; - cx_index = nmpsIcx; - } - } - // renormD; - do { - if (this.ct === 0) - this.byteIn(); - - this.a <<= 1; - this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1); - this.clow = (this.clow << 1) & 0xFFFF; - this.ct--; - } while ((this.a & 0x8000) === 0); - - contexts[pos] = cx_index << 1 | cx_mps; - return d; - } - }; - - return ArithmeticDecoder; - })(); - // Utility data structures function ContextCache() {} ContextCache.prototype = { getContexts: function(id) { - if (id in this) + if (id in this) { return this[id]; + } return (this[id] = new Int8Array(1<<16)); } }; @@ -37522,52 +37655,63 @@ var Jbig2Image = (function Jbig2ImageClosure() { var toRead = 32, offset = 4436; // defaults for state 7 while (state) { var bit = decoder.readBit(contexts, prev); - prev = prev < 256 ? (prev << 1) | bit : - (((prev << 1) | bit) & 511) | 256; + prev = (prev < 256 ? (prev << 1) | bit : + (((prev << 1) | bit) & 511) | 256); switch (state) { case 1: s = !!bit; break; case 2: - if (bit) break; + if (bit) { + break; + } state = 7; toRead = 2; offset = 0; break; case 3: - if (bit) break; + if (bit) { + break; + } state = 7; toRead = 4; offset = 4; break; case 4: - if (bit) break; + if (bit) { + break; + } state = 7; toRead = 6; offset = 20; break; case 5: - if (bit) break; + if (bit) { + break; + } state = 7; toRead = 8; offset = 84; break; case 6: - if (bit) break; + if (bit) { + break; + } state = 7; toRead = 12; offset = 340; break; default: v = v * 2 + bit; - if (--toRead === 0) + if (--toRead === 0) { state = 0; + } continue; } state++; } v += offset; - return !s ? v : v > 0 ? -v : null; + return (!s ? v : (v > 0 ? -v : null)); } // A.3 The IAID decoding procedure @@ -37579,10 +37723,10 @@ var Jbig2Image = (function Jbig2ImageClosure() { var bit = decoder.readBit(contexts, prev); prev = (prev * 2) + bit; } - if (codeLength < 31) + if (codeLength < 31) { return prev & ((1 << codeLength) - 1); - else - return prev - Math.pow(2, codeLength); + } + return prev - Math.pow(2, codeLength); } // 7.3 Segment types @@ -37671,8 +37815,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { // 6.2 Generic Region Decoding Procedure function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at, decodingContext) { - if (mmr) + if (mmr) { error('JBIG2 error: MMR encoding is not supported'); + } var useskip = !!skip; var template = CodingTemplates[templateIndex].concat(at); @@ -37768,8 +37913,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { offsetX, offsetY, prediction, at, decodingContext) { var codingTemplate = RefinementTemplates[templateIndex].coding; - if (templateIndex === 0) + if (templateIndex === 0) { codingTemplate = codingTemplate.concat([at[0]]); + } var codingTemplateLength = codingTemplate.length; var codingTemplateX = new Int32Array(codingTemplateLength); var codingTemplateY = new Int32Array(codingTemplateLength); @@ -37777,9 +37923,11 @@ var Jbig2Image = (function Jbig2ImageClosure() { codingTemplateX[k] = codingTemplate[k].x; codingTemplateY[k] = codingTemplate[k].y; } + var referenceTemplate = RefinementTemplates[templateIndex].reference; - if (templateIndex === 0) + if (templateIndex === 0) { referenceTemplate = referenceTemplate.concat([at[1]]); + } var referenceTemplateLength = referenceTemplate.length; var referenceTemplateX = new Int32Array(referenceTemplateLength); var referenceTemplateY = new Int32Array(referenceTemplateLength); @@ -37805,24 +37953,28 @@ var Jbig2Image = (function Jbig2ImageClosure() { var row = new Uint8Array(width); bitmap.push(row); for (var j = 0; j < width; j++) { - if (ltp) + if (ltp) { error('JBIG2 error: prediction is not supported'); + } var contextLabel = 0; for (var k = 0; k < codingTemplateLength; k++) { var i0 = i + codingTemplateY[k], j0 = j + codingTemplateX[k]; - if (i0 < 0 || j0 < 0 || j0 >= width) + if (i0 < 0 || j0 < 0 || j0 >= width) { contextLabel <<= 1; // out of bound pixel - else + } else { contextLabel = (contextLabel << 1) | bitmap[i0][j0]; + } } for (var k = 0; k < referenceTemplateLength; k++) { var i0 = i + referenceTemplateY[k] + offsetY; var j0 = j + referenceTemplateX[k] + offsetX; - if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) + if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || + j0 >= referenceWidth) { contextLabel <<= 1; // out of bound pixel - else + } else { contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0]; + } } var pixel = decoder.readBit(contexts, contextLabel); row[j] = pixel; @@ -37838,8 +37990,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { huffmanTables, templateIndex, at, refinementTemplateIndex, refinementAt, decodingContext) { - if (huffman) + if (huffman) { error('JBIG2 error: huffman is not supported'); + } var newSymbols = []; var currentHeight = 0; @@ -37855,21 +38008,23 @@ var Jbig2Image = (function Jbig2ImageClosure() { var totalWidth = 0; while (true) { var deltaWidth = decodeInteger(contextCache, 'IADW', decoder); // 6.5.7 - if (deltaWidth === null) + if (deltaWidth === null) { break; // OOB + } currentWidth += deltaWidth; totalWidth += currentWidth; var bitmap; if (refinement) { // 6.5.8.2 Refinement/aggregate-coded symbol bitmap var numberOfInstances = decodeInteger(contextCache, 'IAAI', decoder); - if (numberOfInstances > 1) + if (numberOfInstances > 1) { error('JBIG2 error: number of instances > 1 is not supported'); + } var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); var rdx = decodeInteger(contextCache, 'IARDX', decoder); // 6.4.11.3 var rdy = decodeInteger(contextCache, 'IARDY', decoder); // 6.4.11.4 - var symbol = symbolId < symbols.length ? symbols[symbolId] : - newSymbols[symbolId - symbols.length]; + var symbol = (symbolId < symbols.length ? symbols[symbolId] : + newSymbols[symbolId - symbols.length]); bitmap = decodeRefinement(currentWidth, currentHeight, refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt, decodingContext); @@ -37887,14 +38042,21 @@ var Jbig2Image = (function Jbig2ImageClosure() { var totalSymbolsLength = symbols.length + numberOfNewSymbols; while (flags.length < totalSymbolsLength) { var runLength = decodeInteger(contextCache, 'IAEX', decoder); - while (runLength--) + while (runLength--) { flags.push(currentFlag); + } currentFlag = !currentFlag; } - for (var i = 0, ii = symbols.length; i < ii; i++) - if (flags[i]) exportedSymbols.push(symbols[i]); - for (var j = 0; j < numberOfNewSymbols; i++, j++) - if (flags[i]) exportedSymbols.push(newSymbols[j]); + for (var i = 0, ii = symbols.length; i < ii; i++) { + if (flags[i]) { + exportedSymbols.push(symbols[i]); + } + } + for (var j = 0; j < numberOfNewSymbols; i++, j++) { + if (flags[i]) { + exportedSymbols.push(newSymbols[j]); + } + } return exportedSymbols; } @@ -37905,16 +38067,18 @@ var Jbig2Image = (function Jbig2ImageClosure() { combinationOperator, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext) { - if (huffman) + if (huffman) { error('JBIG2 error: huffman is not supported'); + } // Prepare bitmap var bitmap = []; for (var i = 0; i < height; i++) { var row = new Uint8Array(width); if (defaultPixelValue) { - for (var j = 0; j < width; j++) + for (var j = 0; j < width; j++) { row[j] = defaultPixelValue; + } } bitmap.push(row); } @@ -38010,8 +38174,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { } i++; var deltaS = decodeInteger(contextCache, 'IADS', decoder); // 6.4.8 - if (deltaS === null) + if (deltaS === null) { break; // OOB + } currentS += deltaS + dsOffset; } while (true); } @@ -38023,11 +38188,13 @@ var Jbig2Image = (function Jbig2ImageClosure() { segmentHeader.number = readUint32(data, start); var flags = data[start + 4]; var segmentType = flags & 0x3F; - if (!SegmentTypes[segmentType]) + if (!SegmentTypes[segmentType]) { error('JBIG2 error: invalid segment type: ' + segmentType); + } segmentHeader.type = segmentType; segmentHeader.typeName = SegmentTypes[segmentType]; segmentHeader.deferredNonRetain = !!(flags & 0x80); + var pageAssociationFieldSize = !!(flags & 0x40); var referredFlags = data[start + 5]; var referredToCount = (referredFlags >> 5) & 7; @@ -38041,28 +38208,31 @@ var Jbig2Image = (function Jbig2ImageClosure() { while (--bytes > 0) { retainBits.push(data[position++]); } - } else if (referredFlags == 5 || referredFlags == 6) + } else if (referredFlags == 5 || referredFlags == 6) { error('JBIG2 error: invalid referred-to flags'); + } + segmentHeader.retainBits = retainBits; - var referredToSegmentNumberSize = segmentHeader.number <= 256 ? 1 : - segmentHeader.number <= 65536 ? 2 : 4; + var referredToSegmentNumberSize = (segmentHeader.number <= 256 ? 1 : + (segmentHeader.number <= 65536 ? 2 : 4)); var referredTo = []; for (var i = 0; i < referredToCount; i++) { - var number = referredToSegmentNumberSize == 1 ? data[position] : - referredToSegmentNumberSize == 2 ? readUint16(data, position) : - readUint32(data, position); + var number = (referredToSegmentNumberSize == 1 ? data[position] : + (referredToSegmentNumberSize == 2 ? readUint16(data, position) : + readUint32(data, position))); referredTo.push(number); position += referredToSegmentNumberSize; } segmentHeader.referredTo = referredTo; - if (!pageAssociationFieldSize) + if (!pageAssociationFieldSize) { segmentHeader.pageAssociation = data[position++]; - else { + } else { segmentHeader.pageAssociation = readUint32(data, position); position += 4; } segmentHeader.length = readUint32(data, position); position += 4; + if (segmentHeader.length == 0xFFFFFFFF) { // 7.2.7 Segment data length, unknown segment length if (segmentType === 38) { // ImmediateGenericRegion @@ -38118,8 +38288,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { segment.end = position; } segments.push(segment); - if (segmentHeader.type == 51) + if (segmentHeader.type == 51) { break; // end of file is found + } } if (header.randomAccess) { for (var i = 0, ii = segments.length; i < ii; i++) { @@ -38237,8 +38408,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { textRegion.numberOfSymbolInstances = readUint32(data, position); position += 4; // TODO 7.4.3.1.7 Symbol ID Huffman table decoding - if (textRegion.huffman) + if (textRegion.huffman) { error('JBIG2 error: huffman is not supported'); + } args = [textRegion, header.referredTo, data, position, end]; break; case 38: // ImmediateGenericRegion @@ -38271,8 +38443,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { resolutionX: readUint32(data, position + 8), resolutionY: readUint32(data, position + 12) }; - if (pageInfo.height == 0xFFFFFFFF) + if (pageInfo.height == 0xFFFFFFFF) { delete pageInfo.height; + } var pageSegmentFlags = data[position + 16]; var pageStripingInformatiom = readUint16(data, position + 17); pageInfo.lossless = !!(pageSegmentFlags & 1); @@ -38297,13 +38470,15 @@ var Jbig2Image = (function Jbig2ImageClosure() { header.type + ') is not implemented'); } var callbackName = 'on' + header.typeName; - if (callbackName in visitor) + if (callbackName in visitor) { visitor[callbackName].apply(visitor, args); + } } function processSegments(segments, visitor) { - for (var i = 0, ii = segments.length; i < ii; i++) + for (var i = 0, ii = segments.length; i < ii; i++) { processSegment(segments[i], visitor); + } } function parseJbig2(data, start, end) { @@ -38311,8 +38486,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { if (data[position] != 0x97 || data[position + 1] != 0x4A || data[position + 2] != 0x42 || data[position + 3] != 0x32 || data[position + 4] != 0x0D || data[position + 5] != 0x0A || - data[position + 6] != 0x1A || data[position + 7] != 0x0A) + data[position + 6] != 0x1A || data[position + 7] != 0x0A) { error('JBIG2 error: invalid header'); + } var header = {}; position += 8; var flags = data[position++]; @@ -38409,17 +38585,20 @@ var Jbig2Image = (function Jbig2ImageClosure() { referredSegments, data, start, end) { var huffmanTables; - if (dictionary.huffman) + if (dictionary.huffman) { error('JBIG2 error: huffman is not supported'); + } // Combines exported symbols from all referred segments var symbols = this.symbols; - if (!symbols) + if (!symbols) { this.symbols = symbols = {}; + } var inputSymbols = []; - for (var i = 0, ii = referredSegments.length; i < ii; i++) + for (var i = 0, ii = referredSegments.length; i < ii; i++) { inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]); + } var decodingContext = new DecodingContext(data, start, end); symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman, @@ -38439,8 +38618,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { // Combines exported symbols from all referred segments var symbols = this.symbols; var inputSymbols = []; - for (var i = 0, ii = referredSegments.length; i < ii; i++) + for (var i = 0, ii = referredSegments.length; i < ii; i++) { inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]); + } var symbolCodeLength = log2(inputSymbols.length); var decodingContext = new DecodingContext(data, start, end); @@ -38454,7 +38634,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { }, onImmediateLosslessTextRegion: function SimpleSegmentVisitor_onImmediateLosslessTextRegion() { - this.onImmediateTextRegion.apply(this, arguments); + this.onImmediateTextRegion.apply(this, arguments); } }; @@ -38531,8 +38711,9 @@ var bidi = PDFJS.bidi = (function bidiClosure() { function findUnequal(arr, start, value) { var j; for (var j = start, jj = arr.length; j < jj; ++j) { - if (arr[j] != value) + if (arr[j] != value) { return j; + } } return j; } @@ -38593,17 +38774,17 @@ var bidi = PDFJS.bidi = (function bidiClosure() { function BidiResult(str, isLTR, vertical) { this.str = str; - this.dir = vertical ? 'ttb' : isLTR ? 'ltr' : 'rtl'; + this.dir = (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl')); } function bidi(str, startLevel, vertical) { var isLTR = true; var strLength = str.length; - if (strLength === 0 || vertical) + if (strLength === 0 || vertical) { return new BidiResult(str, isLTR, vertical); + } - // get types, fill arrays - + // Get types and fill arrays var chars = []; var types = []; var numBidi = 0; @@ -38613,25 +38794,25 @@ var bidi = PDFJS.bidi = (function bidiClosure() { var charCode = str.charCodeAt(i); var charType = 'L'; - if (charCode <= 0x00ff) + if (charCode <= 0x00ff) { charType = baseTypes[charCode]; - else if (0x0590 <= charCode && charCode <= 0x05f4) + } else if (0x0590 <= charCode && charCode <= 0x05f4) { charType = 'R'; - else if (0x0600 <= charCode && charCode <= 0x06ff) + } else if (0x0600 <= charCode && charCode <= 0x06ff) { charType = arabicTypes[charCode & 0xff]; - else if (0x0700 <= charCode && charCode <= 0x08AC) + } else if (0x0700 <= charCode && charCode <= 0x08AC) { charType = 'AL'; - - if (charType == 'R' || charType == 'AL' || charType == 'AN') + } + if (charType == 'R' || charType == 'AL' || charType == 'AN') { numBidi++; - + } types[i] = charType; } - // detect the bidi method - // if there are no rtl characters then no bidi needed - // if less than 30% chars are rtl then string is primarily ltr - // if more than 30% chars are rtl then string is primarily rtl + // Detect the bidi method + // - If there are no rtl characters then no bidi needed + // - If less than 30% chars are rtl then string is primarily ltr + // - If more than 30% chars are rtl then string is primarily rtl if (numBidi === 0) { isLTR = true; return new BidiResult(str, isLTR); @@ -38648,7 +38829,6 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } var levels = []; - for (var i = 0; i < strLength; ++i) { levels[i] = startLevel; } @@ -38656,8 +38836,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { /* X1-X10: skip most of this, since we are NOT doing the embeddings. */ - - var e = isOdd(startLevel) ? 'R' : 'L'; + var e = (isOdd(startLevel) ? 'R' : 'L'); var sor = e; var eor = sor; @@ -38666,13 +38845,13 @@ var bidi = PDFJS.bidi = (function bidiClosure() { type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor. */ - var lastType = sor; for (var i = 0; i < strLength; ++i) { - if (types[i] == 'NSM') + if (types[i] == 'NSM') { types[i] = lastType; - else + } else { lastType = types[i]; + } } /* @@ -38680,24 +38859,24 @@ var bidi = PDFJS.bidi = (function bidiClosure() { first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number. */ - var lastType = sor; for (var i = 0; i < strLength; ++i) { var t = types[i]; - if (t == 'EN') + if (t == 'EN') { types[i] = (lastType == 'AL') ? 'AN' : 'EN'; - else if (t == 'R' || t == 'L' || t == 'AL') + } else if (t == 'R' || t == 'L' || t == 'AL') { lastType = t; + } } /* W3. Change all ALs to R. */ - for (var i = 0; i < strLength; ++i) { var t = types[i]; - if (t == 'AL') + if (t == 'AL') { types[i] = 'R'; + } } /* @@ -38705,32 +38884,34 @@ var bidi = PDFJS.bidi = (function bidiClosure() { European number. A single common separator between two numbers of the same type changes to that type: */ - for (var i = 1; i < strLength - 1; ++i) { - if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN') + if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN') { types[i] = 'EN'; + } if (types[i] == 'CS' && (types[i - 1] == 'EN' || types[i - 1] == 'AN') && - types[i + 1] == types[i - 1]) + types[i + 1] == types[i - 1]) { types[i] = types[i - 1]; + } } /* W5. A sequence of European terminators adjacent to European numbers changes to all European numbers: */ - for (var i = 0; i < strLength; ++i) { if (types[i] == 'EN') { // do before for (var j = i - 1; j >= 0; --j) { - if (types[j] != 'ET') + if (types[j] != 'ET') { break; + } types[j] = 'EN'; } // do after for (var j = i + 1; j < strLength; --j) { - if (types[j] != 'ET') + if (types[j] != 'ET') { break; + } types[j] = 'EN'; } } @@ -38739,11 +38920,11 @@ var bidi = PDFJS.bidi = (function bidiClosure() { /* W6. Otherwise, separators and terminators change to Other Neutral: */ - for (var i = 0; i < strLength; ++i) { var t = types[i]; - if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS') + if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS') { types[i] = 'ON'; + } } /* @@ -38751,14 +38932,14 @@ var bidi = PDFJS.bidi = (function bidiClosure() { first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L. */ - var lastType = sor; for (var i = 0; i < strLength; ++i) { var t = types[i]; - if (t == 'EN') - types[i] = (lastType == 'L') ? 'L' : 'EN'; - else if (t == 'R' || t == 'L') + if (t == 'EN') { + types[i] = ((lastType == 'L') ? 'L' : 'EN'); + } else if (t == 'R' || t == 'L') { lastType = t; + } } /* @@ -38767,22 +38948,27 @@ var bidi = PDFJS.bidi = (function bidiClosure() { numbers are treated as though they were R. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries. */ - for (var i = 0; i < strLength; ++i) { if (types[i] == 'ON') { var end = findUnequal(types, i + 1, 'ON'); var before = sor; - if (i > 0) + if (i > 0) { before = types[i - 1]; + } + var after = eor; - if (end + 1 < strLength) + if (end + 1 < strLength) { after = types[end + 1]; - if (before != 'L') + } + if (before != 'L') { before = 'R'; - if (after != 'L') + } + if (after != 'L') { after = 'R'; - if (before == after) + } + if (before == after) { setValues(types, i, end, before); + } i = end - 1; // reset to end (-1 so next iteration is ok) } } @@ -38790,10 +38976,10 @@ var bidi = PDFJS.bidi = (function bidiClosure() { /* N2. Any remaining neutrals take the embedding direction. */ - for (var i = 0; i < strLength; ++i) { - if (types[i] == 'ON') + if (types[i] == 'ON') { types[i] = e; + } } /* @@ -38803,7 +38989,6 @@ var bidi = PDFJS.bidi = (function bidiClosure() { I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level. */ - for (var i = 0; i < strLength; ++i) { var t = types[i]; if (isEven(levels[i])) { @@ -38812,7 +38997,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } else if (t == 'AN' || t == 'EN') { levels[i] += 2; } - } else { // isOdd, so + } else { // isOdd if (t == 'L' || t == 'AN' || t == 'EN') { levels[i] += 1; } @@ -38839,19 +39024,19 @@ var bidi = PDFJS.bidi = (function bidiClosure() { */ // find highest level & lowest odd level - var highestLevel = -1; var lowestOddLevel = 99; for (var i = 0, ii = levels.length; i < ii; ++i) { var level = levels[i]; - if (highestLevel < level) + if (highestLevel < level) { highestLevel = level; - if (lowestOddLevel > level && isOdd(level)) + } + if (lowestOddLevel > level && isOdd(level)) { lowestOddLevel = level; + } } // now reverse between those limits - for (var level = highestLevel; level >= lowestOddLevel; --level) { // find segments to reverse var start = -1; @@ -38888,14 +39073,13 @@ var bidi = PDFJS.bidi = (function bidiClosure() { // don't mirror as characters are already mirrored in the pdf // Finally, return string - var result = ''; for (var i = 0, ii = chars.length; i < ii; ++i) { var ch = chars[i]; - if (ch != '<' && ch != '>') + if (ch != '<' && ch != '>') { result += ch; + } } - return new BidiResult(result, isLTR); } diff --git a/browser/extensions/pdfjs/content/web/debugger.js b/browser/extensions/pdfjs/content/web/debugger.js index a70ad0cd94a..a25136fe95b 100644 --- a/browser/extensions/pdfjs/content/web/debugger.js +++ b/browser/extensions/pdfjs/content/web/debugger.js @@ -46,13 +46,17 @@ var FontInspector = (function FontInspectorClosure() { } } function textLayerClick(e) { - if (!e.target.dataset.fontName || e.target.tagName.toUpperCase() !== 'DIV') + if (!e.target.dataset.fontName || + e.target.tagName.toUpperCase() !== 'DIV') { return; + } var fontName = e.target.dataset.fontName; var selects = document.getElementsByTagName('input'); for (var i = 0; i < selects.length; ++i) { var select = selects[i]; - if (select.dataset.fontName != fontName) continue; + if (select.dataset.fontName != fontName) { + continue; + } select.checked = !select.checked; selectFont(fontName, select.checked); select.scrollIntoView(); @@ -140,8 +144,9 @@ var FontInspector = (function FontInspectorClosure() { // Somewhat of a hack, should probably add a hook for when the text layer // is done rendering. setTimeout(function() { - if (this.active) + if (this.active) { resetSelection(); + } }.bind(this), 2000); } }; @@ -172,8 +177,9 @@ var StepperManager = (function StepperManagerClosure() { stepperDiv = document.createElement('div'); this.panel.appendChild(stepperControls); this.panel.appendChild(stepperDiv); - if (sessionStorage.getItem('pdfjsBreakPoints')) + if (sessionStorage.getItem('pdfjsBreakPoints')) { breakPoints = JSON.parse(sessionStorage.getItem('pdfjsBreakPoints')); + } }, enabled: false, active: false, @@ -191,19 +197,22 @@ var StepperManager = (function StepperManagerClosure() { var initBreakPoints = breakPoints[pageIndex] || []; var stepper = new Stepper(debug, pageIndex, initBreakPoints); steppers.push(stepper); - if (steppers.length === 1) + if (steppers.length === 1) { this.selectStepper(pageIndex, false); + } return stepper; }, selectStepper: function selectStepper(pageIndex, selectPanel) { - if (selectPanel) + if (selectPanel) { this.manager.selectPanel(1); + } for (var i = 0; i < steppers.length; ++i) { var stepper = steppers[i]; - if (stepper.pageIndex == pageIndex) + if (stepper.pageIndex == pageIndex) { stepper.panel.removeAttribute('hidden'); - else + } else { stepper.panel.setAttribute('hidden', true); + } } var options = stepperChooser.options; for (var i = 0; i < options.length; ++i) { @@ -223,8 +232,9 @@ var Stepper = (function StepperClosure() { // Shorter way to create element and optionally set textContent. function c(tag, textContent) { var d = document.createElement(tag); - if (textContent) + if (textContent) { d.textContent = textContent; + } return d; } @@ -297,10 +307,11 @@ var Stepper = (function StepperClosure() { cbox.checked = checked; cbox.onclick = (function(x) { return function() { - if (this.checked) + if (this.checked) { self.breakPoints.push(x); - else + } else { self.breakPoints.splice(self.breakPoints.indexOf(x), 1); + } StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints); }; })(i); @@ -336,8 +347,9 @@ var Stepper = (function StepperClosure() { getNextBreakPoint: function getNextBreakPoint() { this.breakPoints.sort(function(a, b) { return a - b; }); for (var i = 0; i < this.breakPoints.length; i++) { - if (this.breakPoints[i] > this.currentIdx) + if (this.breakPoints[i] > this.currentIdx) { return this.breakPoints[i]; + } } return null; }, @@ -385,13 +397,16 @@ var Stepper = (function StepperClosure() { var Stats = (function Stats() { var stats = []; function clear(node) { - while (node.hasChildNodes()) + while (node.hasChildNodes()) { node.removeChild(node.lastChild); + } } function getStatIndex(pageNumber) { - for (var i = 0, ii = stats.length; i < ii; ++i) - if (stats[i].pageNumber === pageNumber) + for (var i = 0, ii = stats.length; i < ii; ++i) { + if (stats[i].pageNumber === pageNumber) { return i; + } + } return false; } return { @@ -408,8 +423,9 @@ var Stats = (function Stats() { active: false, // Stats specific functions. add: function(pageNumber, stat) { - if (!stat) + if (!stat) { return; + } var statsIndex = getStatIndex(pageNumber); if (statsIndex !== false) { var b = stats[statsIndex]; @@ -428,8 +444,9 @@ var Stats = (function Stats() { stats.push({ pageNumber: pageNumber, div: wrapper }); stats.sort(function(a, b) { return a.pageNumber - b.pageNumber; }); clear(this.panel); - for (var i = 0, ii = stats.length; i < ii; ++i) + for (var i = 0, ii = stats.length; i < ii; ++i) { this.panel.appendChild(stats[i].div); + } } }; })(); @@ -448,12 +465,14 @@ var PDFBug = (function PDFBugClosure() { ], enable: function(ids) { var all = false, tools = this.tools; - if (ids.length === 1 && ids[0] === 'all') + if (ids.length === 1 && ids[0] === 'all') { all = true; + } for (var i = 0; i < tools.length; ++i) { var tool = tools[i]; - if (all || ids.indexOf(tool.id) !== -1) + if (all || ids.indexOf(tool.id) !== -1) { tool.enabled = true; + } } if (!all) { // Sort the tools by the order they are enabled. @@ -509,19 +528,21 @@ var PDFBug = (function PDFBugClosure() { panels.appendChild(panel); tool.panel = panel; tool.manager = this; - if (tool.enabled) + if (tool.enabled) { tool.init(); - else + } else { panel.textContent = tool.name + ' is disabled. To enable add ' + ' "' + tool.id + '" to the pdfBug parameter ' + 'and refresh (seperate multiple by commas).'; + } buttons.push(panelButton); } this.selectPanel(0); }, selectPanel: function selectPanel(index) { - if (index === activePanel) + if (index === activePanel) { return; + } activePanel = index; var tools = this.tools; for (var j = 0; j < tools.length; ++j) { diff --git a/browser/extensions/pdfjs/content/web/images/annotation-noicon.svg b/browser/extensions/pdfjs/content/web/images/annotation-noicon.svg new file mode 100644 index 00000000000..c07d1080832 --- /dev/null +++ b/browser/extensions/pdfjs/content/web/images/annotation-noicon.svg @@ -0,0 +1,7 @@ + + + diff --git a/browser/extensions/pdfjs/content/web/viewer.css b/browser/extensions/pdfjs/content/web/viewer.css index 6865b132a58..832ea67e5e4 100644 --- a/browser/extensions/pdfjs/content/web/viewer.css +++ b/browser/extensions/pdfjs/content/web/viewer.css @@ -1157,14 +1157,7 @@ canvas { background-color: white; } -.page > a, -.annotationLayer > a { - display: block; - position: absolute; -} - -.page > a:hover, -.annotationLayer > a:hover { +.annotLink > a:hover { opacity: 0.2; background: #ff0; box-shadow: 0px 2px 10px #ff0; @@ -1229,29 +1222,49 @@ canvas { ::selection { background:rgba(0,0,255,0.3); } ::-moz-selection { background:rgba(0,0,255,0.3); } -.annotText > div { - z-index: 200; +.annotationHighlight { position: absolute; - padding: 0.6em; - max-width: 20em; - background-color: #FFFF99; - box-shadow: 0px 2px 10px #333; - border-radius: 7px; + border: 2px #FFFF99 solid; } .annotText > img { position: absolute; - opacity: 0.6; + cursor: pointer; } -.annotText > img:hover { - opacity: 1; +.annotTextContentWrapper { + position: absolute; + width: 20em; } -.annotText > div > h1 { - font-size: 1.2em; +.annotTextContent { + z-index: 200; + float: left; + max-width: 20em; + background-color: #FFFF99; + box-shadow: 0px 2px 5px #333; + border-radius: 2px; + padding: 0.6em; + cursor: pointer; +} + +.annotTextContent > h1 { + font-size: 1em; border-bottom: 1px solid #000000; - margin: 0px; + padding-bottom: 0.2em; +} + +.annotTextContent > p { + padding-top: 0.2em; +} + +.annotLink > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; } #errorWrapper { diff --git a/browser/extensions/pdfjs/content/web/viewer.js b/browser/extensions/pdfjs/content/web/viewer.js index 6135b5abf56..74148ba582a 100644 --- a/browser/extensions/pdfjs/content/web/viewer.js +++ b/browser/extensions/pdfjs/content/web/viewer.js @@ -68,10 +68,9 @@ var CustomStyle = (function CustomStyleClosure() { // in some versions of IE9 it is critical that ms appear in this list // before Moz var prefixes = ['ms', 'Moz', 'Webkit', 'O']; - var _cache = { }; + var _cache = {}; - function CustomStyle() { - } + function CustomStyle() {} CustomStyle.getProp = function get(propName, element) { // check cache only when no element is given @@ -104,8 +103,9 @@ var CustomStyle = (function CustomStyleClosure() { CustomStyle.setProp = function set(propName, element, str) { var prop = this.getProp(propName); - if (prop != 'undefined') + if (prop != 'undefined') { element.style[prop] = str; + } }; return CustomStyle; @@ -291,11 +291,13 @@ var Cache = function cacheCache(size) { var data = []; this.push = function cachePush(view) { var i = data.indexOf(view); - if (i >= 0) + if (i >= 0) { data.splice(i); + } data.push(view); - if (data.length > size) + if (data.length > size) { data.shift().destroy(); + } }; }; @@ -315,12 +317,13 @@ var Preferences = (function PreferencesClosure() { function Preferences() { this.prefs = {}; this.isInitializedPromiseResolved = false; - this.initializedPromise = this.readFromStorage().then(function(prefObj) { - this.isInitializedPromiseResolved = true; - if (prefObj) { - this.prefs = prefObj; - } - }.bind(this)); + this.initializedPromise = this.readFromStorage(DEFAULT_PREFERENCES).then( + function(prefObj) { + this.isInitializedPromiseResolved = true; + if (prefObj) { + this.prefs = prefObj; + } + }.bind(this)); } Preferences.prototype = { @@ -328,7 +331,7 @@ var Preferences = (function PreferencesClosure() { return; }, - readFromStorage: function Preferences_readFromStorage() { + readFromStorage: function Preferences_readFromStorage(prefObj) { var readFromStoragePromise = Promise.resolve(); return readFromStoragePromise; }, @@ -336,7 +339,7 @@ var Preferences = (function PreferencesClosure() { reset: function Preferences_reset() { if (this.isInitializedPromiseResolved) { this.prefs = {}; - this.writeToStorage(this.prefs); + this.writeToStorage(DEFAULT_PREFERENCES); } }, @@ -361,6 +364,12 @@ var Preferences = (function PreferencesClosure() { valueType + '\", expected a \"' + defaultType + '\".'); return; } + } else { + if (valueType === 'number' && (value | 0) !== value) { + console.error('Preferences_set: \'' + value + + '\' must be an \"integer\".'); + return; + } } this.prefs[name] = value; this.writeToStorage(this.prefs); @@ -427,8 +436,8 @@ var FirefoxCom = (function FirefoxComClosure() { var request = document.createTextNode(''); if (callback) { document.addEventListener('pdf.js.response', function listener(event) { - var node = event.target, - response = event.detail.response; + var node = event.target; + var response = event.detail.response; document.documentElement.removeChild(node); @@ -483,9 +492,10 @@ Preferences.prototype.writeToStorage = function(prefObj) { FirefoxCom.requestSync('setPreferences', prefObj); }; -Preferences.prototype.readFromStorage = function() { +Preferences.prototype.readFromStorage = function(prefObj) { var readFromStoragePromise = new Promise(function (resolve) { - var readPrefs = JSON.parse(FirefoxCom.requestSync('getPreferences')); + var readPrefs = JSON.parse(FirefoxCom.requestSync('getPreferences', + prefObj)); resolve(readPrefs); }); return readFromStoragePromise; @@ -576,8 +586,6 @@ var ViewHistory = (function ViewHistoryClosure() { })(); -/* globals PDFFindController, FindStates, mozL10n */ - /** * Creates a "search bar" given set of DOM elements * that act as controls for searching, or for setting @@ -586,7 +594,6 @@ var ViewHistory = (function ViewHistoryClosure() { * searching is done by PDFFindController */ var PDFFindBar = { - opened: false, bar: null, toggleButton: null, @@ -600,7 +607,7 @@ var PDFFindBar = { initialize: function(options) { if(typeof PDFFindController === 'undefined' || PDFFindController === null) { - throw 'PDFFindBar cannot be initialized ' + + throw 'PDFFindBar cannot be initialized ' + 'without a PDFFindController instance.'; } @@ -715,8 +722,9 @@ var PDFFindBar = { }, close: function() { - if (!this.opened) return; - + if (!this.opened) { + return; + } this.opened = false; this.toggleButton.classList.remove('toggled'); this.bar.classList.add('hidden'); @@ -735,8 +743,6 @@ var PDFFindBar = { -/* globals PDFFindBar, PDFJS, FindStates, FirefoxCom, Promise */ - /** * Provides a "search" or "find" functionality for the PDF. * This object actually performs the search for a given string. @@ -876,8 +882,9 @@ var PDFFindController = { self.pageContents.push(str); extractTextPromisesResolves[pageIndex](pageIndex); - if ((pageIndex + 1) < self.pdfPageSource.pages.length) + if ((pageIndex + 1) < self.pdfPageSource.pages.length) { extractPageText(pageIndex + 1); + } } ); } @@ -2432,10 +2439,11 @@ var PDFView = { viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) { var currentY = viewAreaElement.scrollTop; var lastY = state.lastY; - if (currentY > lastY) + if (currentY > lastY) { state.down = true; - else if (currentY < lastY) + } else if (currentY < lastY) { state.down = false; + } // else do nothing and use previous value state.lastY = currentY; callback(); @@ -2688,8 +2696,9 @@ var PDFView = { } var args = e.data; - if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) + if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) { return; + } switch (args.pdfjsLoadAction) { case 'supportsRangedLoading': PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, { @@ -2884,8 +2893,9 @@ var PDFView = { }, getDestinationHash: function pdfViewGetDestinationHash(dest) { - if (typeof dest === 'string') + if (typeof dest === 'string') { return PDFView.getAnchorUrl('#' + escape(dest)); + } if (dest instanceof Array) { var destRef = dest[0]; // see navigateTo method for dest format var pageNumber = destRef instanceof Object ? @@ -3003,15 +3013,18 @@ var PDFView = { var thumbsView = document.getElementById('thumbnailView'); thumbsView.parentNode.scrollTop = 0; - while (thumbsView.hasChildNodes()) + while (thumbsView.hasChildNodes()) { thumbsView.removeChild(thumbsView.lastChild); + } - if ('_loadingInterval' in thumbsView) + if ('_loadingInterval' in thumbsView) { clearInterval(thumbsView._loadingInterval); + } var container = document.getElementById('viewer'); - while (container.hasChildNodes()) + while (container.hasChildNodes()) { container.removeChild(container.lastChild); + } var pagesCount = pdfDocument.numPages; @@ -3178,16 +3191,17 @@ var PDFView = { (PDFJS.version ? ' (PDF.js: ' + PDFJS.version + ')' : '')); var pdfTitle; - if (metadata) { - if (metadata.has('dc:title')) - pdfTitle = metadata.get('dc:title'); + if (metadata && metadata.has('dc:title')) { + pdfTitle = metadata.get('dc:title'); } - if (!pdfTitle && info && info['Title']) + if (!pdfTitle && info && info['Title']) { pdfTitle = info['Title']; + } - if (pdfTitle) + if (pdfTitle) { self.setTitle(pdfTitle + ' - ' + document.title); + } if (info.IsAcroFormPresent) { console.warn('Warning: AcroForm/XFA is not supported'); @@ -3313,21 +3327,24 @@ var PDFView = { } for (var i = 0; i < numVisible; ++i) { var view = visibleViews[i].view; - if (!this.isViewFinished(view)) + if (!this.isViewFinished(view)) { return view; + } } // All the visible views have rendered, try to render next/previous pages. if (scrolledDown) { var nextPageIndex = visible.last.id; // ID's start at 1 so no need to add 1. - if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) + if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) { return views[nextPageIndex]; + } } else { var previousPageIndex = visible.first.id - 2; if (views[previousPageIndex] && - !this.isViewFinished(views[previousPageIndex])) + !this.isViewFinished(views[previousPageIndex])) { return views[previousPageIndex]; + } } // Everything that needs to be rendered has been. return false; @@ -3360,8 +3377,9 @@ var PDFView = { }, setHash: function pdfViewSetHash(hash) { - if (!hash) + if (!hash) { return; + } if (hash.indexOf('=') >= 0) { var params = PDFView.parseQueryString(hash); @@ -3449,8 +3467,9 @@ var PDFView = { thumbsView.classList.add('hidden'); outlineView.classList.remove('hidden'); - if (outlineButton.getAttribute('disabled')) + if (outlineButton.getAttribute('disabled')) { return; + } break; } }, @@ -3567,8 +3586,9 @@ var PDFView = { afterPrint: function pdfViewSetupAfterPrint() { var div = document.getElementById('printContainer'); - while (div.hasChildNodes()) + while (div.hasChildNodes()) { div.removeChild(div.lastChild); + } }, rotatePages: function pdfViewRotatePages(delta) { @@ -3610,14 +3630,16 @@ var PDFView = { // In case one page has already been flipped there is a cooldown time // which has to expire before next page can be scrolled on to. if (currentTime > storedTime && - currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) + currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) { return; + } // In case the user decides to scroll to the opposite direction than before // clear the accumulated delta. if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) || - (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) + (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) { this.clearMouseScrollState(); + } this.mouseScrollDelta += mouseScrollDelta; @@ -3640,8 +3662,9 @@ var PDFView = { // to do anything. if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) || (currentPage == this.pages.length && - pageFlipDirection == PageFlipDirection.DOWN)) + pageFlipDirection == PageFlipDirection.DOWN)) { return; + } this.page += pageFlipDirection; this.mouseScrollTimeStamp = currentTime; @@ -3946,10 +3969,13 @@ var PageView = function pageView(container, id, scale, CustomStyle.setProp('transformOrigin', element, transformOriginStr); if (data.subtype === 'Link' && !data.url) { - if (data.action) { - bindNamedAction(element, data.action); - } else { - bindLink(element, ('dest' in data) ? data.dest : null); + var link = element.getElementsByTagName('a')[0]; + if (link) { + if (data.action) { + bindNamedAction(link, data.action); + } else { + bindLink(link, ('dest' in data) ? data.dest : null); + } } } @@ -4215,6 +4241,7 @@ var PageView = function pageView(container, id, scale, canvasContext: ctx, viewport: this.viewport, textLayer: textLayer, + // intent: 'default', // === 'display' continueCallback: function pdfViewcContinueCallback(cont) { if (PDFView.highestPriorityPage !== 'page' + self.id) { self.renderingState = RenderingStates.PAUSED; @@ -4286,13 +4313,13 @@ var PageView = function pageView(container, id, scale, var renderContext = { canvasContext: ctx, - viewport: viewport + viewport: viewport, + intent: 'print' }; pdfPage.render(renderContext).promise.then(function() { // Tell the printEngine that rendering this canvas/page has finished. obj.done(); - self.pdfPage.destroy(); }, function(error) { console.error(error); // Tell the printEngine that rendering this canvas/page has failed. @@ -4302,7 +4329,6 @@ var PageView = function pageView(container, id, scale, } else { obj.done(); } - self.pdfPage.destroy(); }); }; }; @@ -4522,12 +4548,12 @@ var TextLayerBuilder = function textLayerBuilder(options) { this.viewport = options.viewport; this.isViewerInPresentationMode = options.isViewerInPresentationMode; - if(typeof PDFFindController === 'undefined') { - window.PDFFindController = null; + if (typeof PDFFindController === 'undefined') { + window.PDFFindController = null; } - if(typeof this.lastScrollSource === 'undefined') { - this.lastScrollSource = null; + if (typeof this.lastScrollSource === 'undefined') { + this.lastScrollSource = null; } this.beginLayout = function textLayerBuilderBeginLayout() { @@ -4548,8 +4574,9 @@ var TextLayerBuilder = function textLayerBuilder(options) { // No point in rendering so many divs as it'd make the browser unusable // even after the divs are rendered var MAX_TEXT_DIVS_TO_RENDER = 100000; - if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) + if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) { return; + } for (var i = 0, ii = textDivs.length; i < ii; i++) { var textDiv = textDivs[i]; @@ -4581,16 +4608,17 @@ var TextLayerBuilder = function textLayerBuilder(options) { // run it right away var RENDER_DELAY = 200; // in ms var self = this; - var lastScroll = this.lastScrollSource === null ? - 0 : this.lastScrollSource.lastScroll; + var lastScroll = (this.lastScrollSource === null ? + 0 : this.lastScrollSource.lastScroll); if (Date.now() - lastScroll > RENDER_DELAY) { // Render right away this.renderLayer(); } else { // Schedule - if (this.renderTimer) + if (this.renderTimer) { clearTimeout(this.renderTimer); + } this.renderTimer = setTimeout(function() { self.setupRenderLayoutTimer(); }, RENDER_DELAY); @@ -4608,8 +4636,8 @@ var TextLayerBuilder = function textLayerBuilder(options) { textDiv.style.fontSize = fontHeight + 'px'; textDiv.style.fontFamily = geom.fontFamily; - var fontAscent = geom.ascent ? geom.ascent * fontHeight : - geom.descent ? (1 + geom.descent) * fontHeight : fontHeight; + var fontAscent = (geom.ascent ? geom.ascent * fontHeight : + (geom.descent ? (1 + geom.descent) * fontHeight : fontHeight)); textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px'; textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px'; @@ -4621,8 +4649,9 @@ var TextLayerBuilder = function textLayerBuilder(options) { this.insertDivContent = function textLayerUpdateTextContent() { // Only set the content of the divs once layout has finished, the content // for the divs is available and content is not yet set on the divs. - if (!this.layoutDone || this.divContentDone || !this.textContent) + if (!this.layoutDone || this.divContentDone || !this.textContent) { return; + } this.divContentDone = true; @@ -4661,8 +4690,8 @@ var TextLayerBuilder = function textLayerBuilder(options) { var iIndex = 0; var bidiTexts = this.textContent; var end = bidiTexts.length - 1; - var queryLen = PDFFindController === null ? - 0 : PDFFindController.state.query.length; + var queryLen = (PDFFindController === null ? + 0 : PDFFindController.state.query.length); var lastDivIdx = -1; var pos; @@ -4721,14 +4750,14 @@ var TextLayerBuilder = function textLayerBuilder(options) { var bidiTexts = this.textContent; var textDivs = this.textDivs; var prevEnd = null; - var isSelectedPage = PDFFindController === null ? - false : (this.pageIdx === PDFFindController.selected.pageIdx); + var isSelectedPage = (PDFFindController === null ? + false : (this.pageIdx === PDFFindController.selected.pageIdx)); - var selectedMatchIdx = PDFFindController === null ? - -1 : PDFFindController.selected.matchIdx; + var selectedMatchIdx = (PDFFindController === null ? + -1 : PDFFindController.selected.matchIdx); - var highlightAll = PDFFindController === null ? - false : PDFFindController.state.highlightAll; + var highlightAll = (PDFFindController === null ? + false : PDFFindController.state.highlightAll); var infty = { divIdx: -1, @@ -4827,8 +4856,9 @@ var TextLayerBuilder = function textLayerBuilder(options) { this.updateMatches = function textLayerUpdateMatches() { // Only show matches, once all rendering is done. - if (!this.renderingDone) + if (!this.renderingDone) { return; + } // Clear out all matches. var matches = this.matches; @@ -4848,14 +4878,14 @@ var TextLayerBuilder = function textLayerBuilder(options) { clearedUntilDivIdx = match.end.divIdx + 1; } - if (PDFFindController === null || !PDFFindController.active) + if (PDFFindController === null || !PDFFindController.active) { return; + } // Convert the matches on the page controller into the match format used // for the textLayer. - this.matches = matches = - this.convertMatches(PDFFindController === null ? - [] : (PDFFindController.pageMatches[this.pageIdx] || [])); + this.matches = matches = (this.convertMatches(PDFFindController === null ? + [] : (PDFFindController.pageMatches[this.pageIdx] || []))); this.renderMatches(this.matches); }; @@ -4866,13 +4896,14 @@ var TextLayerBuilder = function textLayerBuilder(options) { var DocumentOutlineView = function documentOutlineView(outline) { var outlineView = document.getElementById('outlineView'); var outlineButton = document.getElementById('viewOutline'); - while (outlineView.firstChild) + while (outlineView.firstChild) { outlineView.removeChild(outlineView.firstChild); + } if (!outline) { - if (!outlineView.classList.contains('hidden')) + if (!outlineView.classList.contains('hidden')) { PDFView.switchSidebarView('thumbs'); - + } return; } @@ -5101,8 +5132,9 @@ document.addEventListener('DOMContentLoaded', webViewerLoad, true); function updateViewarea() { - if (!PDFView.initialized) + if (!PDFView.initialized) { return; + } var visible = PDFView.getVisiblePages(); var visiblePages = visible.views; if (visiblePages.length === 0) { @@ -5118,9 +5150,9 @@ function updateViewarea() { i < ii; ++i) { var page = visiblePages[i]; - if (page.percent < 100) + if (page.percent < 100) { break; - + } if (page.id === PDFView.page) { stillFullyVisible = true; break; @@ -5473,23 +5505,6 @@ window.addEventListener('keydown', function keydown(evt) { PDFView.rotatePages(90); break; } - if (!handled && !PresentationMode.active) { - // 33=Page Up 34=Page Down 35=End 36=Home - // 37=Left 38=Up 39=Right 40=Down - if (evt.keyCode >= 33 && evt.keyCode <= 40 && - !PDFView.container.contains(curElement)) { - // The page container is not focused, but a page navigation key has been - // pressed. Change the focus to the viewer container to make sure that - // navigation by keyboard works as expected. - PDFView.container.focus(); - } - // 32=Spacebar - if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') { - // Workaround for issue in Firefox, that prevents scroll keys from working - // when elements with 'tabindex' are focused. (#3499) - PDFView.container.blur(); - } - } } if (cmd === 4) { // shift-key @@ -5509,6 +5524,24 @@ window.addEventListener('keydown', function keydown(evt) { } } + if (!handled && !PresentationMode.active) { + // 33=Page Up 34=Page Down 35=End 36=Home + // 37=Left 38=Up 39=Right 40=Down + if (evt.keyCode >= 33 && evt.keyCode <= 40 && + !PDFView.container.contains(curElement)) { + // The page container is not focused, but a page navigation key has been + // pressed. Change the focus to the viewer container to make sure that + // navigation by keyboard works as expected. + PDFView.container.focus(); + } + // 32=Spacebar + if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') { + // Workaround for issue in Firefox, that prevents scroll keys from + // working when elements with 'tabindex' are focused. (#3498) + PDFView.container.blur(); + } + } + if (cmd === 2) { // alt-key switch (evt.keyCode) { case 37: // left arrow diff --git a/config/milestone.txt b/config/milestone.txt index 6334341896b..5420350e072 100644 --- a/config/milestone.txt +++ b/config/milestone.txt @@ -10,4 +10,4 @@ # hardcoded milestones in the tree from these two files. #-------------------------------------------------------- -30.0a1 +31.0a1 diff --git a/content/media/MediaStreamGraph.cpp b/content/media/MediaStreamGraph.cpp index 240c3d4f7fe..a494b8522f8 100644 --- a/content/media/MediaStreamGraph.cpp +++ b/content/media/MediaStreamGraph.cpp @@ -1221,6 +1221,16 @@ MediaStreamGraphImpl::RunThread() } } + // The loop is woken up so soon that mCurrentTime barely advances and we + // end up having endBlockingDecisions == mStateComputedTime. + // Since stream blocking is computed in the interval of + // [mStateComputedTime, endBlockingDecisions), it won't be computed at all. + // We should ensure next iteration so that pending blocking changes will be + // computed in next loop. + if (endBlockingDecisions == mStateComputedTime) { + ensureNextIteration = true; + } + // Figure out which streams are blocked and when. GraphTime prevComputedTime = mStateComputedTime; RecomputeBlocking(endBlockingDecisions); diff --git a/content/media/test/mochitest.ini b/content/media/test/mochitest.ini index df301b9434d..29e500fb8f4 100644 --- a/content/media/test/mochitest.ini +++ b/content/media/test/mochitest.ini @@ -380,7 +380,6 @@ skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(bug 90 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(timed out in small-shot.mp3) b2g-desktop(timed out in small-shot.mp3) [test_readyState.html] [test_referer.html] -skip-if = buildapp == 'b2g' [test_reset_events_async.html] [test_replay_metadata.html] skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) diff --git a/content/media/test/test_preload_actions.html b/content/media/test/test_preload_actions.html index b2797329202..015fe32a0b4 100644 --- a/content/media/test/test_preload_actions.html +++ b/content/media/test/test_preload_actions.html @@ -74,7 +74,7 @@ var tests = [ // is(v.networkState, v.NETWORK_IDLE, "(1) NetworkState must be NETWORK_IDLE"); maybeFinish(v, 1); }, - + setup: function(v) { v._gotLoadStart = false; @@ -86,6 +86,8 @@ var tests = [ v.src = test.name; document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none. }, + + name: "test1", }, { // 2. Add preload:metadata video with src to document. Should halt with NETWORK_IDLE, HAVE_CURRENT_DATA @@ -100,7 +102,7 @@ var tests = [ // is(v.networkState, v.NETWORK_IDLE, "(2) NetworkState must be NETWORK_IDLE"); maybeFinish(v, 2); }, - + setup: function(v) { v._gotLoadStart = false; @@ -113,6 +115,8 @@ var tests = [ document.body.appendChild(v); // Causes implicit load, which will be halted after // metadata due to preload:metadata. }, + + name: "test2", }, { // 3. Add preload:auto to document. Should receive canplaythrough eventually. @@ -123,7 +127,7 @@ var tests = [ is(v._gotLoadedMetaData, true, "(3) Must get loadedmetadata."); maybeFinish(v, 3); }, - + setup: function(v) { v._gotLoadStart = false; @@ -135,6 +139,8 @@ var tests = [ v.src = test.name; // Causes implicit load. document.body.appendChild(v); }, + + name: "test3", }, { // 4. Add preload:none video to document. Call play(), should load then play through. @@ -152,13 +158,13 @@ var tests = [ // is(v.networkState, v.NETWORK_IDLE, "(4) NetworkState must be NETWORK_IDLE"); v.play(); // Should load and play through. }, - + ended: function(e) { ok(true, "(4) Got playback ended"); maybeFinish(e.target, 4); }, - + setup: function(v) { v._gotLoadStart = false; @@ -172,6 +178,8 @@ var tests = [ v.src = test.name; document.body.appendChild(v); }, + + name: "test4", }, { // 5. preload:none video without resource, add to document, will implicitly start a @@ -186,7 +194,7 @@ var tests = [ // is(v.networkState, v.NETWORK_IDLE, "(5) NetworkState must be NETWORK_IDLE"); maybeFinish(v, 5); }, - + setup: function(v) { v._gotLoadStart = false; @@ -198,6 +206,8 @@ var tests = [ document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource. v.src = test.name; // Load should start, and halt at preload:none. }, + + name: "test5", }, { // 6. preload:none video without resource, add to document, will implicitly start a @@ -212,7 +222,7 @@ var tests = [ // is(v.networkState, v.NETWORK_IDLE, "(6) NetworkState must be NETWORK_IDLE"); maybeFinish(v, 6); }, - + setup: function(v) { v._gotLoadStart = false; @@ -227,6 +237,8 @@ var tests = [ s.type = test.type; v.appendChild(s); // Load should start, and halt at preload:none. }, + + name: "test6", }, { // 7. create a preload:none document with multiple sources, the first of which is invalid. @@ -252,7 +264,7 @@ var tests = [ is(v._gotErrorEvent, true, "(7) Should get error event from first source load failure"); maybeFinish(v, 7); }, - + setup: function(v) { v._gotLoadStart = false; @@ -274,6 +286,8 @@ var tests = [ v.appendChild(s2); document.body.appendChild(v); // Causes implicit load, which will be halt at preload:none on the second resource. }, + + name: "test7", }, { // 8. Change preload value from none to metadata should cause metadata to be loaded. @@ -286,7 +300,7 @@ var tests = [ // is(v.networkState, v.NETWORK_IDLE, "(8) NetworkState must be NETWORK_IDLE when load is halted"); maybeFinish(v, 8); }, - + setup: function(v) { v._gotLoadedMetaData = false; @@ -297,6 +311,8 @@ var tests = [ v.src = test.name; // Causes implicit load. document.body.appendChild(v); }, + + name: "test8", }, /*{ // 9. Change preload value from metadata to auto should cause entire media to be loaded. @@ -309,7 +325,7 @@ var tests = [ is(v._gotLoadedMetaData, true, "(9) Must get loadedmetadata."); maybeFinish(v, 9); }, - + setup: function(v) { v._gotLoadStart = false; @@ -331,7 +347,7 @@ var tests = [ is(v._gotLoadedMetaData, true, "(10) Must get loadedmetadata."); maybeFinish(v, 10); }, - + setup: function(v) { v._gotLoadedMetaData = false; @@ -342,6 +358,8 @@ var tests = [ v.src = test.name; // Causes implicit load. document.body.appendChild(v); }, + + name: "test10", }, { // 11. Change preload value from none to metadata should cause metadata to load. @@ -365,10 +383,14 @@ var tests = [ v.src = test.name; // Causes implicit load. document.body.appendChild(v); }, + + name: "test11", }, - { + /*{ // 12. Change preload value from auto to metadata after load started, // should still do full load, should not halt after metadata only. + // disable this test since the spec is no longer found in the document + // http://dev.w3.org/html5/spec-preview/media-elements.html canplaythrough: function(e) { var v = e.target; @@ -389,7 +411,9 @@ var tests = [ document.body.appendChild(v); v.preload = "metadata"; }, - }, + + name: "test12", + },*/ { // 13. Change preload value from auto to none after specifying a src // should load according to preload none, no buffering should have taken place @@ -416,7 +440,9 @@ var tests = [ v.addEventListener("suspend", this.suspend, false); document.body.appendChild(v); // Causes implicit load, should load according to preload none var s = document.createElement("source"); - } + }, + + name: "test13", }, { // 14. Add preload:metadata video with src to document. Play(), should play through. @@ -430,7 +456,7 @@ var tests = [ // is(v.networkState, v.NETWORK_IDLE, "(14) NetworkState must be NETWORK_IDLE"); v.play(); }, - + ended: function(e) { ok(true, "(14) Got playback ended"); @@ -451,6 +477,8 @@ var tests = [ document.body.appendChild(v); // Causes implicit load, which will be halted after // metadata due to preload:metadata. }, + + name: "test14", }, { // 15. Autoplay should override preload:none. @@ -460,7 +488,7 @@ var tests = [ var v = e.target; maybeFinish(v, 15); }, - + setup: function(v) { v._gotLoadStart = false; @@ -473,6 +501,8 @@ var tests = [ v.src = test.name; // Causes implicit load. document.body.appendChild(v); }, + + name: "test15", }, { // 16. Autoplay should override preload:metadata. @@ -482,7 +512,7 @@ var tests = [ var v = e.target; maybeFinish(v, 16); }, - + setup: function(v) { v.preload = "metadata"; @@ -491,6 +521,8 @@ var tests = [ v.src = test.name; // Causes implicit load. document.body.appendChild(v); }, + + name: "test16", }, { // 17. On a preload:none video, adding autoplay should disable preload none, i.e. don't break autoplay! @@ -500,7 +532,7 @@ var tests = [ var v = e.target; maybeFinish(v, 17); }, - + setup: function(v) { v.addEventListener("ended", this.ended, false); @@ -508,7 +540,9 @@ var tests = [ document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none. v.autoplay = true; v.src = test.name; - }, + }, + + name: "test17", }, { // 18. On a preload='none' video, call play() before load algorithms's sync @@ -519,7 +553,7 @@ var tests = [ var v = e.target; maybeFinish(v, 18); }, - + setup: function(v) { v.addEventListener("ended", this.ended, false); @@ -527,7 +561,9 @@ var tests = [ v.src = test.name; // Schedules async section to continue load algorithm. document.body.appendChild(v); v.play(); // Should cause preload:none to be overridden. - }, + }, + + name: "test18", }, { // 19. Set preload='auto' on first video source then switching preload='none' and swapping the video source to another. @@ -555,7 +591,9 @@ var tests = [ // add a listener for when the video has loaded, so we know preload auto has worked v.addEventListener( "loadedmetadata", this.loadedmetadata, false); document.body.appendChild(v); - } + }, + + name: "test19", } ]; @@ -563,6 +601,7 @@ var iterationCount = 0; function startTest(test, token) { if (test == tests[0]) { ++iterationCount; + info("iterationCount=" + iterationCount); } if (iterationCount == 2) { // Do this series of tests on logically different resources diff --git a/content/media/test/test_referer.html b/content/media/test/test_referer.html index 0c24f80f3e0..93a98208824 100644 --- a/content/media/test/test_referer.html +++ b/content/media/test/test_referer.html @@ -28,12 +28,20 @@ function checkComplete() { SimpleTest.finish(); } - + +function removeNode(v) { + v.removeEventListener("error", loadError, false); + v.removeEventListener("loadedmetadata", loadedMetadata, false); + v.remove(); + v.src = ""; +} + function loadError(evt) { // If no referer is sent then the sjs returns an error ok(false, "check referer is sent with media request"); evt.target._complete = true; checkComplete(); + removeNode(evt.target); } function loadedMetadata(evt) { @@ -41,6 +49,7 @@ function loadedMetadata(evt) { ok(true, "check referer is sent with media request"); evt.target._complete = true; checkComplete(); + removeNode(evt.target); } // Create all media objects. @@ -56,6 +65,8 @@ for (var i=0; i, + * teleservice: , + * SMSC: , + * sentTimestamp: , + * timestamp: , + * sender: , + * pid: , + * encoding: , + * messageClass: , + * iccId: , + * + * [Concatenation Info] + * segmentRef: , + * segmentSeq: , + * segmentMaxSeq: , + * + * [Application Port Info] + * originatorPort: , + * destinationPort: , + * + * [MWI status] + * mwiPresent: , + * mwiDiscard: , + * mwiMsgCount: , + * mwiActive: , + * + * [CDMA Cellbroadcast related fields] + * serviceCategory: , + * language: , + * + * [Message Body] + * data: , (available if it's 8bit encoding) + * body: , (normal text body) + * + * [Handy fields created by DB for concatenation] + * id: , keypath of this objectStore. + * hash: , Use to identify the segments to the same SMS. + * receivedSegments: , + * segments: [] + * } + * + */ + let smsSegmentStore = db.createObjectStore(SMS_SEGMENT_STORE_NAME, + { keyPath: "id", + autoIncrement: true }); + smsSegmentStore.createIndex("hash", "hash", { unique: true }); + next(); + }, + matchParsedPhoneNumbers: function(addr1, parsedAddr1, addr2, parsedAddr2) { if ((parsedAddr1.internationalNumber && parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) || @@ -2428,6 +2492,128 @@ MobileMessageDB.prototype = { } }, + saveSmsSegment: function(aSmsSegment, aCallback) { + let completeMessage = null; + this.newTxn(READ_WRITE, function(error, txn, segmentStore) { + if (error) { + if (DEBUG) debug(error); + aCallback.notify(error, null); + return; + } + + txn.oncomplete = function oncomplete(event) { + if (DEBUG) debug("Transaction " + txn + " completed."); + if (completeMessage) { + // Rebuild full body + if (completeMessage.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { + // Uint8Array doesn't have `concat`, so + // we have to merge all segements by hand. + let fullDataLen = 0; + for (let i = 1; i <= completeMessage.segmentMaxSeq; i++) { + fullDataLen += completeMessage.segments[i].length; + } + + completeMessage.fullData = new Uint8Array(fullDataLen); + for (let d = 0, i = 1; i <= completeMessage.segmentMaxSeq; i++) { + let data = completeMessage.segments[i]; + for (let j = 0; j < data.length; j++) { + completeMessage.fullData[d++] = data[j]; + } + } + } else { + completeMessage.fullBody = completeMessage.segments.join(""); + } + + // Remove handy fields after completing the concatenation. + delete completeMessage.id; + delete completeMessage.hash; + delete completeMessage.receivedSegments; + delete completeMessage.segments; + } + aCallback.notify(Cr.NS_OK, completeMessage); + }; + + txn.onabort = function onerror(event) { + if (DEBUG) debug("Caught error on transaction", event.target.errorCode); + aCallback.notify(Cr.NS_ERROR_FAILURE, null, null); + }; + + aSmsSegment.hash = aSmsSegment.sender + ":" + + aSmsSegment.segmentRef + ":" + + aSmsSegment.segmentMaxSeq + ":" + + aSmsSegment.iccId; + let seq = aSmsSegment.segmentSeq; + if (DEBUG) { + debug("Saving SMS Segment: " + aSmsSegment.hash + ", seq: " + seq); + } + let getRequest = segmentStore.index("hash").get(aSmsSegment.hash); + getRequest.onsuccess = function(event) { + let segmentRecord = event.target.result; + if (!segmentRecord) { + if (DEBUG) { + debug("Not found! Create a new record to store the segments."); + } + aSmsSegment.receivedSegments = 1; + aSmsSegment.segments = []; + if (aSmsSegment.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { + aSmsSegment.segments[seq] = aSmsSegment.data; + } else { + aSmsSegment.segments[seq] = aSmsSegment.body; + } + + segmentStore.add(aSmsSegment); + + return; + } + + if (DEBUG) { + debug("Append SMS Segment into existed message object: " + segmentRecord.id); + } + + if (segmentRecord.segments[seq]) { + if (DEBUG) debug("Got duplicated segment no. " + seq); + return; + } + + segmentRecord.timestamp = aSmsSegment.timestamp; + + if (segmentRecord.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { + segmentRecord.segments[seq] = aSmsSegment.data; + } else { + segmentRecord.segments[seq] = aSmsSegment.body; + } + segmentRecord.receivedSegments++; + + // The port information is only available in 1st segment for CDMA WAP Push. + // If the segments of a WAP Push are not received in sequence + // (e.g., SMS with seq == 1 is not the 1st segment received by the device), + // we have to retrieve the port information from 1st segment and + // save it into the segmentRecord. + if (aSmsSegment.teleservice === RIL.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP + && seq === 1) { + if (aSmsSegment.originatorPort) { + segmentRecord.originatorPort = aSmsSegment.originatorPort; + } + + if (aSmsSegment.destinationPort) { + segmentRecord.destinationPort = aSmsSegment.destinationPort; + } + } + + if (segmentRecord.receivedSegments < segmentRecord.segmentMaxSeq) { + if (DEBUG) debug("Message is incomplete."); + segmentStore.put(segmentRecord); + return; + } + + completeMessage = segmentRecord; + + // Delete Record in DB + segmentStore.delete(segmentRecord.id); + }; + }, [SMS_SEGMENT_STORE_NAME]); + }, + /** * nsIMobileMessageDatabaseService API */ diff --git a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js index cc7cdabcf19..e6ffe92bbd8 100644 --- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js +++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js @@ -99,6 +99,10 @@ MobileMessageDatabaseService.prototype = { return this.mmdb.translateCrErrorToMessageCallbackError(aCrError); }, + saveSmsSegment: function(aSmsSegment, aCallback) { + this.mmdb.saveSmsSegment(aSmsSegment, aCallback); + }, + /** * nsIMobileMessageDatabaseService API */ diff --git a/dom/mobilemessage/tests/marionette/head.js b/dom/mobilemessage/tests/marionette/head.js index 5b7b72388c8..e44a12bd408 100644 --- a/dom/mobilemessage/tests/marionette/head.js +++ b/dom/mobilemessage/tests/marionette/head.js @@ -45,6 +45,31 @@ function ensureMobileMessage() { return deferred.promise; } +/** + * Wait for one named MobileMessageManager event. + * + * Resolve if that named event occurs. Never reject. + * + * Fulfill params: the DOMEvent passed. + * + * @param aEventName + * A string event name. + * + * @return A deferred promise. + */ +function waitForManagerEvent(aEventName) { + let deferred = Promise.defer(); + + manager.addEventListener(aEventName, function onevent(aEvent) { + manager.removeEventListener(aEventName, onevent); + + ok(true, "MobileMessageManager event '" + aEventName + "' got."); + deferred.resolve(aEvent); + }); + + return deferred.promise; +} + /** * Send a SMS message to a single receiver. Resolve if it succeeds, reject * otherwise. @@ -331,6 +356,9 @@ function sendTextSmsToEmulator(aFrom, aText) { /** * Send raw SMS TPDU to emulator. * + * @param: aPdu + * A hex string representing the whole SMS T-PDU. + * * Fulfill params: * result -- an array of emulator response lines. * diff --git a/dom/mobilemessage/tests/marionette/manifest.ini b/dom/mobilemessage/tests/marionette/manifest.ini index ceef42b21c0..31f6a960889 100644 --- a/dom/mobilemessage/tests/marionette/manifest.ini +++ b/dom/mobilemessage/tests/marionette/manifest.ini @@ -44,3 +44,4 @@ qemu = true [test_mmdb_foreachmatchedmmsdeliveryinfo.js] [test_mmdb_full_storage.js] [test_replace_short_message_type.js] +[test_mt_sms_concatenation.js] diff --git a/dom/mobilemessage/tests/marionette/test_mt_sms_concatenation.js b/dom/mobilemessage/tests/marionette/test_mt_sms_concatenation.js new file mode 100644 index 00000000000..36fa928bb1e --- /dev/null +++ b/dom/mobilemessage/tests/marionette/test_mt_sms_concatenation.js @@ -0,0 +1,138 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const PDU_SMSC_NONE = "00"; // no SMSC Address + +const PDU_FIRST_OCTET = "40"; // RP:no, UDHI:yes, SRI:no, MMS:no, MTI:SMS-DELIVER + +const PDU_SENDER = "0A912143658709"; // +1234567890 +const SENDER = "+1234567890"; + +const PDU_PID_NORMAL = "00"; + +const PDU_DCS_NORMAL_UCS2 = "08"; +const PDU_DCS_CLASS0_UCS2 = "18"; +const PDU_DCS_NORMAL_8BIT = "04"; +const PDU_DCS_CLASS0_8BIT = "14"; + +const PDU_TIMESTAMP = "00101000000000"; // 2000/01/01 + +function byteValueToHexString(aValue) { + let str = Number(aValue).toString(16).toUpperCase(); + return str.length == 1 ? "0" + str : str; +} + +let ref_num = 0; +function buildTextPdus(aDcs) { + ref_num++; + + let IEI_CONCATE_1 = "0003" + byteValueToHexString(ref_num) + "0301"; + let IEI_CONCATE_2 = "0003" + byteValueToHexString(ref_num) + "0302"; + let IEI_CONCATE_3 = "0003" + byteValueToHexString(ref_num) + "0303"; + let PDU_UDL = "08"; // UDHL(1) + UDH(5) + UCS2 Char (2) + let PDU_UDHL = "05"; + + let PDU_UD_A = "0041"; // "A" + let PDU_UD_B = "0042"; // "B" + let PDU_UD_C = "0043"; // "C" + + let PDU_COMMON = PDU_SMSC_NONE + PDU_FIRST_OCTET + PDU_SENDER + + PDU_PID_NORMAL + aDcs + PDU_TIMESTAMP + PDU_UDL + PDU_UDHL; + + return [ + PDU_COMMON + IEI_CONCATE_1 + PDU_UD_A, + PDU_COMMON + IEI_CONCATE_2 + PDU_UD_B, + PDU_COMMON + IEI_CONCATE_3 + PDU_UD_C + ]; +} + +function buildBinaryPdus(aDcs) { + ref_num++; + let IEI_PORT = "05040B8423F0"; + + let PDU_DATA1 = "C106316170706C69636174696F6E2F76" + + "6E642E7761702E6D6D732D6D65737361" + + "676500B131302E382E3133302E313800" + + "AF84B4818C82986B4430595538595347" + + "77464E446741416B4876736C58303141" + + "41414141414141008D90890380310096" + + "05EA4D4D53008A808E02024188058103" + + "015F9083687474703A2F2F6D6D732E65"; + + let PDU_DATA2 = "6D6F6D652E6E65743A383030322F6B44" + + "3059553859534777464E446741416B48" + + "76736C583031414141414141414100"; + + let PDU_COMMON = PDU_SMSC_NONE + PDU_FIRST_OCTET + PDU_SENDER + + PDU_PID_NORMAL + aDcs + PDU_TIMESTAMP; + + function construstBinaryUserData(aBinaryData, aSeqNum) { + let ieiConcat = "0003" + byteValueToHexString(ref_num) + "02" + + byteValueToHexString(aSeqNum); + + let udh = IEI_PORT + ieiConcat; + let udhl = byteValueToHexString(udh.length / 2); + let ud = udhl + udh + aBinaryData; + let udl = byteValueToHexString(ud.length / 2); + + return udl + ud; + } + + return [ + PDU_COMMON + construstBinaryUserData(PDU_DATA1, 1), + PDU_COMMON + construstBinaryUserData(PDU_DATA2, 2) + ]; +} + +function sendRawSmsAndWait(aPdus) { + let promises = []; + + promises.push(waitForManagerEvent("received")); + for (let pdu of aPdus) { + promises.push(sendRawSmsToEmulator(pdu)); + } + + return Promise.all(promises); +} + +function verifyTextMessage(aMessage, aMessageClass) { + is(aMessage.messageClass, aMessageClass, "SmsMessage class"); + is(aMessage.sender, SENDER, "SmsMessage sender"); + is(aMessage.body, "ABC", "SmsMessage body"); +} + +function verifyBinaryMessage(aMessage) { + is(aMessage.type, "mms", "MmsMessage type"); + is(aMessage.delivery, "not-downloaded", "MmsMessage delivery"); + + // remove duplicated M-Notification.ind for next test. + return deleteMessagesById([aMessage.id]); +} + +function testText(aDcs, aClass) { + log("testText(): aDcs = " + aDcs + ", aClass = " + aClass); + return sendRawSmsAndWait(buildTextPdus(aDcs)) + .then((resolutions) => verifyTextMessage(resolutions[0].message, aClass)); +} + +function testBinary(aDcs) { + log("testBinary(): aDcs = " + aDcs); + return sendRawSmsAndWait(buildBinaryPdus(aDcs)) + .then((resolutions) => verifyBinaryMessage(resolutions[0].message)); +} + +SpecialPowers.pushPrefEnv( + {"set": [["dom.mms.retrieval_mode", "manual"]]}, + function startTest() { + startTestCommon(function testCaseMain() { + return Promise.resolve() + .then(() => testText(PDU_DCS_NORMAL_UCS2, "normal")) + .then(() => testText(PDU_DCS_CLASS0_UCS2, "class-0")) + .then(() => testBinary(PDU_DCS_NORMAL_8BIT)) + .then(() => testBinary(PDU_DCS_CLASS0_8BIT)); + }); + } +); diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index e1d69f66d95..1806670584c 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -1905,6 +1905,8 @@ function RadioInterface(aClientId, aWorkerMessenger) { this.portAddressedSmsApps = {}; this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this); + this._receivedSmsSegmentsMap = {}; + this._sntp = new Sntp(this.setClockBySntp.bind(this), Services.prefs.getIntPref("network.sntp.maxRetryCount"), Services.prefs.getIntPref("network.sntp.refreshPeriod"), @@ -2207,13 +2209,8 @@ RadioInterface.prototype = { this.clientId, message); break; case "sms-received": - let ackOk = this.handleSmsReceived(message); - // Note: ACK has been done by modem for NEW_SMS_ON_SIM - if (ackOk && message.simStatus === undefined) { - this.workerMessenger.send("ackSMS", { result: RIL.PDU_FCS_OK }); - } - return; - case "broadcastsms-received": + this.handleSmsMultipart(message); + break; case "cellbroadcast-received": message.timestamp = Date.now(); gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived", @@ -2719,9 +2716,9 @@ RadioInterface.prototype = { let options = { bearer: WAP.WDP_BEARER_GSM_SMS_GSM_MSISDN, sourceAddress: message.sender, - sourcePort: message.header.originatorPort, + sourcePort: message.originatorPort, destinationAddress: this.rilContext.iccInfo.msisdn, - destinationPort: message.header.destinationPort, + destinationPort: message.destinationPort, serviceId: this.clientId }; WAP.WapPushManager.receiveWdpPDU(message.fullData, message.fullData.length, @@ -2767,23 +2764,7 @@ RadioInterface.prototype = { _smsHandledWakeLock: null, _smsHandledWakeLockTimer: null, - _releaseSmsHandledWakeLock: function() { - if (DEBUG) this.debug("Releasing the CPU wake lock for handling SMS."); - if (this._smsHandledWakeLockTimer) { - this._smsHandledWakeLockTimer.cancel(); - } - if (this._smsHandledWakeLock) { - this._smsHandledWakeLock.unlock(); - this._smsHandledWakeLock = null; - } - }, - - portAddressedSmsApps: null, - handleSmsReceived: function(message) { - if (DEBUG) this.debug("handleSmsReceived: " + JSON.stringify(message)); - - // We need to acquire a CPU wake lock to avoid the system falling into - // the sleep mode when the RIL handles the received SMS. + _acquireSmsHandledWakeLock: function() { if (!this._smsHandledWakeLock) { if (DEBUG) this.debug("Acquiring a CPU wake lock for handling SMS."); this._smsHandledWakeLock = gPowerManagerService.newWakeLock("cpu"); @@ -2798,17 +2779,251 @@ RadioInterface.prototype = { .initWithCallback(this._releaseSmsHandledWakeLock.bind(this), SMS_HANDLED_WAKELOCK_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT); + }, - // FIXME: Bug 737202 - Typed arrays become normal arrays when sent to/from workers - if (message.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { - message.fullData = new Uint8Array(message.fullData); + _releaseSmsHandledWakeLock: function() { + if (DEBUG) this.debug("Releasing the CPU wake lock for handling SMS."); + if (this._smsHandledWakeLockTimer) { + this._smsHandledWakeLockTimer.cancel(); + } + if (this._smsHandledWakeLock) { + this._smsHandledWakeLock.unlock(); + this._smsHandledWakeLock = null; + } + }, + + /** + * Hash map for received multipart sms fragments. Messages are hashed with + * its sender address and concatenation reference number. Three additional + * attributes `segmentMaxSeq`, `receivedSegments`, `segments` are inserted. + */ + _receivedSmsSegmentsMap: null, + + /** + * Helper for processing received multipart SMS. + * + * @return null for handled segments, and an object containing full message + * body/data once all segments are received. + */ + _processReceivedSmsSegment: function(aSegment) { + + // Directly replace full message body for single SMS. + if (!(aSegment.segmentMaxSeq && (aSegment.segmentMaxSeq > 1))) { + if (aSegment.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { + aSegment.fullData = aSegment.data; + } else { + aSegment.fullBody = aSegment.body; + } + return aSegment; + } + + // Handle Concatenation for Class 0 SMS + let hash = aSegment.sender + ":" + + aSegment.segmentRef + ":" + + aSegment.segmentMaxSeq; + let seq = aSegment.segmentSeq; + + let options = this._receivedSmsSegmentsMap[hash]; + if (!options) { + options = aSegment; + this._receivedSmsSegmentsMap[hash] = options; + + options.receivedSegments = 0; + options.segments = []; + } else if (options.segments[seq]) { + // Duplicated segment? + if (DEBUG) { + this.debug("Got duplicated segment no." + seq + + " of a multipart SMS: " + JSON.stringify(aSegment)); + } + return null; + } + + if (options.receivedSegments > 0) { + // Update received timestamp. + options.timestamp = aSegment.timestamp; + } + + if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { + options.segments[seq] = aSegment.data; + } else { + options.segments[seq] = aSegment.body; + } + options.receivedSegments++; + + // The port information is only available in 1st segment for CDMA WAP Push. + // If the segments of a WAP Push are not received in sequence + // (e.g., SMS with seq == 1 is not the 1st segment received by the device), + // we have to retrieve the port information from 1st segment and + // save it into the cached options. + if (aSegment.teleservice === RIL.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP + && seq === 1) { + if (!options.originatorPort && aSegment.originatorPort) { + options.originatorPort = aSegment.originatorPort; + } + + if (!options.destinationPort && aSegment.destinationPort) { + options.destinationPort = aSegment.destinationPort; + } + } + + if (options.receivedSegments < options.segmentMaxSeq) { + if (DEBUG) { + this.debug("Got segment no." + seq + " of a multipart SMS: " + + JSON.stringify(options)); + } + return null; + } + + // Remove from map + delete this._receivedSmsSegmentsMap[hash]; + + // Rebuild full body + if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { + // Uint8Array doesn't have `concat`, so we have to merge all segements + // by hand. + let fullDataLen = 0; + for (let i = 1; i <= options.segmentMaxSeq; i++) { + fullDataLen += options.segments[i].length; + } + + options.fullData = new Uint8Array(fullDataLen); + for (let d= 0, i = 1; i <= options.segmentMaxSeq; i++) { + let data = options.segments[i]; + for (let j = 0; j < data.length; j++) { + options.fullData[d++] = data[j]; + } + } + } else { + options.fullBody = options.segments.join(""); + } + + // Remove handy fields after completing the concatenation. + delete options.receivedSegments; + delete options.segments; + + if (DEBUG) { + this.debug("Got full multipart SMS: " + JSON.stringify(options)); + } + + return options; + }, + + /** + * Helper to create Savable SmsSegment. + */ + _createSavableSmsSegment: function(aMessage) { + // We precisely define what data fields to be stored into + // DB here for better data migration. + let segment = {}; + segment.messageType = aMessage.messageType; + segment.teleservice = aMessage.teleservice; + segment.SMSC = aMessage.SMSC; + segment.sentTimestamp = aMessage.sentTimestamp; + segment.timestamp = Date.now(); + segment.sender = aMessage.sender; + segment.pid = aMessage.pid; + segment.encoding = aMessage.encoding; + segment.messageClass = aMessage.messageClass; + segment.iccId = this.getIccId(); + if (aMessage.header) { + segment.segmentRef = aMessage.header.segmentRef; + segment.segmentSeq = aMessage.header.segmentSeq; + segment.segmentMaxSeq = aMessage.header.segmentMaxSeq; + segment.originatorPort = aMessage.header.originatorPort; + segment.destinationPort = aMessage.header.destinationPort; + } + segment.mwiPresent = (aMessage.mwi)? true: false; + segment.mwiDiscard = (segment.mwiPresent)? aMessage.mwi.discard: false; + segment.mwiMsgCount = (segment.mwiPresent)? aMessage.mwi.msgCount: 0; + segment.mwiActive = (segment.mwiPresent)? aMessage.mwi.active: false; + segment.serviceCategory = aMessage.serviceCategory; + segment.language = aMessage.language; + segment.data = aMessage.data; + segment.body = aMessage.body; + + return segment; + }, + + /** + * Helper to purge complete message. + * + * We remove unnessary fields defined in _createSavableSmsSegment() after + * completing the concatenation. + */ + _purgeCompleteSmsMessage: function(aMessage) { + // Purge concatenation info + delete aMessage.segmentRef; + delete aMessage.segmentSeq; + delete aMessage.segmentMaxSeq; + + // Purge partial message body + delete aMessage.data; + delete aMessage.body; + }, + + /** + * handle concatenation of received SMS. + */ + handleSmsMultipart: function(aMessage) { + if (DEBUG) this.debug("handleSmsMultipart: " + JSON.stringify(aMessage)); + + this._acquireSmsHandledWakeLock(); + + let segment = this._createSavableSmsSegment(aMessage); + + let isMultipart = (segment.segmentMaxSeq && (segment.segmentMaxSeq > 1)); + let messageClass = segment.messageClass; + + let handleReceivedAndAck = function(aRvOfIncompleteMsg, aCompleteMessage) { + if (aCompleteMessage) { + this._purgeCompleteSmsMessage(aCompleteMessage); + if (this.handleSmsReceived(aCompleteMessage)) { + this.sendAckSms(Cr.NS_OK, aCompleteMessage); + } + // else Ack will be sent after further process in handleSmsReceived. + } else { + this.sendAckSms(aRvOfIncompleteMsg, segment); + } + }.bind(this); + + // No need to access SmsSegmentStore for Class 0 SMS and Single SMS. + if (!isMultipart || + (messageClass == RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0])) { + // `When a mobile terminated message is class 0 and the MS has the + // capability of displaying short messages, the MS shall display the + // message immediately and send an acknowledgement to the SC when the + // message has successfully reached the MS irrespective of whether + // there is memory available in the (U)SIM or ME. The message shall + // not be automatically stored in the (U)SIM or ME.` + // ~ 3GPP 23.038 clause 4 + + handleReceivedAndAck(Cr.NS_OK, // ACK OK For Incomplete Class 0 + this._processReceivedSmsSegment(segment)); + } else { + gMobileMessageDatabaseService + .saveSmsSegment(segment, function notifyResult(aRv, aCompleteMessage) { + handleReceivedAndAck(aRv, // Ack according to the result after saving + aCompleteMessage); + }); + } + }, + + portAddressedSmsApps: null, + handleSmsReceived: function(message) { + if (DEBUG) this.debug("handleSmsReceived: " + JSON.stringify(message)); + + if (message.messageType == RIL.PDU_CDMA_MSG_TYPE_BROADCAST) { + gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived", + this.clientId, message); + return true; } // Dispatch to registered handler if application port addressing is // available. Note that the destination port can possibly be zero when // representing a UDP/TCP port. - if (message.header && message.header.destinationPort != null) { - let handler = this.portAddressedSmsApps[message.header.destinationPort]; + if (message.destinationPort != null) { + let handler = this.portAddressedSmsApps[message.destinationPort]; if (handler) { handler(message); } @@ -2824,8 +3039,6 @@ RadioInterface.prototype = { message.sender = message.sender || null; message.receiver = this.getPhoneNumber(); message.body = message.fullBody = message.fullBody || null; - message.timestamp = Date.now(); - message.iccId = this.getIccId(); if (gSmsService.isSilentNumber(message.sender)) { message.id = -1; @@ -2855,8 +3068,14 @@ RadioInterface.prototype = { return true; } - let mwi = message.mwi; - if (mwi) { + if (message.mwiPresent) { + let mwi = { + discard: message.mwiDiscard, + msgCount: message.mwiMsgCount, + active: message.mwiActive + }; + this.workerMessenger.send("updateMwis", { mwi: mwi }); + mwi.returnNumber = message.sender; mwi.returnMessage = message.fullBody; gMessageManager.sendVoicemailMessage("RIL:VoicemailNotification", @@ -2864,7 +3083,7 @@ RadioInterface.prototype = { // Dicarded MWI comes without text body. // Hence, we discard it here after notifying the MWI status. - if (mwi.discard) { + if (message.mwiDiscard) { return true; } } @@ -2872,14 +3091,7 @@ RadioInterface.prototype = { let notifyReceived = function notifyReceived(rv, domMessage) { let success = Components.isSuccessCode(rv); - // Acknowledge the reception of the SMS. - // Note: Ack has been done by modem for NEW_SMS_ON_SIM - if (message.simStatus === undefined) { - this.workerMessenger.send("ackSMS", { - result: (success ? RIL.PDU_FCS_OK - : RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED) - }); - } + this.sendAckSms(rv, message); if (!success) { // At this point we could send a message to content to notify the user @@ -2926,6 +3138,26 @@ RadioInterface.prototype = { return false; }, + /** + * Handle ACK response of received SMS. + */ + sendAckSms: function(aRv, aMessage) { + if (aMessage.messageClass === RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_2]) { + return; + } + + let result = RIL.PDU_FCS_OK; + if (!Components.isSuccessCode(aRv)) { + if (DEBUG) this.debug("Failed to handle received sms: " + aRv); + result = (aRv === Cr.NS_ERROR_FILE_NO_DEVICE_SPACE) + ? RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED + : RIL.PDU_FCS_UNSPECIFIED; + } + + this.workerMessenger.send("ackSMS", { result: result }); + + }, + /** * Set the setting value of "time.clock.automatic-update.available". */ diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 3041a9eac3b..3b8a464ac09 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -213,7 +213,6 @@ function RilObject(aContext) { this.currentCalls = {}; this.currentConference = {state: null, participants: {}}; this.currentDataCalls = {}; - this._receivedSmsSegmentsMap = {}; this._pendingSentSmsMap = {}; this.pendingNetworkType = {}; this._receivedSmsCbPagesMap = {}; @@ -243,13 +242,6 @@ RilObject.prototype = { */ currentDataCalls: null, - /** - * Hash map for received multipart sms fragments. Messages are hashed with - * its sender address and concatenation reference number. Three additional - * attributes `segmentMaxSeq`, `receivedSegments`, `segments` are inserted. - */ - _receivedSmsSegmentsMap: null, - /** * Outgoing messages waiting for SMS-STATUS-REPORT. */ @@ -378,7 +370,7 @@ RilObject.prototype = { this.deactivateDataCall(datacall); } - // Don't clean up this._receivedSmsSegmentsMap or this._pendingSentSmsMap + // Don't clean up this._pendingSentSmsMap // because on rild restart: we may continue with the pending segments. /** @@ -1814,6 +1806,15 @@ RilObject.prototype = { Buf.sendParcel(); }, + /** + * Update received MWI into EF_MWIS. + */ + updateMwis: function(options) { + if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) { + this.context.SimRecordHelper.updateMWIS(options.mwi); + } + }, + setCellBroadcastDisabled: function(options) { this.cellBroadcastDisabled = options.disabled; @@ -4396,55 +4397,19 @@ RilObject.prototype = { }, /** - * Helper for processing multipart SMS. + * Helper to delegate the received sms segment to RadioInterface to process. * * @param message * Received sms message. * - * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. + * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK */ _processSmsMultipart: function(message) { - if (message.header && message.header.segmentMaxSeq && - (message.header.segmentMaxSeq > 1)) { - message = this._processReceivedSmsSegment(message); - } else { - if (message.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { - message.fullData = message.data; - delete message.data; - } else { - message.fullBody = message.body; - delete message.body; - } - } + message.rilMessageType = "sms-received"; - if (message) { - message.result = PDU_FCS_OK; - if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { - // `MS shall ensure that the message has been to the SMS data field in - // the (U)SIM before sending an ACK to the SC.` ~ 3GPP 23.038 clause 4 - message.result = PDU_FCS_RESERVED; - } + this.sendChromeMessage(message); - if (message.messageType == PDU_CDMA_MSG_TYPE_BROADCAST) { - message.rilMessageType = "broadcastsms-received"; - } else { - message.rilMessageType = "sms-received"; - } - - this.sendChromeMessage(message); - - // Update MWI Status into ICC if present. - if (message.mwi && - this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) { - this.context.SimRecordHelper.updateMWIS(message.mwi); - } - - // We will acknowledge receipt of the SMS after we try to store it - // in the database. - return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK; - } - - return PDU_FCS_OK; + return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK; }, /** @@ -4603,95 +4568,6 @@ RilObject.prototype = { return this._processSmsMultipart(message); }, - /** - * Helper for processing received multipart SMS. - * - * @return null for handled segments, and an object containing full message - * body/data once all segments are received. - */ - _processReceivedSmsSegment: function(original) { - let hash = original.sender + ":" + original.header.segmentRef; - let seq = original.header.segmentSeq; - - let options = this._receivedSmsSegmentsMap[hash]; - if (!options) { - options = original; - this._receivedSmsSegmentsMap[hash] = options; - - options.segmentMaxSeq = original.header.segmentMaxSeq; - options.receivedSegments = 0; - options.segments = []; - } else if (options.segments[seq]) { - // Duplicated segment? - if (DEBUG) { - this.context.debug("Got duplicated segment no." + seq + - " of a multipart SMS: " + JSON.stringify(original)); - } - return null; - } - - if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { - options.segments[seq] = original.data; - delete original.data; - } else { - options.segments[seq] = original.body; - delete original.body; - } - options.receivedSegments++; - - // The port information is only available in 1st segment for CDMA WAP Push. - // If the segments of a WAP Push are not received in sequence - // (e.g., SMS with seq == 1 is not the 1st segment received by the device), - // we have to retrieve the port information from 1st segment and - // save it into the cached options.header. - if (original.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP && seq === 1) { - if (!options.header.originatorPort && original.header.originatorPort) { - options.header.originatorPort = original.header.originatorPort; - } - - if (!options.header.destinationPort && original.header.destinationPort) { - options.header.destinationPort = original.header.destinationPort; - } - } - - if (options.receivedSegments < options.segmentMaxSeq) { - if (DEBUG) { - this.context.debug("Got segment no." + seq + " of a multipart SMS: " + - JSON.stringify(options)); - } - return null; - } - - // Remove from map - delete this._receivedSmsSegmentsMap[hash]; - - // Rebuild full body - if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { - // Uint8Array doesn't have `concat`, so we have to merge all segements - // by hand. - let fullDataLen = 0; - for (let i = 1; i <= options.segmentMaxSeq; i++) { - fullDataLen += options.segments[i].length; - } - - options.fullData = new Uint8Array(fullDataLen); - for (let d= 0, i = 1; i <= options.segmentMaxSeq; i++) { - let data = options.segments[i]; - for (let j = 0; j < data.length; j++) { - options.fullData[d++] = data[j]; - } - } - } else { - options.fullBody = options.segments.join(""); - } - - if (DEBUG) { - this.context.debug("Got full multipart SMS: " + JSON.stringify(options)); - } - - return options; - }, - /** * Helper for processing sent multipart SMS. */ @@ -7618,24 +7494,24 @@ GsmPDUHelperObject.prototype = { // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT, // ST:STATUS-REPORT, C:COMMAND // M:Mandatory, O:Optional, X:Unavailable - // D DR S SR ST C - SMSC: null, // M M M M M M - mti: null, // M M M M M M - udhi: null, // M M O M M M - sender: null, // M X X X X X - recipient: null, // X X M X M M - pid: null, // M O M O O M - epid: null, // M O M O O M - dcs: null, // M O M O O X - mwi: null, // O O O O O O - replace: false, // O O O O O O - header: null, // M M O M M M - body: null, // M O M O O O - data: null, // M O M O O O - timestamp: null, // M X X X X X - status: null, // X X X X M X - scts: null, // X X X M M X - dt: null, // X X X X M X + // D DR S SR ST C + SMSC: null, // M M M M M M + mti: null, // M M M M M M + udhi: null, // M M O M M M + sender: null, // M X X X X X + recipient: null, // X X M X M M + pid: null, // M O M O O M + epid: null, // M O M O O M + dcs: null, // M O M O O X + mwi: null, // O O O O O O + replace: false, // O O O O O O + header: null, // M M O M M M + body: null, // M O M O O O + data: null, // M O M O O O + sentTimestamp: null, // M X X X X X + status: null, // X X X X M X + scts: null, // X X X M M X + dt: null, // X X X X M X }; // SMSC info @@ -8904,7 +8780,7 @@ CdmaPDUHelperObject.prototype = { header: message.header, body: message.body, data: message.data, - timestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP], + sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP], language: message[PDU_CDMA_LANGUAGE_INDICATOR], status: null, scts: null, diff --git a/dom/system/gonk/tests/test_ril_worker_icc.js b/dom/system/gonk/tests/test_ril_worker_icc.js index 80cb358013c..3384b020cbb 100644 --- a/dom/system/gonk/tests/test_ril_worker_icc.js +++ b/dom/system/gonk/tests/test_ril_worker_icc.js @@ -2979,7 +2979,7 @@ add_test(function test_read_new_sms_on_sim() { do_check_eq("sms-received", postedMessage.rilMessageType); do_check_eq("+0123456789", postedMessage.SMSC); do_check_eq("+9876543210", postedMessage.sender); - do_check_eq("How are you?", postedMessage.fullBody); + do_check_eq("How are you?", postedMessage.body); } do_test(); diff --git a/dom/system/gonk/tests/test_ril_worker_sms_cdma.js b/dom/system/gonk/tests/test_ril_worker_sms_cdma.js index d7b956b4b05..3a9e1d85977 100644 --- a/dom/system/gonk/tests/test_ril_worker_sms_cdma.js +++ b/dom/system/gonk/tests/test_ril_worker_sms_cdma.js @@ -307,18 +307,11 @@ add_test(function test_processCdmaSmsWapPush() { do_check_eq(orig_address, postedMessage.sender); do_check_eq(0x23F0, postedMessage.header.originatorPort); do_check_eq(0x0B84, postedMessage.header.destinationPort); - do_check_eq(fullDataHexString, bytesToHexString(postedMessage.fullData)); + do_check_eq(fullDataHexString, bytesToHexString(postedMessage.data)); } // Verify Single WAP PDU test_CdmaSmsWapPdu(["000102030405060708090A0B0C0D0E0F"]); - // Verify Concatenated WAP PDUs - test_CdmaSmsWapPdu(["000102030405060708090A0B0C0D0E0F", "0F0E0D0C0B0A09080706050403020100"]); - - // Verify Concatenated WAP PDUs received in reversed order. - // Note: the port information is only available in 1st segment in CDMA WAP Push. - test_CdmaSmsWapPdu(["000102030405060708090A0B0C0D0E0F", "0F0E0D0C0B0A09080706050403020100"], true); - run_next_test(); }); diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 1b699cc5228..b0e5dc495ad 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -51,6 +51,8 @@ #endif #include "GeckoProfiler.h" #include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/Hal.h" +#include "mozilla/HalTypes.h" using namespace base; using namespace mozilla; @@ -109,6 +111,11 @@ static void ReleaseCompositorThread() } } +static void SetThreadPriority() +{ + hal::SetCurrentThreadPriority(hal::THREAD_PRIORITY_COMPOSITOR); +} + void CompositorParent::StartUpWithExistingThread(MessageLoop* aMsgLoop, PlatformThreadId aThreadID) @@ -162,6 +169,7 @@ bool CompositorParent::CreateThread() sCompositorThread = nullptr; return false; } + return true; } @@ -200,6 +208,8 @@ CompositorParent::CompositorParent(nsIWidget* aWidget, CompositorLoop()->PostTask(FROM_HERE, NewRunnableFunction(&AddCompositor, this, &mCompositorID)); + CompositorLoop()->PostTask(FROM_HERE, NewRunnableFunction(SetThreadPriority)); + mRootLayerTreeID = AllocateLayerTreeId(); sIndirectLayerTrees[mRootLayerTreeID].mParent = this; diff --git a/hal/Hal.cpp b/hal/Hal.cpp index 630a730c1ca..ced34278fb7 100644 --- a/hal/Hal.cpp +++ b/hal/Hal.cpp @@ -871,6 +871,12 @@ SetProcessPriority(int aPid, aBackgroundLRU)); } +void +SetCurrentThreadPriority(ThreadPriority aPriority) +{ + PROXY_IF_SANDBOXED(SetCurrentThreadPriority(aPriority)); +} + // From HalTypes.h. const char* ProcessPriorityToString(ProcessPriority aPriority) @@ -898,6 +904,18 @@ ProcessPriorityToString(ProcessPriority aPriority) } } +const char * +ThreadPriorityToString(ThreadPriority aPriority) +{ + switch (aPriority) { + case THREAD_PRIORITY_COMPOSITOR: + return "COMPOSITOR"; + default: + MOZ_ASSERT(false); + return "???"; + } +} + // From HalTypes.h. const char* ProcessPriorityToString(ProcessPriority aPriority, diff --git a/hal/Hal.h b/hal/Hal.h index 829deec6d84..f25fcd7a07c 100644 --- a/hal/Hal.h +++ b/hal/Hal.h @@ -496,6 +496,13 @@ void SetProcessPriority(int aPid, hal::ProcessCPUPriority aCPUPriority, uint32_t aLRU = 0); +/** + * Set the current thread's priority to appropriate platform-specific value for + * given functionality. Instead of providing arbitrary priority numbers you + * must specify a type of function like THREAD_PRIORITY_COMPOSITOR. + */ +void SetCurrentThreadPriority(hal::ThreadPriority aPriority); + /** * Register an observer for the FM radio. */ diff --git a/hal/HalTypes.h b/hal/HalTypes.h index 925bdcb97df..271dbc64c6e 100644 --- a/hal/HalTypes.h +++ b/hal/HalTypes.h @@ -99,6 +99,16 @@ enum ProcessCPUPriority { NUM_PROCESS_CPU_PRIORITY }; +// Values that can be passed to hal::SetThreadPriority(). These should be +// functional in nature, such as COMPOSITOR, instead of levels, like LOW/HIGH. +// This allows us to tune our priority scheme for the system in one place such +// that it makes sense holistically for the overall operating system. On gonk +// or android we may want different priority schemes than on windows, etc. +enum ThreadPriority { + THREAD_PRIORITY_COMPOSITOR, + NUM_THREAD_PRIORITY +}; + // Convert a ProcessPriority enum value (with an optional ProcessCPUPriority) // to a string. The strings returned by this function are statically // allocated; do not attempt to free one! @@ -112,6 +122,14 @@ const char* ProcessPriorityToString(ProcessPriority aPriority, ProcessCPUPriority aCPUPriority); +// Convert a ThreadPriority enum value to a string. The strings returned by +// this function are statically allocated; do not attempt to free one! +// +// If you pass an unknown process priority (or NUM_THREAD_PRIORITY), we +// fatally assert in debug builds and otherwise return "???". +const char * +ThreadPriorityToString(ThreadPriority aPriority); + /** * Used by ModifyWakeLock */ diff --git a/hal/fallback/FallbackThreadPriority.cpp b/hal/fallback/FallbackThreadPriority.cpp new file mode 100644 index 00000000000..afab11a1889 --- /dev/null +++ b/hal/fallback/FallbackThreadPriority.cpp @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_impl { + +void +SetCurrentThreadPriority(ThreadPriority aPriority) +{ + HAL_LOG(("FallbackThreadPriority - SetCurrentThreadPriority(%d)\n", + ThreadPriorityToString(aPriority))); +} + +} // hal_impl +} // namespace mozilla diff --git a/hal/gonk/GonkHal.cpp b/hal/gonk/GonkHal.cpp index 3584f507c34..07482e1a89c 100644 --- a/hal/gonk/GonkHal.cpp +++ b/hal/gonk/GonkHal.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "mozilla/DebugOnly.h" @@ -38,6 +39,7 @@ #include "hardware_legacy/vibrator.h" #include "hardware_legacy/power.h" #include "libdisplay/GonkDisplay.h" +#include "utils/threads.h" #include "base/message_loop.h" @@ -1348,6 +1350,14 @@ SetNiceForPid(int aPid, int aNice) int tid = static_cast(tidlong); + // Do not set the priority of threads running with a real-time policy + // as part of the bulk process adjustment. These threads need to run + // at their specified priority in order to meet timing guarantees. + int schedPolicy = sched_getscheduler(tid); + if (schedPolicy == SCHED_FIFO || schedPolicy == SCHED_RR) { + continue; + } + errno = 0; // Get and set the task's new priority. int origtaskpriority = getpriority(PRIO_PROCESS, tid); @@ -1360,6 +1370,15 @@ SetNiceForPid(int aPid, int aNice) int newtaskpriority = std::max(origtaskpriority - origProcPriority + aNice, aNice); + + // Do not reduce priority of threads already running at priorities greater + // than normal. These threads are likely special service threads that need + // elevated priorities to process audio, display composition, etc. + if (newtaskpriority > origtaskpriority && + origtaskpriority < ANDROID_PRIORITY_NORMAL) { + continue; + } + rv = setpriority(PRIO_PROCESS, tid, newtaskpriority); if (rv) { @@ -1454,6 +1473,54 @@ SetProcessPriority(int aPid, } } +void +SetCurrentThreadPriority(ThreadPriority aPriority) +{ + int policy = SCHED_OTHER; + int priorityOrNice = ANDROID_PRIORITY_NORMAL; + + switch(aPriority) { + case THREAD_PRIORITY_COMPOSITOR: + priorityOrNice = Preferences::GetInt("hal.gonk.compositor.rt_priority", 0); + if (priorityOrNice >= sched_get_priority_min(SCHED_FIFO) && + priorityOrNice <= sched_get_priority_max(SCHED_FIFO)) { + policy = SCHED_FIFO; + } else { + priorityOrNice = Preferences::GetInt("hal.gonk.compositor.nice", + ANDROID_PRIORITY_URGENT_DISPLAY); + } + break; + default: + LOG("Unrecognized thread priority %d; Doing nothing", aPriority); + return; + } + + int tid = gettid(); + int rv = 0; + + // If a RT scheduler policy is used, then we must set the priority using + // sched_setscheduler() and the sched_param.sched_priority value. + if (policy == SCHED_FIFO || policy == SCHED_RR) { + LOG("Setting thread %d to priority level %s; RT priority %d", + tid, ThreadPriorityToString(aPriority), priorityOrNice); + sched_param schedParam; + schedParam.sched_priority = priorityOrNice; + rv = sched_setscheduler(tid, policy, &schedParam); + + // Otherwise priority is solely defined by the nice level, so use the + // setpriority() function. + } else { + LOG("Setting thread %d to priority level %s; nice level %d", + tid, ThreadPriorityToString(aPriority), priorityOrNice); + rv = setpriority(PRIO_PROCESS, tid, priorityOrNice); + } + + if (rv) { + LOG("Failed to set thread %d to priority level %s; error code %d", + tid, ThreadPriorityToString(aPriority), rv); + } +} + void FactoryReset() { diff --git a/hal/moz.build b/hal/moz.build index 9cf12a10832..e01626ec65c 100644 --- a/hal/moz.build +++ b/hal/moz.build @@ -151,6 +151,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk': 'fallback/FallbackProcessPriority.cpp', 'fallback/FallbackScreenPower.cpp', 'fallback/FallbackSwitch.cpp', + 'fallback/FallbackThreadPriority.cpp', 'fallback/FallbackTime.cpp', 'fallback/FallbackWakeLocks.cpp', ] diff --git a/hal/sandbox/SandboxHal.cpp b/hal/sandbox/SandboxHal.cpp index bd489d26ce6..1820c8b4dbe 100644 --- a/hal/sandbox/SandboxHal.cpp +++ b/hal/sandbox/SandboxHal.cpp @@ -362,6 +362,12 @@ SetProcessPriority(int aPid, NS_RUNTIMEABORT("Only the main process may set processes' priorities."); } +void +SetCurrentThreadPriority(ThreadPriority aPriority) +{ + NS_RUNTIMEABORT("Only the main process may set thread priorities."); +} + void EnableFMRadio(const hal::FMRadioSettings& aSettings) { diff --git a/mobile/android/base/toolbar/BrowserToolbar.java b/mobile/android/base/toolbar/BrowserToolbar.java index 5d57a599bb1..7f6836dfadd 100644 --- a/mobile/android/base/toolbar/BrowserToolbar.java +++ b/mobile/android/base/toolbar/BrowserToolbar.java @@ -503,6 +503,9 @@ public class BrowserToolbar extends GeckoRelativeLayout break; case SELECTED: + flags.add(UpdateFlags.PRIVATE_MODE); + setPrivateMode(tab.isPrivate()); + // Fall through. case LOAD_ERROR: flags.add(UpdateFlags.TITLE); // Fall through. @@ -511,12 +514,9 @@ public class BrowserToolbar extends GeckoRelativeLayout // us of a title change, so we don't update the title here. flags.add(UpdateFlags.FAVICON); flags.add(UpdateFlags.SITE_IDENTITY); - flags.add(UpdateFlags.PRIVATE_MODE); updateBackButton(tab); updateForwardButton(tab); - - setPrivateMode(tab.isPrivate()); break; case TITLE: diff --git a/mobile/android/confvars.sh b/mobile/android/confvars.sh index adcb3c759d0..ddd52f4e8d0 100644 --- a/mobile/android/confvars.sh +++ b/mobile/android/confvars.sh @@ -5,7 +5,7 @@ MOZ_APP_BASENAME=Fennec MOZ_APP_VENDOR=Mozilla -MOZ_APP_VERSION=30.0a1 +MOZ_APP_VERSION=31.0a1 MOZ_APP_UA_NAME=Firefox MOZ_BRANDING_DIRECTORY=mobile/android/branding/unofficial diff --git a/services/sync/Makefile.in b/services/sync/Makefile.in index 634a402cfb9..31948f76697 100644 --- a/services/sync/Makefile.in +++ b/services/sync/Makefile.in @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # Definitions used by constants.js. -weave_version := 1.32.0 +weave_version := 1.33.0 weave_id := {340c2bbc-ce74-4362-90b5-7c26312808ef} # Preprocess files. diff --git a/toolkit/components/jsdownloads/src/DownloadCore.jsm b/toolkit/components/jsdownloads/src/DownloadCore.jsm index 04aa304f005..a82880410b5 100644 --- a/toolkit/components/jsdownloads/src/DownloadCore.jsm +++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm @@ -63,7 +63,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm") XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", @@ -411,27 +411,48 @@ this.Download.prototype = { } } + // In case the download was restarted while cancellation was in progress, + // but the previous attempt actually succeeded before cancellation could + // be processed, it is possible that the download has already finished. + if (this.succeeded) { + return; + } + try { // Disallow download if parental controls service restricts it. if (yield DownloadIntegration.shouldBlockForParentalControls(this)) { throw new DownloadError({ becauseBlockedByParentalControls: true }); } + // We should check if we have been canceled in the meantime, after all + // the previous asynchronous operations have been executed and just + // before we call the "execute" method of the saver. + if (this._promiseCanceled) { + // The exception will become a cancellation in the "catch" block. + throw undefined; + } + // Execute the actual download through the saver object. + this._saverExecuting = true; yield this.saver.execute(DS_setProgressBytes.bind(this), DS_setProperties.bind(this)); + // Check for application reputation, which requires the entire file to - // be downloaded. - if (yield DownloadIntegration.shouldBlockForReputationCheck(this)) { - // Delete the target file that BackgroundFileSaver already moved - // into place. + // be downloaded. After that, check for the last time if the download + // has been canceled. Both cases require the target file to be deleted, + // thus we process both in the same block of code. + if ((yield DownloadIntegration.shouldBlockForReputationCheck(this)) || + this._promiseCanceled) { try { yield OS.File.remove(this.target.path); } catch (ex) { Cu.reportError(ex); } + // If this is actually a cancellation, this exception will be changed + // in the catch block below. throw new DownloadError({ becauseBlockedByReputationCheck: true }); } + // Update the status properties for a successful download. this.progress = 100; this.succeeded = true; @@ -461,6 +482,7 @@ this.Download.prototype = { throw ex; } finally { // Any cancellation request has now been processed. + this._saverExecuting = false; this._promiseCanceled = null; // Update the status properties, unless a new attempt already started. @@ -546,6 +568,12 @@ this.Download.prototype = { */ _promiseCanceled: null, + /** + * True between the call to the "execute" method of the saver and the + * completion of the current download attempt. + */ + _saverExecuting: false, + /** * Cancels the download. * @@ -589,8 +617,12 @@ this.Download.prototype = { this.canceled = true; this._notifyChange(); - // Execute the actual cancellation through the saver object. - this.saver.cancel(); + // Execute the actual cancellation through the saver object, in case it + // has already started. Otherwise, the cancellation will be handled just + // before the saver is started. + if (this._saverExecuting) { + this.saver.cancel(); + } } return this._promiseCanceled; @@ -1363,31 +1395,6 @@ this.DownloadSaver.prototype = { targetUri); }, - /** - * Return true if the request's response has been blocked by Windows parental - * controls with an HTTP 450 error code. - * - * @param aRequest - * nsIRequest object - * @return True if the response is blocked. - */ - isResponseParentalBlocked: function(aRequest) - { - // If the HTTP status is 450, then Windows Parental Controls have - // blocked this download. - if (aRequest instanceof Ci.nsIHttpChannel && - aRequest.responseStatus == 450) { - // Cancel the request, but set a flag on the download that can be - // retrieved later when handling the cancellation so that the proper - // blocked by parental controls error can be thrown. - this.download._blockedByParentalControls = true; - aRequest.cancel(Cr.NS_BINDING_ABORTED); - return true; - } - - return false; - }, - /** * Returns a static representation of the current object state. * @@ -1612,7 +1619,14 @@ this.DownloadCopySaver.prototype = { onStartRequest: function (aRequest, aContext) { backgroundFileSaver.onStartRequest(aRequest, aContext); - if (this.isResponseParentalBlocked(aRequest)) { + // Check if the request's response has been blocked by Windows + // Parental Controls with an HTTP 450 error code. + if (aRequest instanceof Ci.nsIHttpChannel && + aRequest.responseStatus == 450) { + // Set a flag that can be retrieved later when handling the + // cancellation so that the proper error can be thrown. + this.download._blockedByParentalControls = true; + aRequest.cancel(Cr.NS_BINDING_ABORTED); return; } @@ -1707,6 +1721,13 @@ this.DownloadCopySaver.prototype = { }.bind(copySaver), }, null); + // We should check if we have been canceled in the meantime, after + // all the previous asynchronous operations have been executed and + // just before we set the _backgroundFileSaver property. + if (this._canceled) { + throw new DownloadError({ message: "Saver canceled." }); + } + // If the operation succeeded, store the object to allow cancellation. this._backgroundFileSaver = backgroundFileSaver; } catch (ex) { @@ -1930,10 +1951,6 @@ this.DownloadLegacySaver.prototype = { ex.result == Cr.NS_ERROR_NOT_RESUMABLE) { } } - if (this.isResponseParentalBlocked(aRequest)) { - return; - } - // For legacy downloads, we must update the referrer at this time. if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) { this.download.source.referrer = aRequest.referrer.spec; diff --git a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm index 209a158bb3f..34be122de83 100644 --- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm +++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm @@ -46,7 +46,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); #endif XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", diff --git a/toolkit/components/jsdownloads/src/DownloadLegacy.js b/toolkit/components/jsdownloads/src/DownloadLegacy.js index 1a3ba9bf82b..ea4a78afb84 100644 --- a/toolkit/components/jsdownloads/src/DownloadLegacy.js +++ b/toolkit/components/jsdownloads/src/DownloadLegacy.js @@ -29,7 +29,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); //////////////////////////////////////////////////////////////////////////////// //// DownloadLegacyTransfer @@ -89,9 +89,25 @@ DownloadLegacyTransfer.prototype = { if ((aStateFlags & Ci.nsIWebProgressListener.STATE_START) && (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) { + + // If the request's response has been blocked by Windows Parental Controls + // with an HTTP 450 error code, we must cancel the request synchronously. + let blockedByParentalControls = aRequest instanceof Ci.nsIHttpChannel && + aRequest.responseStatus == 450; + if (blockedByParentalControls) { + aRequest.cancel(Cr.NS_BINDING_ABORTED); + } + // The main request has just started. Wait for the associated Download // object to be available before notifying. this._deferDownload.promise.then(download => { + // If the request was blocked, now that we have the download object we + // should set a flag that can be retrieved later when handling the + // cancellation so that the proper error can be thrown. + if (blockedByParentalControls) { + download._blockedByParentalControls = true; + } + download.saver.onTransferStarted( aRequest, this._cancelable instanceof Ci.nsIHelperAppLauncher); diff --git a/toolkit/components/jsdownloads/src/DownloadList.jsm b/toolkit/components/jsdownloads/src/DownloadList.jsm index 732870e48a0..e976621b662 100644 --- a/toolkit/components/jsdownloads/src/DownloadList.jsm +++ b/toolkit/components/jsdownloads/src/DownloadList.jsm @@ -37,7 +37,7 @@ const Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); diff --git a/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm b/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm index e6f26891058..ae10cf9c6ce 100644 --- a/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm +++ b/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm @@ -27,7 +27,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); diff --git a/toolkit/components/jsdownloads/src/Downloads.jsm b/toolkit/components/jsdownloads/src/Downloads.jsm index 6500f7ed1ac..bc381387859 100644 --- a/toolkit/components/jsdownloads/src/Downloads.jsm +++ b/toolkit/components/jsdownloads/src/Downloads.jsm @@ -36,7 +36,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "DownloadSummary", XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper", "resource://gre/modules/DownloadUIHelper.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); diff --git a/toolkit/components/jsdownloads/test/unit/common_test_Download.js b/toolkit/components/jsdownloads/test/unit/common_test_Download.js index a9a4e782c97..31e6a0daea0 100644 --- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js +++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js @@ -806,7 +806,10 @@ add_task(function test_cancel_immediately_restart_immediately() continueResponses(); try { yield promiseAttempt; - do_throw("The download should have been canceled."); + // If we get here, it means that the first attempt actually succeeded. In + // fact, this could be a valid outcome, because the cancellation request may + // not have been processed in time before the download finished. + do_print("The download should have been canceled."); } catch (ex if ex instanceof Downloads.Error) { do_check_false(ex.becauseSourceFailed); do_check_false(ex.becauseTargetFailed); diff --git a/toolkit/components/jsdownloads/test/unit/head.js b/toolkit/components/jsdownloads/test/unit/head.js index 66529bf1799..0b26d3198ab 100644 --- a/toolkit/components/jsdownloads/test/unit/head.js +++ b/toolkit/components/jsdownloads/test/unit/head.js @@ -34,7 +34,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", @@ -731,6 +731,15 @@ add_task(function test_common_initialize() gHttpServer.registerDirectory("/", do_get_file("../data")); gHttpServer.start(-1); + // Cache locks might prevent concurrent requests to the same resource, and + // this may block tests that use the interruptible handlers. + Services.prefs.setBoolPref("browser.cache.disk.enable", false); + Services.prefs.setBoolPref("browser.cache.memory.enable", false); + do_register_cleanup(function () { + Services.prefs.clearUserPref("browser.cache.disk.enable"); + Services.prefs.clearUserPref("browser.cache.memory.enable"); + }); + registerInterruptibleHandler("/interruptible.txt", function firstPart(aRequest, aResponse) { aResponse.setHeader("Content-Type", "text/plain", false); diff --git a/toolkit/locales/en-US/chrome/global/aboutSupport.dtd b/toolkit/locales/en-US/chrome/global/aboutSupport.dtd index 45cc7effd64..57d8f40da7d 100644 --- a/toolkit/locales/en-US/chrome/global/aboutSupport.dtd +++ b/toolkit/locales/en-US/chrome/global/aboutSupport.dtd @@ -25,6 +25,15 @@ This is likely the same like id.heading in crashes.dtd. --> + + + + + + + + + diff --git a/webapprt/PaymentUIGlue.js b/webapprt/PaymentUIGlue.js index e4d58770673..c7d62361136 100644 --- a/webapprt/PaymentUIGlue.js +++ b/webapprt/PaymentUIGlue.js @@ -14,20 +14,21 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "nsIMessageSender"); function paymentSuccess(aRequestId) { - return paymentCallback(aRequestId, "Payment:Success"); + return function(aResult) { + closePaymentWindow(aRequestId, function() { + cpmm.sendAsyncMessage("Payment:Success", { requestId: aRequestId, + result: aResult }); + }); + }; } function paymentFailed(aRequestId) { - return paymentCallback(aRequestId, "Payment:Failed"); -} - -function paymentCallback(aRequestId, aMsg) { - return function(aResult) { - closePaymentWindow(aRequestId, function() { - cpmm.sendAsyncMessage(aMsg, { result: aResult, - requestId: aRequestId }); - }); - }; + return function(aErrorMsg) { + closePaymentWindow(aRequestId, function() { + cpmm.sendAsyncMessage("Payment:Failed", { requestId: aRequestId, + errorMsg: aErrorMsg }); + }); + }; } let payments = {}; diff --git a/webapprt/test/chrome/browser_mozpay.js b/webapprt/test/chrome/browser_mozpay.js index 066270a1904..9da82455888 100644 --- a/webapprt/test/chrome/browser_mozpay.js +++ b/webapprt/test/chrome/browser_mozpay.js @@ -1,11 +1,22 @@ Cu.import("resource://gre/modules/Services.jsm"); let { PaymentManager } = Cu.import("resource://gre/modules/Payment.jsm", {}); +Cu.import("resource://webapprt/modules/WebappRT.jsm"); function test() { waitForExplicitFinish(); - let providerWindow = null; - let providerUri = "https://example.com:443/webapprtChrome/webapprt/test/chrome/mozpay-success.html?req="; + let curTest = 0; + + let tests = []; + tests.push({ + providerUri: "https://example.com:443/webapprtChrome/webapprt/test/chrome/mozpay-success.html?req=", + message: "Success." + }); + tests.push({ + providerUri: "https://example.com:443/webapprtChrome/webapprt/test/chrome/mozpay-failure.html?req=", + message: "Chocolate rejected." + }); + let jwt = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJhdWQiOiAibW9j" + "a3BheXByb3ZpZGVyLnBocGZvZ2FwcC5jb20iLCAiaXNzIjogIkVudGVyI" + "HlvdSBhcHAga2V5IGhlcmUhIiwgInJlcXVlc3QiOiB7Im5hbWUiOiAiUG" + @@ -21,17 +32,19 @@ function test() { PaymentManager.registeredProviders["mock/payments/inapp/v1"] = { name: "mockprovider", description: "Mock Payment Provider", - uri: providerUri, + uri: tests[curTest].providerUri, requestMethod: "GET" }; + let providerWindow; + let winObserver = function(win, topic) { if (topic == "domwindowopened") { win.addEventListener("load", function onLoadWindow() { win.removeEventListener("load", onLoadWindow, false); if (win.document.getElementById("content").getAttribute("src") == - (providerUri + jwt)) { + (tests[curTest].providerUri + jwt)) { ok(true, "Payment provider window shown."); providerWindow = win; } @@ -43,25 +56,45 @@ function test() { let mutObserver = null; - loadWebapp("mozpay.webapp", undefined, function onLoad() { + function onLoad() { let msg = gAppBrowser.contentDocument.getElementById("msg"); mutObserver = new MutationObserver(function(mutations) { - if (msg.textContent == "Success.") { - ok(true, "Payment success."); - } else { - ok(false, "Payment success."); - } + is(msg.textContent, tests[curTest].message, "Got: " + tests[curTest].message); - if (providerWindow == null) { + if (!providerWindow) { ok(false, "Payment provider window shown."); } else { providerWindow.close(); + providerWindow = null; } - finish(); + runNextTest(); }); mutObserver.observe(msg, { childList: true }); - }); + } + + loadWebapp("mozpay.webapp", undefined, onLoad); + + function runNextTest() { + providerWindow = null; + if (mutObserver) { + mutObserver.disconnect(); + } + + curTest++; + + if (curTest < tests.length) { + PaymentManager.registeredProviders["mock/payments/inapp/v1"].uri = tests[curTest].providerUri; + + gAppBrowser.addEventListener("load", function onLoadH() { + gAppBrowser.removeEventListener("load", onLoadH, true); + onLoad(); + }, true); + gAppBrowser.reload(); + } else { + finish(); + } + } registerCleanupFunction(function() { Services.ww.unregisterNotification(winObserver); diff --git a/webapprt/test/chrome/mozpay-failure.html b/webapprt/test/chrome/mozpay-failure.html new file mode 100644 index 00000000000..baaba42e961 --- /dev/null +++ b/webapprt/test/chrome/mozpay-failure.html @@ -0,0 +1,12 @@ + + + + + + + +

Webapp waiting to pay...

+ + diff --git a/webapprt/test/chrome/mozpay.html b/webapprt/test/chrome/mozpay.html index 3973791a34c..8997bc6c7de 100644 --- a/webapprt/test/chrome/mozpay.html +++ b/webapprt/test/chrome/mozpay.html @@ -37,7 +37,7 @@ document.getElementById("msg").textContent = "Success."; }; request.onerror = function onerror() { - document.getElementById("msg").textContent = "Failure."; + document.getElementById("msg").textContent = request.error.name; };

Webapp waiting to be paid...

diff --git a/webapprt/test/chrome/webapprt.ini b/webapprt/test/chrome/webapprt.ini index 715f8e4041f..026742fc299 100644 --- a/webapprt/test/chrome/webapprt.ini +++ b/webapprt/test/chrome/webapprt.ini @@ -26,6 +26,7 @@ support-files = mozpay.webapp^headers^ mozpay.html mozpay-success.html + mozpay-failure.html getUserMedia.webapp getUserMedia.webapp^headers^ getUserMedia.html diff --git a/xpcom/components/Module.h b/xpcom/components/Module.h index 210cfb30afa..53b04e7ced4 100644 --- a/xpcom/components/Module.h +++ b/xpcom/components/Module.h @@ -21,7 +21,7 @@ namespace mozilla { */ struct Module { - static const unsigned int kVersion = 30; + static const unsigned int kVersion = 31; struct CIDEntry;