diff --git a/accessible/src/jsat/AccessFu.css b/accessible/src/jsat/AccessFu.css index 4958cc97e8a..6ddca010d07 100644 --- a/accessible/src/jsat/AccessFu.css +++ b/accessible/src/jsat/AccessFu.css @@ -2,7 +2,7 @@ * 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/. */ -#virtual-cursor-box { +#virtual-cursor-box { position: fixed; border: 1px solid orange; pointer-events: none; @@ -11,7 +11,7 @@ box-shadow: 1px 1px 1px #444; } -#virtual-cursor-inset { +#virtual-cursor-inset { border-radius: 1px; box-shadow: inset 1px 1px 1px #444; display: block; @@ -19,4 +19,12 @@ width: 100%; height: 100%; pointer-events: none; -} \ No newline at end of file +} + +#accessfu-glass { + width: 100%; + height: 100%; + position: fixed; + top: 0px; + left: 0px; +} diff --git a/accessible/src/jsat/AccessFu.jsm b/accessible/src/jsat/AccessFu.jsm index e8d269cc65d..f3b8733c894 100644 --- a/accessible/src/jsat/AccessFu.jsm +++ b/accessible/src/jsat/AccessFu.jsm @@ -16,6 +16,7 @@ Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/accessibility/Utils.jsm'); Cu.import('resource://gre/modules/accessibility/Presenters.jsm'); Cu.import('resource://gre/modules/accessibility/VirtualCursorController.jsm'); +Cu.import('resource://gre/modules/accessibility/TouchAdapter.jsm'); const ACCESSFU_DISABLE = 0; const ACCESSFU_ENABLE = 1; @@ -44,8 +45,24 @@ var AccessFu = { this.prefsBranch.addObserver('activate', this, false); this.prefsBranch.addObserver('explorebytouch', this, false); - if (Utils.MozBuildApp == 'mobile/android') - Services.obs.addObserver(this, 'Accessibility:Settings', false); + this.touchAdapter = TouchAdapter; + + switch(Utils.MozBuildApp) { + case 'mobile/android': + Services.obs.addObserver(this, 'Accessibility:Settings', false); + this.touchAdapter = AndroidTouchAdapter; + break; + case 'b2g': + aWindow.addEventListener( + 'ContentStart', + (function(event) { + let content = aWindow.shell.contentBrowser.contentWindow; + content.addEventListener('mozContentEvent', this, false, true); + }).bind(this), false); + break; + default: + break; + } this._processPreferences(); }, @@ -60,6 +77,13 @@ var AccessFu = { this._enabled = true; Logger.info('enable'); + + // Add stylesheet + let stylesheetURL = 'chrome://global/content/accessibility/AccessFu.css'; + this.stylesheet = this.chromeWin.document.createProcessingInstruction( + 'xml-stylesheet', 'href="' + stylesheetURL + '" type="text/css"'); + this.chromeWin.document.insertBefore(this.stylesheet, this.chromeWin.document.firstChild); + this.addPresenter(new VisualPresenter()); // Implicitly add the Android presenter on Android. @@ -88,6 +112,8 @@ var AccessFu = { Logger.info('disable'); + this.chromeWin.document.removeChild(this.stylesheet); + this.presenters.forEach(function(p) { p.detach(); }); this.presenters = []; @@ -130,8 +156,10 @@ var AccessFu = { else this._disable(); - VirtualCursorController.exploreByTouch = ebtPref == ACCESSFU_ENABLE; - Logger.info('Explore by touch:', VirtualCursorController.exploreByTouch); + if (ebtPref == ACCESSFU_ENABLE) + this.touchAdapter.attach(this.chromeWin); + else + this.touchAdapter.detach(this.chromeWin); }, addPresenter: function addPresenter(presenter) { @@ -192,6 +220,14 @@ var AccessFu = { this.presenters.forEach(function(p) { p.viewportChanged(); }); break; } + case 'mozContentEvent': + { + if (aEvent.detail.type == 'accessibility-screenreader') { + let pref = aEvent.detail.enabled + 0; + this._processPreferences(pref, pref); + } + break; + } } }, diff --git a/accessible/src/jsat/Presenters.jsm b/accessible/src/jsat/Presenters.jsm index 5c5cd1a39e7..e16fdc3b1e4 100644 --- a/accessible/src/jsat/Presenters.jsm +++ b/accessible/src/jsat/Presenters.jsm @@ -117,12 +117,6 @@ VisualPresenter.prototype = { attach: function VisualPresenter_attach(aWindow) { this.chromeWin = aWindow; - // Add stylesheet - let stylesheetURL = 'chrome://global/content/accessibility/AccessFu.css'; - this.stylesheet = aWindow.document.createProcessingInstruction( - 'xml-stylesheet', 'href="' + stylesheetURL + '" type="text/css"'); - aWindow.document.insertBefore(this.stylesheet, aWindow.document.firstChild); - // Add highlight box this.highlightBox = this.chromeWin.document. createElementNS('http://www.w3.org/1999/xhtml', 'div'); @@ -138,7 +132,6 @@ VisualPresenter.prototype = { }, detach: function VisualPresenter_detach() { - this.chromeWin.document.removeChild(this.stylesheet); this.highlightBox.parentNode.removeChild(this.highlightBox); this.highlightBox = this.stylesheet = null; }, diff --git a/accessible/src/jsat/TouchAdapter.jsm b/accessible/src/jsat/TouchAdapter.jsm new file mode 100644 index 00000000000..1a7480c8af6 --- /dev/null +++ b/accessible/src/jsat/TouchAdapter.jsm @@ -0,0 +1,402 @@ +/* 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/. */ + +'use strict'; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +var EXPORTED_SYMBOLS = ['TouchAdapter', 'AndroidTouchAdapter']; + +Cu.import('resource://gre/modules/accessibility/Utils.jsm'); + +// We should not be emitting explore events more than 10 times a second. +// It is granular enough to feel natural, and it does not hammer the CPU. +const EXPLORE_THROTTLE = 100; + +var TouchAdapter = { + // minimal swipe distance in inches + SWIPE_MIN_DISTANCE: 0.4, + + // maximum duration of swipe + SWIPE_MAX_DURATION: 400, + + // how straight does a swipe need to be + SWIPE_DIRECTNESS: 1.2, + + // maximum consecutive + MAX_CONSECUTIVE_GESTURE_DELAY: 400, + + // delay before tap turns into dwell + DWELL_THRESHOLD: 500, + + // delay before distinct dwell events + DWELL_REPEAT_DELAY: 300, + + // maximum distance the mouse could move during a tap in inches + TAP_MAX_RADIUS: 0.2, + + attach: function TouchAdapter_attach(aWindow) { + if (this.chromeWin) + return; + + Logger.info('TouchAdapter.attach'); + + this.chromeWin = aWindow; + this._touchPoints = {}; + this._dwellTimeout = 0; + this._prevGestures = {}; + this._lastExploreTime = 0; + this._dpi = this.chromeWin.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils).displayDPI; + + this.glass = this.chromeWin.document. + createElementNS('http://www.w3.org/1999/xhtml', 'div'); + this.glass.id = 'accessfu-glass'; + this.chromeWin.document.documentElement.appendChild(this.glass); + + this.glass.addEventListener('touchend', this, true, true); + this.glass.addEventListener('touchmove', this, true, true); + this.glass.addEventListener('touchstart', this, true, true); + + if (Utils.OS != 'Android') + Mouse2Touch.attach(aWindow); + }, + + detach: function TouchAdapter_detach(aWindow) { + if (!this.chromeWin) + return; + + Logger.info('TouchAdapter.detach'); + + this.glass.removeEventListener('touchend', this, true, true); + this.glass.removeEventListener('touchmove', this, true, true); + this.glass.removeEventListener('touchstart', this, true, true); + this.glass.parentNode.removeChild(this.glass); + + if (Utils.OS != 'Android') + Mouse2Touch.detach(aWindow); + + delete this.chromeWin; + }, + + handleEvent: function TouchAdapter_handleEvent(aEvent) { + let touches = aEvent.changedTouches; + switch (aEvent.type) { + case 'touchstart': + for (var i = 0; i < touches.length; i++) { + let touch = touches[i]; + let touchPoint = new TouchPoint(touch, aEvent.timeStamp, this._dpi); + this._touchPoints[touch.identifier] = touchPoint; + this._lastExploreTime = aEvent.timeStamp + this.SWIPE_MAX_DURATION; + } + this._dwellTimeout = this.chromeWin.setTimeout( + (function () { + this.compileAndEmit(aEvent.timeStamp + this.DWELL_THRESHOLD); + }).bind(this), this.DWELL_THRESHOLD); + break; + case 'touchmove': + for (var i = 0; i < touches.length; i++) { + let touch = touches[i]; + let touchPoint = this._touchPoints[touch.identifier]; + touchPoint.update(touch, aEvent.timeStamp); + } + if (aEvent.timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) { + this.compileAndEmit(aEvent.timeStamp); + this._lastExploreTime = aEvent.timeStamp; + } + break; + case 'touchend': + for (var i = 0; i < touches.length; i++) { + let touch = touches[i]; + let touchPoint = this._touchPoints[touch.identifier]; + touchPoint.update(touch, aEvent.timeStamp); + touchPoint.finish(); + } + this.compileAndEmit(aEvent.timeStamp); + break; + } + }, + + cleanupTouches: function cleanupTouches() { + for (var identifier in this._touchPoints) { + if (!this._touchPoints[identifier].done) + continue; + + delete this._touchPoints[identifier]; + } + }, + + compile: function TouchAdapter_compile(aTime) { + let multiDetails = {}; + + // Compound multiple simultaneous touch gestures. + for (let identifier in this._touchPoints) { + let touchPoint = this._touchPoints[identifier]; + let details = touchPoint.compile(aTime); + + if (!details) + continue; + + details.touches = [identifier]; + + let otherTouches = multiDetails[details.type]; + if (otherTouches) { + otherTouches.touches.push(identifier); + otherTouches.startTime = + Math.min(otherTouches.startTime, touchPoint.startTime); + } else { + details.startTime = touchPoint.startTime; + details.endTime = aTime; + multiDetails[details.type] = details; + } + } + + // Compound multiple consecutive touch gestures. + for each (let details in multiDetails) { + let idhash = details.touches.slice().sort().toString(); + let prevGesture = this._prevGestures[idhash]; + + if (prevGesture) { + // The time delta is calculated as the period between the end of the + // last gesture and the start of this one. + let timeDelta = details.startTime - prevGesture.endTime; + if (timeDelta > this.MAX_CONSECUTIVE_GESTURE_DELAY) { + delete this._prevGestures[idhash]; + } else { + if (details.type == 'tap' && prevGesture.type == 'tap') + details.type = 'doubletap'; + if (details.type == 'tap' && prevGesture.type == 'doubletap') + details.type = 'tripletap'; + if (details.type == 'dwell' && prevGesture.type == 'tap') + details.type = 'taphold'; + } + } + + this._prevGestures[idhash] = details; + } + + this.chromeWin.clearTimeout(this._dwellTimeout); + this.cleanupTouches(); + + return multiDetails; + }, + + emitGesture: function TouchAdapter_emitGesture(aDetails) { + let evt = this.chromeWin.document.createEvent('CustomEvent'); + evt.initCustomEvent('mozAccessFuGesture', true, true, aDetails); + this.chromeWin.dispatchEvent(evt); + }, + + compileAndEmit: function TouchAdapter_compileAndEmit(aTime) { + for each (let details in this.compile(aTime)) { + this.emitGesture(details); + } + } +}; + +/*** + * A TouchPoint represents a single touch from the moment of contact until it is + * lifted from the surface. It is capable of compiling gestures from the scope + * of one single touch. + */ +function TouchPoint(aTouch, aTime, aDPI) { + this.startX = aTouch.screenX; + this.startY = aTouch.screenY; + this.startTime = aTime; + this.distanceTraveled = 0; + this.dpi = aDPI; + this.done = false; +} + +TouchPoint.prototype = { + update: function TouchPoint_update(aTouch, aTime) { + let lastX = this.x; + let lastY = this.y; + this.x = aTouch.screenX; + this.y = aTouch.screenY; + this.time = aTime; + + if (lastX != undefined && lastY != undefined) + this.distanceTraveled += this.getDistanceToCoord(lastX, lastY); + }, + + getDistanceToCoord: function TouchPoint_getDistanceToCoord(aX, aY) { + return Math.sqrt(Math.pow(this.x - aX, 2) + Math.pow(this.y - aY, 2)); + }, + + finish: function TouchPoint_finish() { + this.done = true; + }, + + /** + * Compile a gesture from an individual touch point. This is used by the + * TouchAdapter to compound multiple single gestures in to higher level + * gestures. + */ + compile: function TouchPoint_compile(aTime) { + let directDistance = this.directDistanceTraveled; + let duration = aTime - this.startTime; + + // To be considered a tap/dwell... + if ((this.distanceTraveled / this.dpi) < TouchAdapter.TAP_MAX_RADIUS) { // Didn't travel + if (duration < TouchAdapter.DWELL_THRESHOLD) { + // Mark it as done so we don't use this touch for another gesture. + this.finish(); + return {type: 'tap', x: this.startX, y: this.startY}; + } else if (!this.done && duration == TouchAdapter.DWELL_THRESHOLD) { + return {type: 'dwell', x: this.startX, y: this.startY}; + } + } + + // To be considered a swipe... + if (duration <= TouchAdapter.SWIPE_MAX_DURATION && // Quick enough + (directDistance / this.dpi) >= TouchAdapter.SWIPE_MIN_DISTANCE && // Traveled far + (directDistance * 1.2) >= this.distanceTraveled) { // Direct enough + + let swipeGesture = {x1: this.startX, y1: this.startY, + x2: this.x, y2: this.y}; + let deltaX = this.x - this.startX; + let deltaY = this.y - this.startY; + + if (Math.abs(deltaX) > Math.abs(deltaY)) { + // Horizontal swipe. + if (deltaX > 0) + swipeGesture.type = 'swiperight'; + else + swipeGesture.type = 'swipeleft'; + } else if (Math.abs(deltaX) < Math.abs(deltaY)) { + // Vertical swipe. + if (deltaY > 0) + swipeGesture.type = 'swipedown'; + else + swipeGesture.type = 'swipeup'; + } else { + // A perfect 45 degree swipe?? Not in our book. + return null; + } + + this.finish(); + + return swipeGesture; + } + + // To be considered an explore... + if (!this.done && + duration > TouchAdapter.SWIPE_MAX_DURATION && + (this.distanceTraveled / this.dpi) > TouchAdapter.TAP_MAX_RADIUS) { + return {type: 'explore', x: this.x, y: this.y}; + } + + return null; + }, + + get directDistanceTraveled() { + return this.getDistanceToCoord(this.startX, this.startY); + } +}; + +var Mouse2Touch = { + _MouseToTouchMap: { + mousedown: 'touchstart', + mouseup: 'touchend', + mousemove: 'touchmove' + }, + + attach: function Mouse2Touch_attach(aWindow) { + this.chromeWin = aWindow; + this.chromeWin.addEventListener('mousedown', this, true, true); + this.chromeWin.addEventListener('mouseup', this, true, true); + this.chromeWin.addEventListener('mousemove', this, true, true); + }, + + detach: function Mouse2Touch_detach(aWindow) { + this.chromeWin.removeEventListener('mousedown', this, true, true); + this.chromeWin.removeEventListener('mouseup', this, true, true); + this.chromeWin.removeEventListener('mousemove', this, true, true); + }, + + handleEvent: function Mouse2Touch_handleEvent(aEvent) { + if (aEvent.buttons == 0) + return; + + let name = this._MouseToTouchMap[aEvent.type]; + let evt = this.chromeWin.document.createEvent("touchevent"); + let points = [this.chromeWin.document.createTouch( + this.chromeWin, aEvent.target, 0, + aEvent.pageX, aEvent.pageY, aEvent.screenX, aEvent.screenY, + aEvent.clientX, aEvent.clientY, 1, 1, 0, 0)]; + + // Simulate another touch point at a 5px offset when ctrl is pressed. + if (aEvent.ctrlKey) + points.push(this.chromeWin.document.createTouch( + this.chromeWin, aEvent.target, 1, + aEvent.pageX + 5, aEvent.pageY + 5, + aEvent.screenX + 5, aEvent.screenY + 5, + aEvent.clientX + 5, aEvent.clientY + 5, + 1, 1, 0, 0)); + + // Simulate another touch point at a -5px offset when alt is pressed. + if (aEvent.altKey) + points.push(this.chromeWin.document.createTouch( + this.chromeWin, aEvent.target, 2, + aEvent.pageX - 5, aEvent.pageY - 5, + aEvent.screenX - 5, aEvent.screenY - 5, + aEvent.clientX - 5, aEvent.clientY - 5, + 1, 1, 0, 0)); + + let touches = this.chromeWin.document.createTouchList(points); + if (name == "touchend") { + let empty = this.chromeWin.document.createTouchList(); + evt.initTouchEvent(name, true, true, this.chromeWin, 0, + false, false, false, false, empty, empty, touches); + } else { + evt.initTouchEvent(name, true, true, this.chromeWin, 0, + false, false, false, false, touches, touches, touches); + } + aEvent.target.dispatchEvent(evt); + aEvent.preventDefault(); + aEvent.stopImmediatePropagation(); + } +}; + +var AndroidTouchAdapter = { + attach: function AndroidTouchAdapter_attach(aWindow) { + if (this.chromeWin) + return; + + Logger.info('AndroidTouchAdapter.attach'); + + this.chromeWin = aWindow; + this.chromeWin.addEventListener('mousemove', this, true, true); + this._lastExploreTime = 0; + }, + + detach: function AndroidTouchAdapter_detach(aWindow) { + if (!this.chromeWin) + return; + + Logger.info('AndroidTouchAdapter.detach'); + + this.chromeWin.removeEventListener('mousemove', this, true, true); + delete this.chromeWin; + }, + + handleEvent: function AndroidTouchAdapter_handleEvent(aEvent) { + // On non-Android we use the shift key to simulate touch. + if (Utils.MozBuildApp != 'mobile/android' && !aEvent.shiftKey) + return; + + if (aEvent.timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) { + let evt = this.chromeWin.document.createEvent('CustomEvent'); + evt.initCustomEvent( + 'mozAccessFuGesture', true, true, + {type: 'explore', x: aEvent.screenX, y: aEvent.screenY}); + this.chromeWin.dispatchEvent(evt); + this._lastExploreTime = aEvent.timeStamp; + } + } +}; \ No newline at end of file diff --git a/accessible/src/jsat/Utils.jsm b/accessible/src/jsat/Utils.jsm index 2c86a278639..2ebbf145db4 100644 --- a/accessible/src/jsat/Utils.jsm +++ b/accessible/src/jsat/Utils.jsm @@ -71,6 +71,20 @@ var Utils = { return this.getBrowserApp(aWindow).selectedBrowser.contentDocument; }, + getAllDocuments: function getAllDocuments(aWindow) { + let doc = gAccRetrieval. + getAccessibleFor(this.getCurrentContentDoc(aWindow)). + QueryInterface(Ci.nsIAccessibleDocument); + let docs = []; + function getAllDocuments(aDocument) { + docs.push(aDocument.DOMDocument); + for (let i = 0; i < aDocument.childDocumentCount; i++) + getAllDocuments(aDocument.getChildDocumentAt(i)); + } + getAllDocuments(doc); + return docs; + }, + getViewport: function getViewport(aWindow) { switch (this.MozBuildApp) { case 'mobile/android': @@ -103,6 +117,83 @@ var Utils = { } return null; + }, + + scroll: function scroll(aWindow, aPage, aHorizontal) { + for each (let doc in this.getAllDocuments(aWindow)) { + // First see if we could scroll a window. + let win = doc.defaultView; + if (!aHorizontal && win.scrollMaxY && + ((aPage > 0 && win.scrollY < win.scrollMaxY) || + (aPage < 0 && win.scrollY > 0))) { + win.scroll(0, win.innerHeight); + return true; + } else if (aHorizontal && win.scrollMaxX && + ((aPage > 0 && win.scrollX < win.scrollMaxX) || + (aPage < 0 && win.scrollX > 0))) { + win.scroll(win.innerWidth, 0); + return true; + } + + // Second, try to scroll main section or current target if there is no + // main section. + let main = doc.querySelector('[role=main]') || + doc.querySelector(':target'); + + if (main) { + if ((!aHorizontal && main.clientHeight < main.scrollHeight) || + (aHorizontal && main.clientWidth < main.scrollWidth)) { + let s = win.getComputedStyle(main); + if (!aHorizontal) { + if (s.overflowY == 'scroll' || s.overflowY == 'auto') { + main.scrollTop += aPage * main.clientHeight; + return true; + } + } else { + if (s.overflowX == 'scroll' || s.overflowX == 'auto') { + main.scrollLeft += aPage * main.clientWidth; + return true; + } + } + } + } + } + + return false; + }, + + changePage: function changePage(aWindow, aPage) { + for each (let doc in this.getAllDocuments(aWindow)) { + // Get current main section or active target. + let main = doc.querySelector('[role=main]') || + doc.querySelector(':target'); + if (!main) + continue; + + let mainAcc = gAccRetrieval.getAccessibleFor(main); + if (!mainAcc) + continue; + + let controllers = mainAcc. + getRelationByType(Ci.nsIAccessibleRelation.RELATION_CONTROLLED_BY); + + for (var i=0; controllers.targetsCount > i; i++) { + let controller = controllers.getTarget(i); + // If the section has a controlling slider, it should be considered + // the page-turner. + if (controller.role == Ci.nsIAccessibleRole.ROLE_SLIDER) { + // Sliders are controlled with ctrl+right/left. I just decided :) + let evt = doc.createEvent("KeyboardEvent"); + evt.initKeyEvent('keypress', true, true, null, + true, false, false, false, + (aPage > 0) ? evt.DOM_VK_RIGHT : evt.DOM_VK_LEFT, 0); + controller.DOMNode.dispatchEvent(evt); + return true; + } + } + } + + return false; } }; diff --git a/accessible/src/jsat/VirtualCursorController.jsm b/accessible/src/jsat/VirtualCursorController.jsm index 386cff0c659..cdbe1b3f708 100644 --- a/accessible/src/jsat/VirtualCursorController.jsm +++ b/accessible/src/jsat/VirtualCursorController.jsm @@ -197,12 +197,12 @@ var VirtualCursorController = { attach: function attach(aWindow) { this.chromeWin = aWindow; this.chromeWin.document.addEventListener('keypress', this, true); - this.chromeWin.document.addEventListener('mousemove', this, true); + this.chromeWin.addEventListener('mozAccessFuGesture', this, true); }, detach: function detach() { this.chromeWin.document.removeEventListener('keypress', this, true); - this.chromeWin.document.removeEventListener('mousemove', this, true); + this.chromeWin.removeEventListener('mozAccessFuGesture', this, true); }, handleEvent: function VirtualCursorController_handleEvent(aEvent) { @@ -210,45 +210,68 @@ var VirtualCursorController = { case 'keypress': this._handleKeypress(aEvent); break; - case 'mousemove': - this._handleMousemove(aEvent); + case 'mozAccessFuGesture': + this._handleGesture(aEvent); break; } }, - _handleMousemove: function _handleMousemove(aEvent) { - // Explore by touch is disabled. - if (!this.exploreByTouch) - return; + _handleGesture: function _handleGesture(aEvent) { + let document = Utils.getCurrentContentDoc(this.chromeWin); + let detail = aEvent.detail; + Logger.info('Gesture', detail.type, + '(fingers: ' + detail.touches.length + ')'); - // On non-Android we use the shift key to simulate touch. - if (Utils.OS != 'Android' && !aEvent.shiftKey) - return; - - // We should not be calling moveToPoint more than 10 times a second. - // It is granular enough to feel natural, and it does not hammer the CPU. - if (!this._handleMousemove._lastEventTime || - aEvent.timeStamp - this._handleMousemove._lastEventTime >= 100) { - this.moveToPoint(Utils.getCurrentContentDoc(this.chromeWin), - aEvent.screenX, aEvent.screenY); - this._handleMousemove._lastEventTime = aEvent.timeStamp; + if (detail.touches.length == 1) { + switch (detail.type) { + case 'swiperight': + this.moveForward(document, aEvent.shiftKey); + break; + case 'swipeleft': + this.moveBackward(document, aEvent.shiftKey); + break; + case 'doubletap': + this.activateCurrent(document); + break; + case 'explore': + this.moveToPoint(document, detail.x, detail.y); + break; + } } - aEvent.preventDefault(); - aEvent.stopImmediatePropagation(); + if (detail.touches.length == 3) { + switch (detail.type) { + case 'swiperight': + if (!Utils.scroll(this.chromeWin, -1, true)) + Utils.changePage(this.chromeWin, -1); + break; + case 'swipedown': + Utils.scroll(this.chromeWin, -1); + break; + case 'swipeleft': + if (!Utils.scroll(this.chromeWin, 1, true)) + Utils.changePage(this.chromeWin, 1); + case 'swipeup': + Utils.scroll(this.chromeWin, 1); + break; + } + } }, _handleKeypress: function _handleKeypress(aEvent) { let document = Utils.getCurrentContentDoc(this.chromeWin); let target = aEvent.target; + // Ignore keys with modifiers so the content could take advantage of them. + if (aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) + return; + switch (aEvent.keyCode) { case 0: // an alphanumeric key was pressed, handle it separately. // If it was pressed with either alt or ctrl, just pass through. // If it was pressed with meta, pass the key on without the meta. - if (this.editableState || - aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) + if (this.editableState) return; let key = String.fromCharCode(aEvent.charCode); diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index 2f6676f4bbd..ebdac975d61 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -16,6 +16,7 @@ Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm'); Cu.import('resource://gre/modules/Webapps.jsm'); Cu.import('resource://gre/modules/AlarmService.jsm'); Cu.import('resource://gre/modules/ActivitiesService.jsm'); +Cu.import("resource://gre/modules/PermissionPromptHelper.jsm"); XPCOMUtils.defineLazyServiceGetter(Services, 'env', '@mozilla.org/process/environment;1', diff --git a/browser/components/feeds/src/FeedConverter.js b/browser/components/feeds/src/FeedConverter.js index ae4694b70f0..d3e7a28dc75 100644 --- a/browser/components/feeds/src/FeedConverter.js +++ b/browser/components/feeds/src/FeedConverter.js @@ -285,7 +285,7 @@ FeedConverter.prototype = { // getResponseHeader. if (!httpChannel.requestSucceeded) { // Just give up, but don't forget to cancel the channel first! - request.cancel(0x804b0002); // NS_BINDING_ABORTED + request.cancel(Cr.NS_BINDING_ABORTED); return; } var noSniff = httpChannel.getResponseHeader("X-Moz-Is-Feed"); diff --git a/content/base/public/nsDeprecatedOperationList.h b/content/base/public/nsDeprecatedOperationList.h index e75b70c4e11..3497d317bc7 100644 --- a/content/base/public/nsDeprecatedOperationList.h +++ b/content/base/public/nsDeprecatedOperationList.h @@ -45,6 +45,5 @@ DEPRECATED_OPERATION(InputEncoding) DEPRECATED_OPERATION(MozBeforePaint) DEPRECATED_OPERATION(MozBlobBuilder) DEPRECATED_OPERATION(DOMExceptionCode) -DEPRECATED_OPERATION(NoExposedProps) DEPRECATED_OPERATION(MutationEvent) DEPRECATED_OPERATION(MozSlice) diff --git a/content/base/src/CSPUtils.jsm b/content/base/src/CSPUtils.jsm index 8585ed30e0d..c562f89f622 100644 --- a/content/base/src/CSPUtils.jsm +++ b/content/base/src/CSPUtils.jsm @@ -515,8 +515,10 @@ CSPRep.prototype = { for (var dir in CSPRep.SRC_DIRECTIVES) { var dirv = CSPRep.SRC_DIRECTIVES[dir]; - newRep._directives[dirv] = this._directives[dirv] - .intersectWith(aCSPRep._directives[dirv]); + if (this._directives.hasOwnProperty(dirv)) + newRep._directives[dirv] = this._directives[dirv].intersectWith(aCSPRep._directives[dirv]); + else + newRep._directives[dirv] = aCSPRep._directives[dirv]; } // REPORT_URI @@ -534,12 +536,6 @@ CSPRep.prototype = { newRep._directives[reportURIDir] = aCSPRep._directives[reportURIDir].concat(); } - for (var dir in CSPRep.SRC_DIRECTIVES) { - var dirv = CSPRep.SRC_DIRECTIVES[dir]; - newRep._directives[dirv] = this._directives[dirv] - .intersectWith(aCSPRep._directives[dirv]); - } - newRep._allowEval = this.allowsEvalInScripts && aCSPRep.allowsEvalInScripts; @@ -779,6 +775,8 @@ CSPSourceList.prototype = { var newCSPSrcList = null; + if (!that) return this.clone(); + if (this.isNone() || that.isNone()) newCSPSrcList = CSPSourceList.fromString("'none'"); @@ -1187,32 +1185,32 @@ CSPSource.prototype = { // when a scheme, host or port is undefined.) // port - if (!this._port) - newSource._port = that._port; - else if (!that._port) - newSource._port = this._port; - else if (this._port === "*") - newSource._port = that._port; - else if (that._port === "*") - newSource._port = this._port; - else if (that._port === this._port) - newSource._port = this._port; + if (!this.port) + newSource._port = that.port; + else if (!that.port) + newSource._port = this.port; + else if (this.port === "*") + newSource._port = that.port; + else if (that.port === "*") + newSource._port = this.port; + else if (that.port === this.port) + newSource._port = this.port; else { CSPError(CSPLocalizer.getFormatStr("notIntersectPort", [this.toString(), that.toString()])); return null; } // scheme - if (!this._scheme) - newSource._scheme = that._scheme; - else if (!that._scheme) - newSource._scheme = this._scheme; - if (this._scheme === "*") - newSource._scheme = that._scheme; - else if (that._scheme === "*") - newSource._scheme = this._scheme; - else if (that._scheme === this._scheme) - newSource._scheme = this._scheme; + if (!this.scheme) + newSource._scheme = that.scheme; + else if (!that.scheme) + newSource._scheme = this.scheme; + if (this.scheme === "*") + newSource._scheme = that.scheme; + else if (that.scheme === "*") + newSource._scheme = this.scheme; + else if (that.scheme === this.scheme) + newSource._scheme = this.scheme; else { CSPError(CSPLocalizer.getFormatStr("notIntersectScheme", [this.toString(), that.toString()])); return null; @@ -1227,14 +1225,14 @@ CSPSource.prototype = { // error should still be reported. // host - if (this._host && that._host) { - newSource._host = this._host.intersectWith(that._host); - } else if (this._host) { + if (this.host && that.host) { + newSource._host = this.host.intersectWith(that.host); + } else if (this.host) { CSPError(CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost", [that.toString()])); - newSource._host = this._host.clone(); - } else if (that._host) { + newSource._host = this.host.clone(); + } else if (that.host) { CSPError(CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost", [this.toString()])); - newSource._host = that._host.clone(); + newSource._host = that.host.clone(); } else { CSPError(CSPLocalizer.getFormatStr("intersectingSourcesWithUndefinedHosts", [this.toString(), that.toString()])); newSource._host = CSPHost.fromString("*"); diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index ee604ca74a5..d8e4e1f209f 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -76,6 +76,7 @@ #include "nsIDOMFormData.h" #include "DictionaryHelpers.h" #include "mozilla/Attributes.h" +#include "nsIPermissionManager.h" #include "nsWrapperCacheInlines.h" #include "nsStreamListenerWrapper.h" @@ -572,9 +573,16 @@ nsXMLHttpRequest::InitParameters(bool aAnon, bool aSystem) return; } - nsCOMPtr uri; - doc->NodePrincipal()->GetURI(getter_AddRefs(uri)); - if (!nsContentUtils::URIIsChromeOrInPref(uri, "dom.systemXHR.whitelist")) { + nsCOMPtr principal = doc->NodePrincipal(); + nsCOMPtr permMgr = + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); + if (!permMgr) + return; + + PRUint32 permission; + nsresult rv = + permMgr->TestPermissionFromPrincipal(principal, "systemXHR", &permission); + if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) { return; } } diff --git a/content/base/test/test_XHR_anon.html b/content/base/test/test_XHR_anon.html index 03621df3b10..95357a9583e 100644 --- a/content/base/test/test_XHR_anon.html +++ b/content/base/test/test_XHR_anon.html @@ -28,12 +28,11 @@ function runTests() { authMgr.setAuthIdentity("http", "example.com", 80, "basic", "testrealm", "", "example.com", "user1", "password1"); - SpecialPowers.setCharPref("dom.systemXHR.whitelist", - "http://mochi.test:8888"); + SpecialPowers.addPermission("systemXHR", true, document); return function tearDown() { authMgr.clearAll(); - SpecialPowers.clearUserPref("dom.systemXHR.whitelist"); + SpecialPowers.removePermission("systemXHR", document); SimpleTest.finish(); } }()); diff --git a/content/base/test/test_XHR_parameters.html b/content/base/test/test_XHR_parameters.html index 93857f1a525..8b78d00df55 100644 --- a/content/base/test/test_XHR_parameters.html +++ b/content/base/test/test_XHR_parameters.html @@ -84,11 +84,11 @@ function runTests() { // ...and once with privileges. havePrivileges = true; - SpecialPowers.setCharPref("dom.systemXHR.whitelist", - "http://mochi.test:8888"); + SpecialPowers.addPermission("systemXHR", true, document); + validParameters.forEach(testValidParameter); invalidParameters.forEach(testInvalidParameter); - SpecialPowers.clearUserPref("dom.systemXHR.whitelist"); + SpecialPowers.removePermission("systemXHR", document); SimpleTest.finish(); } diff --git a/content/base/test/test_XHR_system.html b/content/base/test/test_XHR_system.html index cfa15e652c9..30cce814387 100644 --- a/content/base/test/test_XHR_system.html +++ b/content/base/test/test_XHR_system.html @@ -16,12 +16,12 @@ + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/dom/file/ArchiveEvent.cpp b/dom/file/ArchiveEvent.cpp index a6e4b2e7fc0..00c07fa518c 100644 --- a/dom/file/ArchiveEvent.cpp +++ b/dom/file/ArchiveEvent.cpp @@ -99,7 +99,7 @@ ArchiveReaderEvent::ShareMainThread() // Just to be sure, if something goes wrong, the mimetype is an empty string: nsCString type; - if (GetType(ext, type) == NS_OK) + if (NS_SUCCEEDED(GetType(ext, type))) item->SetType(type); } diff --git a/dom/file/ArchiveRequest.cpp b/dom/file/ArchiveRequest.cpp index 116e7320cd1..15c8a72ecfd 100644 --- a/dom/file/ArchiveRequest.cpp +++ b/dom/file/ArchiveRequest.cpp @@ -111,7 +111,7 @@ nsresult ArchiveRequest::ReaderReady(nsTArray >& aFileList, nsresult aStatus) { - if (aStatus != NS_OK) { + if (NS_FAILED(aStatus)) { FireError(aStatus); return NS_OK; } @@ -183,7 +183,7 @@ ArchiveRequest::GetFilenamesResult(JSContext* aCx, jsval item = STRING_TO_JSVAL(str); - if (rv != NS_OK || !JS_SetElement(aCx, array, i, &item)) { + if (NS_FAILED(rv) || !JS_SetElement(aCx, array, i, &item)) { return NS_ERROR_FAILURE; } } diff --git a/dom/file/ArchiveZipEvent.cpp b/dom/file/ArchiveZipEvent.cpp index 89e16e215ad..450db37736b 100644 --- a/dom/file/ArchiveZipEvent.cpp +++ b/dom/file/ArchiveZipEvent.cpp @@ -86,7 +86,7 @@ ArchiveReaderZipEvent::Exec() nsCOMPtr inputStream; rv = mArchiveReader->GetInputStream(getter_AddRefs(inputStream)); - if (rv != NS_OK || !inputStream) { + if (NS_FAILED(rv) || !inputStream) { return RunShare(NS_ERROR_UNEXPECTED); } @@ -99,7 +99,7 @@ ArchiveReaderZipEvent::Exec() PRUint64 size; rv = mArchiveReader->GetSize(&size); - if (rv != NS_OK) { + if (NS_FAILED(rv)) { return RunShare(NS_ERROR_UNEXPECTED); } @@ -112,7 +112,7 @@ ArchiveReaderZipEvent::Exec() PRUint32 ret; rv = inputStream->Read((char*)buffer, sizeof(buffer), &ret); - if (rv != NS_OK || ret != sizeof(buffer)) { + if (NS_FAILED(rv) || ret != sizeof(buffer)) { return RunShare(NS_ERROR_UNEXPECTED); } @@ -137,7 +137,7 @@ ArchiveReaderZipEvent::Exec() PRUint32 ret; rv = inputStream->Read((char*)¢ralStruct, ZIPCENTRAL_SIZE, &ret); - if (rv != NS_OK || ret != ZIPCENTRAL_SIZE) { + if (NS_FAILED(rv) || ret != ZIPCENTRAL_SIZE) { return RunShare(NS_ERROR_UNEXPECTED); } @@ -154,7 +154,7 @@ ArchiveReaderZipEvent::Exec() // Read the name: char* filename = (char*)PR_Malloc(filenameLen + 1); rv = inputStream->Read(filename, filenameLen, &ret); - if (rv != NS_OK || ret != filenameLen) { + if (NS_FAILED(rv) || ret != filenameLen) { return RunShare(NS_ERROR_UNEXPECTED); } diff --git a/dom/file/ArchiveZipFile.cpp b/dom/file/ArchiveZipFile.cpp index d1c99a1889e..69423152ce3 100644 --- a/dom/file/ArchiveZipFile.cpp +++ b/dom/file/ArchiveZipFile.cpp @@ -17,22 +17,29 @@ USING_FILE_NAMESPACE // a internat input stream object -class ArchiveInputStream MOZ_FINAL : public nsIInputStream +class ArchiveInputStream MOZ_FINAL : public nsIInputStream, + public nsISeekableStream { public: - ArchiveInputStream(ArchiveReader* aReader, + ArchiveInputStream(PRUint64 aParentSize, + nsIInputStream* aInputStream, nsString& aFilename, PRUint32 aStart, PRUint32 aLength, ZipCentral& aCentral) - : mArchiveReader(aReader), - mCentral(aCentral), + : mCentral(aCentral), mFilename(aFilename), mStart(aStart), mLength(aLength), mStatus(NotStarted) { MOZ_COUNT_CTOR(ArchiveInputStream); + + // Reset the data: + memset(&mData, 0, sizeof(mData)); + + mData.parentSize = aParentSize; + mData.inputStream = aInputStream; } virtual ~ArchiveInputStream() @@ -43,12 +50,12 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM private: nsresult Init(); private: // data - nsRefPtr mArchiveReader; ZipCentral mCentral; nsString mFilename; PRUint32 mStart; @@ -63,14 +70,20 @@ private: // data } mStatus; struct { + PRUint64 parentSize; nsCOMPtr inputStream; + unsigned char input[ZIP_CHUNK]; PRUint32 sizeToBeRead; + PRUint32 cursor; + bool compressed; // a zip file can contain stored or compressed files } mData; }; -NS_IMPL_THREADSAFE_ISUPPORTS1(ArchiveInputStream, nsIInputStream) +NS_IMPL_THREADSAFE_ISUPPORTS2(ArchiveInputStream, + nsIInputStream, + nsISeekableStream) nsresult ArchiveInputStream::Init() @@ -82,21 +95,12 @@ ArchiveInputStream::Init() if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY; - // Reset the data: - memset(&mData, 0, sizeof(mData)); mData.sizeToBeRead = ArchiveZipItem::StrToInt32(mCentral.size); PRUint32 offset = ArchiveZipItem::StrToInt32(mCentral.localhdr_offset); - PRUint64 size; - rv = mArchiveReader->GetSize(&size); - NS_ENSURE_SUCCESS(rv, rv); - - // The file is corrupt - if (offset + ZIPLOCAL_SIZE > size) - return NS_ERROR_UNEXPECTED; - mArchiveReader->GetInputStream(getter_AddRefs(mData.inputStream)); - if (rv != NS_OK || !mData.inputStream) + // The file is corrupt + if (offset + ZIPLOCAL_SIZE > mData.parentSize) return NS_ERROR_UNEXPECTED; // From the input stream to a seekable stream @@ -111,7 +115,7 @@ ArchiveInputStream::Init() PRUint32 ret; rv = mData.inputStream->Read((char*)buffer, ZIPLOCAL_SIZE, &ret); - if (rv != NS_OK || ret != ZIPLOCAL_SIZE) + if (NS_FAILED(rv) || ret != ZIPLOCAL_SIZE) return NS_ERROR_UNEXPECTED; // Signature check: @@ -127,7 +131,7 @@ ArchiveInputStream::Init() ArchiveZipItem::StrToInt16(local.extrafield_len); // The file is corrupt if there is not enough data - if (offset + mData.sizeToBeRead > size) + if (offset + mData.sizeToBeRead > mData.parentSize) return NS_ERROR_UNEXPECTED; // Data starts here: @@ -138,20 +142,8 @@ ArchiveInputStream::Init() // We have to skip the first mStart bytes: if (mStart != 0) { - PRUint32 done(mStart); - PRUint32 ret; - char buffer[1024]; - - while (done > 0) { - rv = Read(buffer, done > sizeof(buffer) ? sizeof(buffer) : done, &ret); - if (rv != NS_OK) - return rv; - - if (ret == 0) - return NS_ERROR_UNEXPECTED; - - done -= ret; - } + rv = Seek(NS_SEEK_SET, mStart); + NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; @@ -171,7 +163,7 @@ ArchiveInputStream::Close() NS_IMETHODIMP ArchiveInputStream::Available(PRUint64* _retval) { - *_retval = mLength - mZs.total_out - mStart; + *_retval = mLength - mData.cursor - mStart; return NS_OK; } @@ -190,7 +182,7 @@ ArchiveInputStream::Read(char* aBuffer, mStatus = Started; rv = Init(); - if (rv != NS_OK) + if (NS_FAILED(rv)) return rv; // Let's set avail_out to -1 so we read something from the stream. @@ -208,10 +200,11 @@ ArchiveInputStream::Read(char* aBuffer, { rv = mData.inputStream->Read(aBuffer, (mData.sizeToBeRead > aCount ? - aCount : mData.sizeToBeRead), + aCount : mData.sizeToBeRead), _retval); - if (rv == NS_OK) { + if (NS_SUCCEEDED(rv)) { mData.sizeToBeRead -= *_retval; + mData.cursor += *_retval; if (mData.sizeToBeRead == 0) mStatus = Done; @@ -228,7 +221,7 @@ ArchiveInputStream::Read(char* aBuffer, (mData.sizeToBeRead > sizeof(mData.input) ? sizeof(mData.input) : mData.sizeToBeRead), &ret); - if (rv != NS_OK) + if (NS_FAILED(rv)) return rv; // Terminator: @@ -253,6 +246,7 @@ ArchiveInputStream::Read(char* aBuffer, mStatus = Done; *_retval = aCount - mZs.avail_out; + mData.cursor += *_retval; return NS_OK; } @@ -275,6 +269,76 @@ ArchiveInputStream::IsNonBlocking(bool* _retval) return NS_OK; } +NS_IMETHODIMP +ArchiveInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset) +{ + PRInt64 pos = aOffset; + + switch (aWhence) { + case NS_SEEK_SET: + break; + + case NS_SEEK_CUR: + pos += mData.cursor; + break; + + case NS_SEEK_END: + pos += mLength; + break; + + default: + NS_NOTREACHED("unexpected whence value"); + return NS_ERROR_UNEXPECTED; + } + + if (pos == PRInt64(mData.cursor)) + return NS_OK; + + if (pos < 0 || pos >= mLength) + return NS_ERROR_FAILURE; + + // We have to terminate the previous operation: + nsresult rv; + if (mStatus != NotStarted) { + rv = Close(); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Reset the cursor: + mData.cursor = 0; + + // Note: This code is heavy but inflate does not have any seek() support: + PRUint32 ret; + char buffer[1024]; + while (pos > 0) { + rv = Read(buffer, pos > PRInt64(sizeof(buffer)) ? sizeof(buffer) : pos, &ret); + if (NS_FAILED(rv)) + return rv; + + if (ret == 0) + return NS_ERROR_UNEXPECTED; + + pos -= ret; + } + + return NS_OK; +} + +NS_IMETHODIMP +ArchiveInputStream::Tell(PRInt64 *aResult) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + LL_UI2L(*aResult, mData.cursor); + return NS_OK; +} + +NS_IMETHODIMP +ArchiveInputStream::SetEOF() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} // ArchiveZipFile @@ -284,7 +348,17 @@ ArchiveZipFile::GetInternalStream(nsIInputStream** aStream) if (mLength > PR_INT32_MAX) return NS_ERROR_FAILURE; - nsRefPtr stream = new ArchiveInputStream(mArchiveReader, + PRUint64 size; + nsresult rv = mArchiveReader->GetSize(&size); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr inputStream; + rv = mArchiveReader->GetInputStream(getter_AddRefs(inputStream)); + if (NS_FAILED(rv) || !inputStream) + return NS_ERROR_UNEXPECTED; + + nsRefPtr stream = new ArchiveInputStream(size, + inputStream, mFilename, mStart, mLength, diff --git a/dom/file/test/Makefile.in b/dom/file/test/Makefile.in index 853285039f7..0b213b93739 100644 --- a/dom/file/test/Makefile.in +++ b/dom/file/test/Makefile.in @@ -30,6 +30,7 @@ MOCHITEST_FILES = \ test_write_read_data.html \ test_workers.html \ test_archivereader.html \ + test_archivereader_zip_in_zip.html \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/dom/file/test/test_archivereader_zip_in_zip.html b/dom/file/test/test_archivereader_zip_in_zip.html new file mode 100644 index 00000000000..fa6f7e42bcf --- /dev/null +++ b/dom/file/test/test_archivereader_zip_in_zip.html @@ -0,0 +1,128 @@ + + + + Archive Reader Zip-In-Zip Test + + + + + + + + + + +

+ +

+ + + + diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index c296e4028b2..ac236dbb995 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -947,15 +947,13 @@ ContentChild::RecvLastPrivateDocShellDestroyed() bool ContentChild::RecvFilePathUpdate(const nsString& path, const nsCString& aReason) { - // data strings will have the format of - // reason:path - nsString data; - CopyASCIItoUTF16(aReason, data); - data.Append(NS_LITERAL_STRING(":")); - data.Append(path); + nsCOMPtr file; + NS_NewLocalFile(path, false, getter_AddRefs(file)); + nsString reason; + CopyASCIItoUTF16(aReason, reason); nsCOMPtr obs = mozilla::services::GetObserverService(); - obs->NotifyObservers(nullptr, "file-watcher-update", data.get()); + obs->NotifyObservers(file, "file-watcher-update", reason.get()); return true; } diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 0ff79710eab..85e20ddc5a1 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -370,6 +370,7 @@ ContentParent::Init() obs->AddObserver(this, "child-gc-request", false); obs->AddObserver(this, "child-cc-request", false); obs->AddObserver(this, "last-pb-context-exited", false); + obs->AddObserver(this, "file-watcher-update", false); #ifdef MOZ_WIDGET_GONK obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, false); #endif @@ -543,6 +544,7 @@ ContentParent::ActorDestroy(ActorDestroyReason why) obs->RemoveObserver(static_cast(this), "child-gc-request"); obs->RemoveObserver(static_cast(this), "child-cc-request"); obs->RemoveObserver(static_cast(this), "last-pb-context-exited"); + obs->RemoveObserver(static_cast(this), "file-watcher-update"); #ifdef MOZ_WIDGET_GONK obs->RemoveObserver(static_cast(this), NS_VOLUME_STATE_CHANGED); #endif @@ -697,8 +699,6 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL) //Sending all information to content process unused << SendAppInfo(version, buildID); } - - mFileWatchers.Init(); } ContentParent::~ContentParent() @@ -938,9 +938,6 @@ ContentParent::Observe(nsISupports* aSubject, const PRUnichar* aData) { if (!strcmp(aTopic, "xpcom-shutdown") && mSubprocess) { - - mFileWatchers.Clear(); - Close(); NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess"); } @@ -995,6 +992,18 @@ ContentParent::Observe(nsISupports* aSubject, else if (!strcmp(aTopic, "last-pb-context-exited")) { unused << SendLastPrivateDocShellDestroyed(); } + else if (!strcmp(aTopic, "file-watcher-update")) { + nsCString creason; + CopyUTF16toUTF8(aData, creason); + nsCOMPtr file = do_QueryInterface(aSubject); + if (!file) { + return NS_OK; + } + + nsString path; + file->GetPath(path); + unused << SendFilePathUpdate(path, creason); + } #ifdef MOZ_WIDGET_GONK else if(!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) { nsCOMPtr vol = do_QueryInterface(aSubject); @@ -1739,64 +1748,5 @@ ContentParent::RecvPrivateDocShellsExist(const bool& aExist) return true; } -bool -ContentParent::RecvAddFileWatch(const nsString& root) -{ - nsRefPtr f; - if (mFileWatchers.Get(root, getter_AddRefs(f))) { - f->mUsageCount++; - return true; - } - - f = new WatchedFile(this, root); - mFileWatchers.Put(root, f); - - f->Watch(); - return true; -} - -bool -ContentParent::RecvRemoveFileWatch(const nsString& root) -{ - nsRefPtr f; - bool result = mFileWatchers.Get(root, getter_AddRefs(f)); - if (!result) { - return true; - } - - if (!f) - return true; - - f->mUsageCount--; - - if (f->mUsageCount > 0) { - return true; - } - - f->Unwatch(); - mFileWatchers.Remove(root); - return true; -} - -NS_IMPL_ISUPPORTS1(ContentParent::WatchedFile, nsIFileUpdateListener) - -nsresult -ContentParent::WatchedFile::Update(const char* aReason, nsIFile* aFile) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - nsString path; - aFile->GetPath(path); - - unused << mParent->SendFilePathUpdate(path, nsDependentCString(aReason)); - -#ifdef DEBUG - nsCString cpath; - aFile->GetNativePath(cpath); - printf("ContentParent::WatchedFile::Update: %s -- %s\n", cpath.get(), aReason); -#endif - return NS_OK; -} - } // namespace dom } // namespace mozilla diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index e772d0f06b5..9d48379e6e5 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -23,7 +23,6 @@ #include "nsIMemoryReporter.h" #include "nsCOMArray.h" #include "nsDataHashtable.h" -#include "nsInterfaceHashtable.h" #include "nsHashKeys.h" class mozIApplication; @@ -274,9 +273,6 @@ private: virtual bool RecvPrivateDocShellsExist(const bool& aExist); - virtual bool RecvAddFileWatch(const nsString& root); - virtual bool RecvRemoveFileWatch(const nsString& root); - virtual void ProcessingError(Result what) MOZ_OVERRIDE; GeckoChildProcessHost* mSubprocess; @@ -297,34 +293,6 @@ private: const nsString mAppManifestURL; nsRefPtr mMessageManager; - class WatchedFile MOZ_FINAL : public nsIFileUpdateListener { - public: - WatchedFile(ContentParent* aParent, const nsString& aPath) - : mParent(aParent) - , mUsageCount(1) - { - NS_NewLocalFile(aPath, false, getter_AddRefs(mFile)); - } - - NS_DECL_ISUPPORTS - NS_DECL_NSIFILEUPDATELISTENER - - void Watch() { - mFile->Watch(this); - } - - void Unwatch() { - mFile->Watch(this); - } - - nsRefPtr mParent; - PRInt32 mUsageCount; - nsCOMPtr mFile; - }; - - // This is a cache of all of the registered file watchers. - nsInterfaceHashtable mFileWatchers; - friend class CrashReporterParent; }; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 905fcd49272..8a2b76a81e5 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -298,9 +298,6 @@ parent: // Notify the parent of the presence or absence of private docshells PrivateDocShellsExist(bool aExist); - AddFileWatch(nsString filepath); - RemoveFileWatch(nsString filepath); - both: AsyncMessage(nsString aMessage, ClonedMessageData aData); }; diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 1cb9963c620..39687711dd4 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -127,8 +127,6 @@ MediaLoadDecodeError=Media resource %S could not be decoded. MozBlobBuilderWarning=Use of MozBlobBuilder is deprecated. Use Blob constructor instead. # LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name" DOMExceptionCodeWarning=Use of DOMException's code attribute is deprecated. Use name instead. -# LOCALIZATION NOTE: Do not translate "__exposedProps__" -NoExposedPropsWarning=Exposing chrome JS objects to content without __exposedProps__ is insecure and deprecated. See https://developer.mozilla.org/en/XPConnect_wrappers for more information. # LOCALIZATION NOTE: Do not translate "Mutation Event" and "MutationObserver" MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver instead. # LOCALIZATION NOTE: Do not translate "Blob", "mozSlice", or "slice" diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index ed50e97fc5e..02c10ca9a1f 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -153,7 +153,9 @@ CompositorParent::CompositorParent(nsIWidget* aWidget, CompositorLoop()->PostTask(FROM_HERE, NewRunnableFunction(&AddCompositor, this, &mCompositorID)); - sCurrentCompositor = this; + if (!sCurrentCompositor) { + sCurrentCompositor = this; + } } PlatformThreadId diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 2869ef50ab4..4881d898965 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -887,15 +887,20 @@ ClonedBlockDepth(BytecodeEmitter *bce) static uint16_t AliasedNameToSlot(JSScript *script, PropertyName *name) { + /* + * Beware: BindingIter may contain more than one Binding for a given name + * (in the case of |function f(x,x) {}|) but only one will be aliased. + */ unsigned slot = CallObject::RESERVED_SLOTS; - BindingIter bi(script->bindings); - for (; bi->name() != name; bi++) { - if (bi->aliased()) + for (BindingIter bi(script->bindings); ; bi++) { + if (bi->aliased()) { + if (bi->name() == name) + return slot; slot++; + } } - JS_ASSERT(bi->aliased()); - return slot; + return 0; } static bool diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 59b1fa5dda2..12635fbd9d2 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -324,12 +324,25 @@ Statistics::gcDuration(int64_t *total, int64_t *maxPause) } } +void +Statistics::sccDurations(int64_t *total, int64_t *maxPause) +{ + *total = *maxPause = 0; + for (size_t i = 0; i < sccTimes.length(); i++) { + *total += sccTimes[i]; + *maxPause = Max(*maxPause, sccTimes[i]); + } +} + bool Statistics::formatData(StatisticsSerializer &ss, uint64_t timestamp) { int64_t total, longest; gcDuration(&total, &longest); + int64_t sccTotal, sccLongest; + sccDurations(&sccTotal, &sccLongest); + double mmu20 = computeMMU(20 * PRMJ_USEC_PER_MSEC); double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC); @@ -341,6 +354,8 @@ Statistics::formatData(StatisticsSerializer &ss, uint64_t timestamp) ss.appendNumber("Total Compartments", "%d", "", compartmentCount); ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100)); ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100)); + ss.appendDecimal("SCC Sweep Total", "ms", t(sccTotal)); + ss.appendDecimal("SCC Sweep Max Pause", "ms", t(sccLongest)); if (slices.length() > 1 || ss.isJSON()) ss.appendDecimal("Max Pause", "ms", t(longest)); else @@ -488,6 +503,7 @@ Statistics::beginGC() PodArrayZero(phaseTimes); slices.clearAndFree(); + sccTimes.clearAndFree(); nonincrementalReason = NULL; preBytes = runtime->gcBytes; @@ -508,6 +524,9 @@ Statistics::endGC() int64_t total, longest; gcDuration(&total, &longest); + int64_t sccTotal, sccLongest; + sccDurations(&sccTotal, &sccLongest); + (*cb)(JS_TELEMETRY_GC_IS_COMPARTMENTAL, collectedCount == compartmentCount ? 0 : 1); (*cb)(JS_TELEMETRY_GC_MS, t(total)); (*cb)(JS_TELEMETRY_GC_MAX_PAUSE_MS, t(longest)); @@ -517,6 +536,8 @@ Statistics::endGC() (*cb)(JS_TELEMETRY_GC_MARK_GRAY_MS, t(phaseTimes[PHASE_MARK_GRAY])); (*cb)(JS_TELEMETRY_GC_NON_INCREMENTAL, !!nonincrementalReason); (*cb)(JS_TELEMETRY_GC_INCREMENTAL_DISABLED, !runtime->gcIncrementalEnabled); + (*cb)(JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS, t(sccTotal)); + (*cb)(JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS, t(sccLongest)); double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC); (*cb)(JS_TELEMETRY_GC_MMU_50, mmu50 * 100); @@ -605,6 +626,21 @@ Statistics::endPhase(Phase phase) Probes::GCEndSweepPhase(); } +int64_t +Statistics::beginSCC() +{ + return PRMJ_Now(); +} + +void +Statistics::endSCC(unsigned scc, int64_t start) +{ + if (scc >= sccTimes.length() && !sccTimes.resize(scc + 1)) + return; + + sccTimes[scc] += PRMJ_Now() - start; +} + /* * MMU (minimum mutator utilization) is a measure of how much garbage collection * is affecting the responsiveness of the system. MMU measurements are given diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 1bf9f19e81d..dfd00dbc946 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -80,6 +80,9 @@ struct Statistics { counts[s]++; } + int64_t beginSCC(); + void endSCC(unsigned scc, int64_t start); + jschar *formatMessage(); jschar *formatJSON(uint64_t timestamp); @@ -134,10 +137,14 @@ struct Statistics { /* Allocated space before the GC started. */ size_t preBytes; + /* Sweep times for SCCs of compartments. */ + Vector sccTimes; + void beginGC(); void endGC(); void gcDuration(int64_t *total, int64_t *maxPause); + void sccDurations(int64_t *total, int64_t *maxPause); void printStats(); bool formatData(StatisticsSerializer &ss, uint64_t timestamp); @@ -168,6 +175,17 @@ struct AutoPhase { JS_DECL_USE_GUARD_OBJECT_NOTIFIER }; +struct AutoSCC { + AutoSCC(Statistics &stats, unsigned scc JS_GUARD_OBJECT_NOTIFIER_PARAM) + : stats(stats), scc(scc) { JS_GUARD_OBJECT_NOTIFIER_INIT; start = stats.beginSCC(); } + ~AutoSCC() { stats.endSCC(scc, start); } + + Statistics &stats; + unsigned scc; + int64_t start; + JS_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + } /* namespace gcstats */ } /* namespace js */ diff --git a/js/src/jit-test/tests/basic/testBug783441.js b/js/src/jit-test/tests/basic/testBug783441.js new file mode 100644 index 00000000000..088ecc8b176 --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug783441.js @@ -0,0 +1 @@ +assertEq((function(x, y, x) { return (function() x+y)(); })(1,2,5), 7); diff --git a/js/src/jit-test/tests/basic/testBug783543.js b/js/src/jit-test/tests/basic/testBug783543.js new file mode 100644 index 00000000000..80e3495fc77 --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug783543.js @@ -0,0 +1,11 @@ +// |jit-test| error:ReferenceError + +try { + evaluate(" (function(c) { const x = 1; for (x in null); })();"); + var expect = "Passed"; +} catch ( e ) { + result = expect; +} +schedulegc(10); +eval("var o = new MyObject(); var result = 0; for (var o in foo) { result += this[o]; } ") +function MyObject() {} diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 527d43840f3..5cd61d7c0ad 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -7117,16 +7117,16 @@ JS_SetGCZeal(JSContext *cx, uint8_t zeal, uint32_t frequency) VerifyBarriers(rt, PostBarrierVerifier); } - bool schedule = zeal >= js::gc::ZealAllocValue; - rt->gcZeal_ = zeal; - rt->gcZealFrequency = frequency; - rt->gcNextScheduled = schedule ? frequency : 0; - #ifdef JS_METHODJIT /* In case JSCompartment::compileBarriers() changed... */ for (CompartmentsIter c(rt); !c.done(); c.next()) mjit::ClearAllFrames(c); #endif + + bool schedule = zeal >= js::gc::ZealAllocValue; + rt->gcZeal_ = zeal; + rt->gcZealFrequency = frequency; + rt->gcNextScheduled = schedule ? frequency : 0; } JS_PUBLIC_API(void) diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index dec1def3977..e81e98eb069 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -300,6 +300,9 @@ struct JSCompartment size_t gcMallocAndFreeBytes; size_t gcTriggerMallocAndFreeBytes; + /* During GC, stores the index of this compartment in rt->compartments. */ + unsigned index; + private: /* * Malloc counter to measure memory pressure for GC scheduling. It runs from diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 230a23c62f3..1b11b6853a4 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -84,7 +84,9 @@ enum { JS_TELEMETRY_GC_MMU_50, JS_TELEMETRY_GC_RESET, JS_TELEMETRY_GC_INCREMENTAL_DISABLED, - JS_TELEMETRY_GC_NON_INCREMENTAL + JS_TELEMETRY_GC_NON_INCREMENTAL, + JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS, + JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS }; typedef void diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 35576bc02c6..1b30a83acd6 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3482,6 +3482,141 @@ ValidateIncrementalMarking(JSRuntime *rt) } #endif +/* + * If compartment A has an edge to an unmarked object in compartment B, then we + * must not sweep A in a later slice than we sweep B. That's because a write + * barrier in A that could lead to the unmarked object in B becoming + * marked. However, if we had already swept that object, we would be in trouble. + * + * If we consider these dependencies as a graph, then all the compartments in + * any strongly-connected component of this graph must be swept in the same + * slice. This class is used to compute these strongly connected components via + * Tarjan's algorithm. + */ +class PartitionCompartments +{ + typedef unsigned Node; + typedef Vector NodeVector; + typedef Vector BoolVector; + + static const Node Undefined = Node(-1); + + JSRuntime *runtime; + + /* + * The value of clock ticks monotonically upward as each new compartment is + * discovered by the algorithm. When a new SCC is found, it is assigned a + * number from nextSCC. + */ + Node clock, nextSCC; + + /* + * Compartments have an index based on their order in rt->compartments. The + * index is used as a subscript into the arrays below. + * + * discoveryTime[comp]: The |clock| value when comp was first explored. + * lowLink[comp]: The minimal discovery time of any compartment reachable + * from |comp|. + * stack: List of explored compartments that haven't been assigned to an SCC. + * scc[comp]: SCC number that |comp| is in. + * onStack[comp]: Whether |comp| in in |stack|. + */ + NodeVector discoveryTime, lowLink, stack, scc; + BoolVector onStack; + + bool fail_; + + void processNode(Node v); + void fail() { fail_ = true; } + bool failed() { return fail_; } + + public: + PartitionCompartments(JSRuntime *rt); + void partition(); + unsigned getSCC(JSCompartment *comp) { return failed() ? 0 : scc[comp->index]; } +}; + +const PartitionCompartments::Node PartitionCompartments::Undefined; + +PartitionCompartments::PartitionCompartments(JSRuntime *rt) + : runtime(rt), clock(0), nextSCC(0), fail_(false) +{ + size_t n = runtime->compartments.length(); + if (!discoveryTime.reserve(n) || + !lowLink.reserve(n) || + !scc.reserve(n) || + !onStack.reserve(n) || + !stack.reserve(n)) + { + fail(); + return; + } + + for (Node v = 0; v < runtime->compartments.length(); v++) { + runtime->compartments[v]->index = v; + discoveryTime.infallibleAppend(Undefined); + lowLink.infallibleAppend(Undefined); + scc.infallibleAppend(Undefined); + onStack.infallibleAppend(false); + } +} + +/* See the Wikipedia article "Tarjan's strongly connected components algorithm". */ +void +PartitionCompartments::processNode(Node v) +{ + int stackDummy; + if (failed() || !JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(runtime), &stackDummy)) { + fail(); + return; + } + + discoveryTime[v] = clock; + lowLink[v] = clock; + clock++; + stack.infallibleAppend(v); + onStack[v] = true; + + JSCompartment *comp = runtime->compartments[v]; + + for (WrapperMap::Enum e(comp->crossCompartmentWrappers); !e.empty(); e.popFront()) { + if (e.front().key.kind == CrossCompartmentKey::StringWrapper) + continue; + + Cell *other = e.front().key.wrapped; + if (other->isMarked(BLACK) && !other->isMarked(GRAY)) + continue; + + Node w = other->compartment()->index; + + if (discoveryTime[w] == Undefined) { + processNode(w); + lowLink[v] = Min(lowLink[v], lowLink[w]); + } else if (onStack[w]) { + lowLink[v] = Min(lowLink[v], discoveryTime[w]); + } + } + + if (lowLink[v] == discoveryTime[v]) { + Node w; + do { + w = stack.popCopy(); + onStack[w] = false; + scc[w] = nextSCC; + } while (w != v); + nextSCC++; + } +} + +void +PartitionCompartments::partition() +{ + for (Node n = 0; n < runtime->compartments.length(); n++) { + if (discoveryTime[n] == Undefined) + processNode(n); + } +} + static void BeginSweepPhase(JSRuntime *rt) { @@ -3542,6 +3677,9 @@ BeginSweepPhase(JSRuntime *rt) /* Detach unreachable debuggers and global objects from each other. */ Debugger::sweepAll(&fop); + PartitionCompartments partition(rt); + partition.partition(); + { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_COMPARTMENTS); @@ -3554,6 +3692,7 @@ BeginSweepPhase(JSRuntime *rt) bool releaseTypes = ReleaseObservedTypes(rt); for (CompartmentsIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); if (c->isCollecting()) c->sweep(&fop, releaseTypes); else @@ -3569,14 +3708,22 @@ BeginSweepPhase(JSRuntime *rt) * * Objects are finalized immediately but this may change in the future. */ - for (GCCompartmentsIter c(rt); !c.done(); c.next()) + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); c->arenas.queueObjectsForSweep(&fop); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) + } + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); c->arenas.queueStringsForSweep(&fop); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) + } + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); c->arenas.queueScriptsForSweep(&fop); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) + } + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); c->arenas.queueShapesForSweep(&fop); + } rt->gcSweepPhase = 0; rt->gcSweepCompartmentIndex = 0; diff --git a/js/src/methodjit/MonoIC.cpp b/js/src/methodjit/MonoIC.cpp index 651ceabcfa6..6b741859cd7 100644 --- a/js/src/methodjit/MonoIC.cpp +++ b/js/src/methodjit/MonoIC.cpp @@ -665,7 +665,7 @@ class CallCompiler : public BaseCompiler bool patchInlinePath(JSScript *script, JSObject *obj) { JS_ASSERT(ic.frameSize.isStatic()); - JITScript *jit = script->getJIT(callingNew, f.cx->compartment->needsBarrier()); + JITScript *jit = script->getJIT(callingNew, f.cx->compartment->compileBarriers()); /* Very fast path. */ Repatcher repatch(f.chunk()); diff --git a/js/src/methodjit/PolyIC.cpp b/js/src/methodjit/PolyIC.cpp index 01fd428f887..347337fb9fd 100644 --- a/js/src/methodjit/PolyIC.cpp +++ b/js/src/methodjit/PolyIC.cpp @@ -546,7 +546,7 @@ class SetPropCompiler : public PICStubCompiler * Since we're changing the object's shape, we need a write * barrier. Taking the slow path is the easiest way to get one. */ - if (cx->compartment->needsBarrier()) + if (cx->compartment->compileBarriers()) return disable("ADDPROP write barrier required"); #endif @@ -2884,7 +2884,7 @@ SetElementIC::shouldUpdate(VMFrame &f) return false; } #ifdef JSGC_INCREMENTAL_MJ - JS_ASSERT(!f.cx->compartment->needsBarrier()); + JS_ASSERT(!f.cx->compartment->compileBarriers()); #endif JS_ASSERT(stubsGenerated < MAX_PIC_STUBS); return true; diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 76f12706de9..bcb338c0e27 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -214,7 +214,7 @@ intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp) uint32_t errorNumber = args[0].toInt32(); char *errorArgs[3] = {NULL, NULL, NULL}; - for (int i = 1; i < 3 && i < args.length(); i++) { + for (unsigned i = 1; i < 3 && i < args.length(); i++) { RootedValue val(cx, args[i]); if (val.isInt32() || val.isString()) { errorArgs[i - 1] = JS_EncodeString(cx, ToString(cx, val)); @@ -226,7 +226,7 @@ intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp) JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber, errorArgs[0], errorArgs[1], errorArgs[2]); - for (uint32_t i = 0; i < 3; i++) + for (unsigned i = 0; i < 3; i++) cx->free_(errorArgs[i]); return false; } diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl index f46076f15c6..ea5d770754d 100644 --- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -118,7 +118,7 @@ interface ScheduledGCCallback : nsISupports /** * interface of Components.utils */ -[scriptable, uuid(cc7ef3b0-339d-4317-927b-fdc8f0664927)] +[scriptable, uuid(25442383-a1f5-440c-9e18-a2a1cdb8a638)] interface nsIXPCComponents_Utils : nsISupports { @@ -329,6 +329,14 @@ interface nsIXPCComponents_Utils : nsISupports [implicit_jscontext] void recomputeWrappers([optional] in jsval vobj); + /* + * Dispatches a runnable to the current/main thread. If |scope| is passed, + * the runnable will be dispatch in the compartment of |scope|, which + * affects which error reporter gets called. + */ + [implicit_jscontext] + void dispatch(in jsval runnable, [optional] in jsval scope); + /* * To be called from JS only. * diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 003d0220470..890e6e180df 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -4347,6 +4347,33 @@ nsXPCComponents_Utils::RecomputeWrappers(const jsval &vobj, JSContext *cx) return NS_OK; } +NS_IMETHODIMP +nsXPCComponents_Utils::Dispatch(const jsval &runnable_, const jsval &scope, + JSContext *cx) +{ + // Enter the given compartment, if any, and rewrap runnable. + JSAutoEnterCompartment ac; + js::Value runnable = runnable_; + if (scope.isObject()) { + JSObject *scopeObj = js::UnwrapObject(&scope.toObject()); + if (!scopeObj || !ac.enter(cx, scopeObj) || !JS_WrapValue(cx, &runnable)) + return NS_ERROR_FAILURE; + } + + // Get an XPCWrappedJS for |runnable|. + if (!runnable.isObject()) + return NS_ERROR_INVALID_ARG; + nsCOMPtr run; + nsresult rv = nsXPConnect::GetXPConnect()->WrapJS(cx, &runnable.toObject(), + NS_GET_IID(nsIRunnable), + getter_AddRefs(run)); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(run); + + // Dispatch. + return NS_DispatchToMainThread(run); +} + /* string canCreateWrapper (in nsIIDPtr iid); */ NS_IMETHODIMP nsXPCComponents_Utils::CanCreateWrapper(const nsIID * iid, char **_retval) diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 74ad0846910..aace29a15bc 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2073,6 +2073,12 @@ AccumulateTelemetryCallback(int id, uint32_t sample) case JS_TELEMETRY_GC_NON_INCREMENTAL: Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL, sample); break; + case JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS: + Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_TOTAL_MS, sample); + break; + case JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS: + Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_MAX_PAUSE_MS, sample); + break; } } diff --git a/js/xpconnect/tests/chrome/Makefile.in b/js/xpconnect/tests/chrome/Makefile.in index 40364ce36ad..58ddb32bd89 100644 --- a/js/xpconnect/tests/chrome/Makefile.in +++ b/js/xpconnect/tests/chrome/Makefile.in @@ -33,7 +33,6 @@ MOCHITEST_CHROME_FILES = \ test_bug706301.xul \ test_bug726949.xul \ test_bug743843.xul \ - test_bug758563.xul \ test_bug760076.xul \ test_bug760109.xul \ test_bug763343.xul \ diff --git a/js/xpconnect/tests/chrome/test_bug758563.xul b/js/xpconnect/tests/chrome/test_bug758563.xul deleted file mode 100644 index 7333ebc2897..00000000000 --- a/js/xpconnect/tests/chrome/test_bug758563.xul +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - -