diff --git a/accessible/src/jsat/AccessFu.jsm b/accessible/src/jsat/AccessFu.jsm index ef75795218d1..69df693ea7ef 100644 --- a/accessible/src/jsat/AccessFu.jsm +++ b/accessible/src/jsat/AccessFu.jsm @@ -14,7 +14,6 @@ this.EXPORTED_SYMBOLS = ['AccessFu']; Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/accessibility/Utils.jsm'); -Cu.import('resource://gre/modules/accessibility/TouchAdapter.jsm'); const ACCESSFU_DISABLE = 0; const ACCESSFU_ENABLE = 1; @@ -58,6 +57,8 @@ this.AccessFu = { this._activatePref = ACCESSFU_DISABLE; } + Input.quickNavMode.updateModes(this.prefsBranch); + this._enableOrDisable(); }, @@ -76,9 +77,6 @@ this.AccessFu = { Logger.info('enable'); - this.touchAdapter = (Utils.MozBuildApp == 'mobile/android') ? - AndroidTouchAdapter : TouchAdapter; - for each (let mm in Utils.getAllMessageManagers(this.chromeWin)) this._loadFrameScript(mm); @@ -91,7 +89,7 @@ this.AccessFu = { Input.attach(this.chromeWin); Output.attach(this.chromeWin); - this.touchAdapter.attach(this.chromeWin); + TouchAdapter.attach(this.chromeWin); Services.obs.addObserver(this, 'remote-browser-frame-shown', false); Services.obs.addObserver(this, 'Accessibility:NextObject', false); @@ -117,7 +115,7 @@ this.AccessFu = { mm.sendAsyncMessage('AccessFu:Stop'); Input.detach(); - this.touchAdapter.detach(this.chromeWin); + TouchAdapter.detach(this.chromeWin); this.chromeWin.removeEventListener('TabOpen', this); this.chromeWin.removeEventListener('TabSelect', this); @@ -205,6 +203,8 @@ this.AccessFu = { if (aData == 'activate') { this._activatePref = this.prefsBranch.getIntPref('activate'); this._enableOrDisable(); + } else if (aData == 'quicknav_modes') { + Input.quickNavMode.updateModes(this.prefsBranch); } break; case 'remote-browser-frame-shown': @@ -389,7 +389,7 @@ var Input = { this._handleKeypress(aEvent); break; case 'mozAccessFuGesture': - this._handleGesture(aEvent); + this._handleGesture(aEvent.detail); break; } } catch (x) { @@ -397,44 +397,56 @@ var Input = { } }, - _handleGesture: function _handleGesture(aEvent) { - let detail = aEvent.detail; - Logger.info('Gesture', detail.type, - '(fingers: ' + detail.touches.length + ')'); + _handleGesture: function _handleGesture(aGesture) { + let gestureName = aGesture.type + aGesture.touches.length; + Logger.info('Gesture', aGesture.type, + '(fingers: ' + aGesture.touches.length + ')'); - if (detail.touches.length == 1) { - switch (detail.type) { - case 'swiperight': - this.moveCursor('moveNext', 'Simple', 'gestures'); - break; - case 'swipeleft': - this.moveCursor('movePrevious', 'Simple', 'gesture'); - break; - case 'doubletap': - this.activateCurrent(); - break; - case 'explore': - this.moveCursor('moveToPoint', 'Simple', 'gesture', - detail.x, detail.y); - break; - } - } - - if (detail.touches.length == 3) { - switch (detail.type) { - case 'swiperight': - this.scroll(-1, true); - break; - case 'swipedown': - this.scroll(-1); - break; - case 'swipeleft': - this.scroll(1, true); - break; - case 'swipeup': - this.scroll(1); - break; - } + switch (gestureName) { + case 'dwell1': + case 'explore1': + this.moveCursor('moveToPoint', 'Simple', 'gesture', + aGesture.x, aGesture.y); + break; + case 'doubletap1': + this.activateCurrent(); + break; + case 'swiperight1': + this.moveCursor('moveNext', 'Simple', 'gestures'); + break; + case 'swipeleft1': + this.moveCursor('movePrevious', 'Simple', 'gesture'); + break; + case 'swiperight2': + this.scroll(-1, true); + break; + case 'swipedown2': + this.scroll(-1); + break; + case 'swipeleft2': + this.scroll(1, true); + break; + case 'swipeup2': + this.scroll(1); + break; + case 'explore2': + Utils.getCurrentBrowser(this.chromeWin).contentWindow.scrollBy( + -aGesture.deltaX, -aGesture.deltaY); + break; + case 'swiperight3': + this.moveCursor('moveNext', this.quickNavMode.current, 'gesture'); + break; + case 'swipeleft3': + this.moveCursor('movePrevious', this.quickNavMode.current, 'gesture'); + break; + case 'swipedown3': + this.quickNavMode.next(); + AccessFu.announce('quicknav_' + this.quickNavMode.current); + break; + case 'swipeup3': + this.quickNavMode.previous(); + AccessFu.announce('quicknav_' + this.quickNavMode.current); + break; } }, @@ -568,5 +580,34 @@ var Input = { x: ['moveNext', 'Checkbox'], X: ['movePrevious', 'Checkbox'] }; + + return this.keyMap; + }, + + quickNavMode: { + get current() { + return this.modes[this._currentIndex]; + }, + + previous: function quickNavMode_previous() { + if (--this._currentIndex < 0) + this._currentIndex = this.modes.length - 1; + }, + + next: function quickNavMode_next() { + if (++this._currentIndex >= this.modes.length) + this._currentIndex = 0; + }, + + updateModes: function updateModes(aPrefsBranch) { + try { + this.modes = aPrefsBranch.getCharPref('quicknav_modes').split(','); + } catch (x) { + // Fallback + this.modes = []; + } + }, + + _currentIndex: -1 } }; diff --git a/accessible/src/jsat/TouchAdapter.jsm b/accessible/src/jsat/TouchAdapter.jsm index 21e16c5c0499..765baa0ca3bb 100644 --- a/accessible/src/jsat/TouchAdapter.jsm +++ b/accessible/src/jsat/TouchAdapter.jsm @@ -9,7 +9,7 @@ const Ci = Components.interfaces; const Cu = Components.utils; const Cr = Components.results; -this.EXPORTED_SYMBOLS = ['TouchAdapter', 'AndroidTouchAdapter']; +this.EXPORTED_SYMBOLS = ['TouchAdapter']; Cu.import('resource://gre/modules/accessibility/Utils.jsm'); @@ -39,6 +39,9 @@ this.TouchAdapter = { // maximum distance the mouse could move during a tap in inches TAP_MAX_RADIUS: 0.2, + // The virtual touch ID generated by an Android hover event. + HOVER_ID: 'hover', + attach: function TouchAdapter_attach(aWindow) { if (this.chromeWin) return; @@ -53,14 +56,26 @@ this.TouchAdapter = { 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); + let target = this.chromeWin; - this.glass.addEventListener('touchend', this, true, true); - this.glass.addEventListener('touchmove', this, true, true); - this.glass.addEventListener('touchstart', this, true, true); + if (Utils.MozBuildApp == 'b2g') { + 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); + target = this.glass; + } + + target.addEventListener('mousemove', this, true, true); + target.addEventListener('mouseenter', this, true, true); + target.addEventListener('mouseleave', this, true, true); + target.addEventListener('mousedown', this, true, true); + target.addEventListener('mouseup', this, true, true); + target.addEventListener('click', this, true, true); + + target.addEventListener('touchend', this, true, true); + target.addEventListener('touchmove', this, true, true); + target.addEventListener('touchstart', this, true, true); if (Utils.OS != 'Android') Mouse2Touch.attach(aWindow); @@ -72,10 +87,23 @@ this.TouchAdapter = { 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); + let target = this.chromeWin; + + if (Utils.MozBuildApp == 'b2g') { + target = this.glass; + this.glass.parentNode.removeChild(this.glass); + } + + target.removeEventListener('mousemove', this, true, true); + target.removeEventListener('mouseenter', this, true, true); + target.removeEventListener('mouseleave', this, true, true); + target.removeEventListener('mousedown', this, true, true); + target.removeEventListener('mouseup', this, true, true); + target.removeEventListener('click', this, true, true); + + target.removeEventListener('touchend', this, true, true); + target.removeEventListener('touchmove', this, true, true); + target.removeEventListener('touchstart', this, true, true); if (Utils.OS != 'Android') Mouse2Touch.detach(aWindow); @@ -84,16 +112,23 @@ this.TouchAdapter = { }, handleEvent: function TouchAdapter_handleEvent(aEvent) { - let touches = aEvent.changedTouches; + if (this._delayedEvent) { + this.chromeWin.clearTimeout(this._delayedEvent); + delete this._delayedEvent; + } + + let changedTouches = aEvent.changedTouches || [aEvent]; + // XXX: Until bug 77992 is resolved, on desktop we get microseconds // instead of milliseconds. let timeStamp = (Utils.OS == 'Android') ? aEvent.timeStamp : Date.now(); switch (aEvent.type) { + case 'mouseenter': case 'touchstart': - for (var i = 0; i < touches.length; i++) { - let touch = touches[i]; + for (var i = 0; i < changedTouches.length; i++) { + let touch = changedTouches[i]; let touchPoint = new TouchPoint(touch, timeStamp, this._dpi); - this._touchPoints[touch.identifier] = touchPoint; + this._touchPoints[touch.identifier || this.HOVER_ID] = touchPoint; this._lastExploreTime = timeStamp + this.SWIPE_MAX_DURATION; } this._dwellTimeout = this.chromeWin.setTimeout( @@ -101,27 +136,34 @@ this.TouchAdapter = { this.compileAndEmit(timeStamp + this.DWELL_THRESHOLD); }).bind(this), this.DWELL_THRESHOLD); break; + case 'mousemove': case 'touchmove': - for (var i = 0; i < touches.length; i++) { - let touch = touches[i]; - let touchPoint = this._touchPoints[touch.identifier]; - touchPoint.update(touch, timeStamp); + for (var i = 0; i < changedTouches.length; i++) { + let touch = changedTouches[i]; + let touchPoint = this._touchPoints[touch.identifier || this.HOVER_ID]; + if (touchPoint) + touchPoint.update(touch, timeStamp); } if (timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) { this.compileAndEmit(timeStamp); this._lastExploreTime = timeStamp; } break; + case 'mouseleave': case 'touchend': - for (var i = 0; i < touches.length; i++) { - let touch = touches[i]; - let touchPoint = this._touchPoints[touch.identifier]; - touchPoint.update(touch, timeStamp); - touchPoint.finish(); + for (var i = 0; i < changedTouches.length; i++) { + let touch = changedTouches[i]; + let touchPoint = this._touchPoints[touch.identifier || this.HOVER_ID]; + if (touchPoint) { + touchPoint.update(touch, timeStamp); + touchPoint.finish(); + } } this.compileAndEmit(timeStamp); break; } + + aEvent.preventDefault(); }, cleanupTouches: function cleanupTouches() { @@ -170,12 +212,22 @@ this.TouchAdapter = { 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'; + let sequence = prevGesture.type + '-' + details.type; + switch (sequence) { + case 'tap-tap': + details.type = 'doubletap'; + break; + case 'doubletap-tap': + details.type = 'tripletap'; + break; + case 'tap-dwell': + details.type = 'taphold'; + break; + case 'explore-explore': + details.deltaX = details.x - prevGesture.x; + details.deltaY = details.y - prevGesture.y; + break; + } } } @@ -189,9 +241,36 @@ this.TouchAdapter = { }, emitGesture: function TouchAdapter_emitGesture(aDetails) { - let evt = this.chromeWin.document.createEvent('CustomEvent'); - evt.initCustomEvent('mozAccessFuGesture', true, true, aDetails); - this.chromeWin.dispatchEvent(evt); + let emitDelay = 0; + + // Unmutate gestures we are getting from Android when EBT is enabled. + // Two finger gestures are translated to one. Double taps are translated + // to single taps. + if (Utils.MozBuildApp == 'mobile/android' && + Utils.AndroidSdkVersion >= 14 && + aDetails.touches[0] != this.HOVER_ID) { + if (aDetails.touches.length == 1) { + if (aDetails.type == 'tap') { + emitDelay = 50; + aDetails.type = 'doubletap'; + } else { + aDetails.touches.push(this.HOVER_ID); + } + } + } + + let emit = function emit() { + let evt = this.chromeWin.document.createEvent('CustomEvent'); + evt.initCustomEvent('mozAccessFuGesture', true, true, aDetails); + this.chromeWin.dispatchEvent(evt); + delete this._delayedEvent; + }.bind(this); + + if (emitDelay) { + this._delayedEvent = this.chromeWin.setTimeout(emit, emitDelay); + } else { + emit(); + } }, compileAndEmit: function TouchAdapter_compileAndEmit(aTime) { @@ -364,41 +443,3 @@ var Mouse2Touch = { aEvent.stopImmediatePropagation(); } }; - -this.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, touches: [1]}); - this.chromeWin.dispatchEvent(evt); - this._lastExploreTime = aEvent.timeStamp; - } - } -}; \ No newline at end of file diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index d0ea9fb54977..8b0d2a3e1b06 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -234,16 +234,25 @@ pref("editor.singleLine.pasteNewlines", 2); pref("ui.dragThresholdX", 25); pref("ui.dragThresholdY", 25); -// Layers Acceleration -pref("layers.acceleration.disabled", false); -#ifndef XP_WIN -//TODO: turn this on for Windows in bug 808016 +// Layers Acceleration. We can only have nice things on gonk, because +// they're not maintained anywhere else. +#ifndef MOZ_WIDGET_GONK +pref("dom.ipc.tabs.disabled", true); +pref("layers.offmainthreadcomposition.enabled", false); +pref("layers.offmainthreadcomposition.animate-opacity", false); +pref("layers.offmainthreadcomposition.animate-transform", false); +pref("layers.offmainthreadcomposition.throttle-animations", false); +pref("layers.async-video.enabled", false); +#else +pref("dom.ipc.tabs.disabled", false); pref("layers.offmainthreadcomposition.enabled", true); -#endif +pref("layers.acceleration.disabled", false); pref("layers.offmainthreadcomposition.animate-opacity", true); pref("layers.offmainthreadcomposition.animate-transform", true); +pref("layers.offmainthreadcomposition.throttle-animations", true); pref("layers.async-video.enabled", true); pref("layers.async-pan-zoom.enabled", true); +#endif // Web Notifications pref("notification.feature.enabled", true); @@ -379,7 +388,6 @@ pref("dom.mozBrowserFramesEnabled", true); // We'll run out of PIDs on UNIX-y systems before we hit this limit. pref("dom.ipc.processCount", 100000); -pref("dom.ipc.tabs.disabled", false); pref("dom.ipc.browser_frames.oop_by_default", false); // Temporary permission hack for WebSMS diff --git a/b2g/app/nsBrowserApp.cpp b/b2g/app/nsBrowserApp.cpp index 5c64cfec2c5b..1d5440f01547 100644 --- a/b2g/app/nsBrowserApp.cpp +++ b/b2g/app/nsBrowserApp.cpp @@ -168,10 +168,6 @@ int main(int argc, char* argv[]) { char exePath[MAXPATHLEN]; -#if defined(MOZ_X11) - putenv("MOZ_USE_OMTC=1"); -#endif - nsresult rv = mozilla::BinaryPath::Get(argv[0], exePath); if (NS_FAILED(rv)) { Output("Couldn't calculate the application directory.\n"); diff --git a/browser/base/content/test/head.js b/browser/base/content/test/head.js index df6a6897fb15..a78fead2e019 100644 --- a/browser/base/content/test/head.js +++ b/browser/base/content/test/head.js @@ -150,6 +150,9 @@ function runSocialTestWithProvider(manifest, callback) { Social.enabled = true; registerCleanupFunction(function () { + // disable social before removing the providers to avoid providers + // being activated immediately before we get around to removing it. + Services.prefs.clearUserPref("social.enabled"); // if one test happens to fail, it is likely finishSocialTest will not // be called, causing most future social tests to also fail as they // attempt to add a provider which already exists - so work @@ -159,7 +162,6 @@ function runSocialTestWithProvider(manifest, callback) { SocialService.removeProvider(m.origin, finish); } catch (ex) {} }); - Services.prefs.clearUserPref("social.enabled"); }); function finishSocialTest() { SocialService.removeProvider(provider.origin, finish); diff --git a/browser/devtools/commandline/test/Makefile.in b/browser/devtools/commandline/test/Makefile.in index 7d3286e40420..c046815021e0 100644 --- a/browser/devtools/commandline/test/Makefile.in +++ b/browser/devtools/commandline/test/Makefile.in @@ -14,8 +14,6 @@ include $(DEPTH)/config/autoconf.mk MOCHITEST_BROWSER_FILES = \ browser_dbg_cmd_break.html \ browser_dbg_cmd_break.js \ - browser_dbg_cmd.html \ - browser_dbg_cmd.js \ browser_cmd_addon.js \ browser_cmd_commands.js \ browser_cmd_cookie.js \ @@ -67,10 +65,13 @@ endif ifneq ($(OS_ARCH),WINNT) MOCHITEST_BROWSER_FILES += \ + browser_dbg_cmd.html \ + browser_dbg_cmd.js \ browser_cmd_calllog.js \ browser_cmd_calllog_chrome.js \ $(NULL) else +$(filter disabled-temporarily--bug-820221, browser_dbg_cmd.js) $(filter disabled-temporarily--bug-817304, browser_cmd_calllog.js) $(filter disabled-temporarily--bug-819017, browser_cmd_calllog_chrome.js) endif diff --git a/browser/themes/gnomestripe/Makefile.in b/browser/themes/gnomestripe/Makefile.in index 372888149d48..780475108d65 100644 --- a/browser/themes/gnomestripe/Makefile.in +++ b/browser/themes/gnomestripe/Makefile.in @@ -11,14 +11,8 @@ include $(DEPTH)/config/autoconf.mk DIRS = communicator +ICON_FILES := icon.png +ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} +INSTALL_TARGETS += ICON + include $(topsrcdir)/config/rules.mk - -FILES := \ - icon.png \ - $(NULL) - -libs:: $(FILES) - $(INSTALL) $(foreach f,$^,"$f") $(DIST)/bin/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} - -install:: $(FILES) - $(SYSINSTALL) $(IFLAGS1) $(foreach f,$^,"$f") $(DESTDIR)$(mozappdir)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} diff --git a/browser/themes/pinstripe/Makefile.in b/browser/themes/pinstripe/Makefile.in index 372888149d48..780475108d65 100644 --- a/browser/themes/pinstripe/Makefile.in +++ b/browser/themes/pinstripe/Makefile.in @@ -11,14 +11,8 @@ include $(DEPTH)/config/autoconf.mk DIRS = communicator +ICON_FILES := icon.png +ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} +INSTALL_TARGETS += ICON + include $(topsrcdir)/config/rules.mk - -FILES := \ - icon.png \ - $(NULL) - -libs:: $(FILES) - $(INSTALL) $(foreach f,$^,"$f") $(DIST)/bin/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} - -install:: $(FILES) - $(SYSINSTALL) $(IFLAGS1) $(foreach f,$^,"$f") $(DESTDIR)$(mozappdir)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} diff --git a/browser/themes/winstripe/Makefile.in b/browser/themes/winstripe/Makefile.in index 372888149d48..780475108d65 100644 --- a/browser/themes/winstripe/Makefile.in +++ b/browser/themes/winstripe/Makefile.in @@ -11,14 +11,8 @@ include $(DEPTH)/config/autoconf.mk DIRS = communicator +ICON_FILES := icon.png +ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} +INSTALL_TARGETS += ICON + include $(topsrcdir)/config/rules.mk - -FILES := \ - icon.png \ - $(NULL) - -libs:: $(FILES) - $(INSTALL) $(foreach f,$^,"$f") $(DIST)/bin/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} - -install:: $(FILES) - $(SYSINSTALL) $(IFLAGS1) $(foreach f,$^,"$f") $(DESTDIR)$(mozappdir)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd} diff --git a/build/pymake/pymake/builtins.py b/build/pymake/pymake/builtins.py index 67d9d21a393f..eb6f2e11bd16 100644 --- a/build/pymake/pymake/builtins.py +++ b/build/pymake/pymake/builtins.py @@ -86,8 +86,35 @@ def touch(args): """ Emulate the behavior of touch(1). """ + try: + opts, args = getopt(args, "t:") + except GetoptError, e: + raise PythonException, ("touch: %s" % e, 1) + opts = dict(opts) + times = None + if '-t' in opts: + import re + from time import mktime, localtime + m = re.match('^(?P(?:\d\d)?\d\d)?(?P\d\d)(?P\d\d)(?P\d\d)(?P\d\d)(?:\.(?P\d\d))?$', opts['-t']) + if not m: + raise PythonException, ("touch: invalid date format '%s'" % opts['-t'], 1) + def normalized_field(m, f): + if f == 'Y': + if m.group(f) is None: + return localtime()[0] + y = int(m.group(f)) + if y < 69: + y += 2000 + elif y < 100: + y += 1900 + return y + if m.group(f) is None: + return localtime()[0] if f == 'Y' else 0 + return int(m.group(f)) + time = [normalized_field(m, f) for f in ['Y', 'M', 'D', 'h', 'm', 's']] + [0, 0, -1] + time = mktime(time) + times = (time, time) for f in args: - if os.path.exists(f): - os.utime(f, None) - else: - open(f, 'w').close() + if not os.path.exists(f): + open(f, 'a').close() + os.utime(f, times) diff --git a/build/pymake/pymake/data.py b/build/pymake/pymake/data.py index 720cb4ada03c..d42ab790eebf 100644 --- a/build/pymake/pymake/data.py +++ b/build/pymake/pymake/data.py @@ -1414,9 +1414,9 @@ class _NativeWrapper(_CommandWrapper): # get the module and method to call parts, badchar = process.clinetoargv(self.cline, self.kwargs['cwd']) if parts is None: - raise DataError("native command '%s': shell metacharacter '%s' in command line" % (cline, badchar), self.loc) + raise DataError("native command '%s': shell metacharacter '%s' in command line" % (self.cline, badchar), self.loc) if len(parts) < 2: - raise DataError("native command '%s': no method name specified" % cline, self.loc) + raise DataError("native command '%s': no method name specified" % self.cline, self.loc) module = parts[0] method = parts[1] cline_list = parts[2:] diff --git a/build/pymake/tests/native-touch.mk b/build/pymake/tests/native-touch.mk new file mode 100644 index 000000000000..811161ece79f --- /dev/null +++ b/build/pymake/tests/native-touch.mk @@ -0,0 +1,15 @@ +TOUCH ?= touch + +foo: + $(TOUCH) bar + $(TOUCH) baz + $(MAKE) -f $(TESTPATH)/native-touch.mk baz + $(TOUCH) -t 198007040802 baz + $(MAKE) -f $(TESTPATH)/native-touch.mk baz + +bar: + $(TOUCH) $@ + +baz: bar + echo TEST-PASS + $(TOUCH) $@ diff --git a/config/config.mk b/config/config.mk index b14bb36846ab..8c4f6e479d8a 100644 --- a/config/config.mk +++ b/config/config.mk @@ -76,9 +76,9 @@ LIBXUL_DIST ?= $(DIST) # build products (typelibs, components, chrome). # # If XPI_NAME is set, the files will be shipped to $(DIST)/xpi-stage/$(XPI_NAME) -# If DIST_SUBDIR is set, the files will be shipped to $(DIST)/$(DIST_SUBDIR) -# Otherwise, the default $(DIST)/bin will be used. -FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(if $(DIST_SUBDIR),$(DIST)/bin/$(DIST_SUBDIR),$(DIST)/bin)) +# instead of $(DIST)/bin. In both cases, if DIST_SUBDIR is set, the files will be +# shipped to a $(DIST_SUBDIR) subdirectory. +FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%) ifdef XPI_NAME DEFINES += -DXPI_NAME=$(XPI_NAME) diff --git a/config/makefiles/autotargets.mk b/config/makefiles/autotargets.mk index 2c118a083516..e401ef288387 100644 --- a/config/makefiles/autotargets.mk +++ b/config/makefiles/autotargets.mk @@ -56,18 +56,18 @@ mkdir_deps =$(foreach dir,$(getargv),$(call slash_strip,$(dir)/.mkdir.done)) ####################### %/.mkdir.done: # mkdir -p -p => mkdir -p - $(subst $(space)-p,$(null),$(MKDIR)) -p $(dir $@) + $(subst $(space)-p,$(null),$(MKDIR)) -p "$(dir $@)" # Make the timestamp old enough for not being a problem with symbolic links # targets depending on it. Use Jan 3, 1980 to accomodate any timezone where # 198001010000 would translate to something older than FAT epoch. - @touch -t 198001030000 $@ + @$(TOUCH) -t 198001030000 "$@" # A handful of makefiles are attempting "mkdir dot". Likely not intended # or stale logic so add a stub target to handle the request and warn for now. .mkdir.done: ifndef NOWARN_AUTOTARGETS # { @echo "WARNING: $(MKDIR) -dot- requested by $(MAKE) -C $(CURDIR) $(MAKECMDGOALS)" - @touch -t 198001030000 $@ + @$(TOUCH) -t 198001030000 $@ endif #} INCLUDED_AUTOTARGETS_MK = 1 diff --git a/config/rules.mk b/config/rules.mk index 96706e5178cb..f55fb4fa4b7c 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -1544,7 +1544,7 @@ define install_file_template $(or $(3),libs):: $(2)/$(notdir $(1)) $(call install_cmd_override,$(2)/$(notdir $(1))) $(2)/$(notdir $(1)): $(1) $$(call mkdir_deps,$(2)) - $$(call install_cmd,$(4) $$< $${@D}) + $$(call install_cmd,$(4) "$$<" "$${@D}") endef $(foreach category,$(INSTALL_TARGETS),\ $(if $($(category)_DEST),,$(error Missing $(category)_DEST))\ @@ -1591,8 +1591,8 @@ $(foreach category,$(INSTALL_TARGETS),\ # makefile_target, extra_flags) define preprocess_file_template $(2): $(1) $$(call mkdir_deps,$(dir $(2))) $$(GLOBAL_DEPS) - $$(RM) $$@ - $$(PYTHON) $$(topsrcdir)/config/Preprocessor.py $(4) $$(DEFINES) $$(ACDEFINES) $$(XULPPFLAGS) $$< > $$@ + $$(RM) "$$@" + $$(PYTHON) $$(topsrcdir)/config/Preprocessor.py $(4) $$(DEFINES) $$(ACDEFINES) $$(XULPPFLAGS) "$$<" > "$$@" $(3):: $(2) endef diff --git a/config/writemozinfo.py b/config/writemozinfo.py index 235c4e252d9c..3efabd67d5e5 100755 --- a/config/writemozinfo.py +++ b/config/writemozinfo.py @@ -29,7 +29,7 @@ def build_dict(env=os.environ): known_os = {"Linux": "linux", "WINNT": "win", "Darwin": "mac", - "Android": "android"} + "Android": "b2g" if env["MOZ_WIDGET_TOOLKIT"] == "gonk" else "android"} if o in known_os: d["os"] = known_os[o] else: diff --git a/content/base/public/DirectionalityUtils.h b/content/base/public/DirectionalityUtils.h index 9d4a4000dbff..8dfd2eab2cff 100644 --- a/content/base/public/DirectionalityUtils.h +++ b/content/base/public/DirectionalityUtils.h @@ -7,8 +7,7 @@ #ifndef DirectionalityUtils_h___ #define DirectionalityUtils_h___ -#include "prtypes.h" -#include "mozilla/StandardInteger.h" +#include "nscore.h" class nsIContent; class nsIDocument; diff --git a/content/base/public/mozFlushType.h b/content/base/public/mozFlushType.h index 186a8c960073..c68494b0dd60 100644 --- a/content/base/public/mozFlushType.h +++ b/content/base/public/mozFlushType.h @@ -27,4 +27,17 @@ enum mozFlushType { Flush_Display = 6 /* As above, plus flush painting */ }; +namespace mozilla { + +struct ChangesToFlush { + ChangesToFlush(mozFlushType aFlushType, bool aFlushAnimations) + : mFlushType(aFlushType) + , mFlushAnimations(aFlushAnimations) + {} + + mozFlushType mFlushType; + bool mFlushAnimations; +}; + +} #endif /* mozFlushType_h___ */ diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index f0b1d6e81dc6..634e01f56f2a 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -34,8 +34,6 @@ #include "nsThreadUtils.h" #include "nsIContent.h" #include "nsCharSeparatedTokenizer.h" -#include "gfxContext.h" -#include "gfxFont.h" #include "nsContentList.h" #include "mozilla/AutoRestore.h" @@ -102,7 +100,6 @@ struct nsIntMargin; class nsPIDOMWindow; class nsIDocumentLoaderFactory; class nsIDOMHTMLInputElement; -class gfxTextObjectPaint; namespace mozilla { @@ -2122,13 +2119,6 @@ public: static nsIEditor* GetHTMLEditor(nsPresContext* aPresContext); - static bool PaintSVGGlyph(Element *aElement, gfxContext *aContext, - gfxFont::DrawMode aDrawMode, - gfxTextObjectPaint *aObjectPaint); - - static bool GetSVGGlyphExtents(Element *aElement, const gfxMatrix& aSVGToAppSpace, - gfxRect *aResult); - /** * Check whether a spec feature/version is supported. * @param aObject the object, which should support the feature, diff --git a/content/base/public/nsDOMFile.h b/content/base/public/nsDOMFile.h index b6feedb290c9..b5025117e67a 100644 --- a/content/base/public/nsDOMFile.h +++ b/content/base/public/nsDOMFile.h @@ -17,7 +17,6 @@ #include "nsCOMPtr.h" #include "nsString.h" #include "nsIXMLHttpRequest.h" -#include "prmem.h" #include "nsAutoPtr.h" #include "mozilla/GuardObjects.h" @@ -201,8 +200,7 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMFileCC, nsIDOMFile) }; -class nsDOMFileFile : public nsDOMFile, - public nsIJSNativeInitializer +class nsDOMFileFile : public nsDOMFile { public: // Create as a file @@ -290,15 +288,6 @@ public: mName.SetIsVoid(true); } - NS_DECL_ISUPPORTS_INHERITED - - // nsIJSNativeInitializer - NS_IMETHOD Initialize(nsISupports* aOwner, - JSContext* aCx, - JSObject* aObj, - uint32_t aArgc, - jsval* aArgv); - // Overrides NS_IMETHOD GetSize(uint64_t* aSize); NS_IMETHOD GetType(nsAString& aType); @@ -307,10 +296,6 @@ public: NS_IMETHOD GetMozFullPathInternal(nsAString& aFullPath); NS_IMETHOD GetInternalStream(nsIInputStream**); - // DOMClassInfo constructor (for File("foo")) - static nsresult - NewFile(nsISupports* *aNewObject); - protected: // Create slice nsDOMFileFile(const nsDOMFileFile* aOther, uint64_t aStart, uint64_t aLength, @@ -423,7 +408,7 @@ protected: sDataOwners = nullptr; } - PR_Free(mData); + moz_free(mData); } static void EnsureMemoryReporterRegistered(); diff --git a/content/base/src/DirectionalityUtils.cpp b/content/base/src/DirectionalityUtils.cpp index 89960fe24b50..b31fa5b7b5c1 100644 --- a/content/base/src/DirectionalityUtils.cpp +++ b/content/base/src/DirectionalityUtils.cpp @@ -273,7 +273,7 @@ inline static bool NodeAffectsDirAutoAncestor(nsINode* aTextNode) * first-strong algorithm defined in http://unicode.org/reports/tr9/#P2 * * @param[out] aFirstStrong the offset to the first character in the string with - * strong directionality, or PR_UINT32_MAX if there is none (return + * strong directionality, or UINT32_MAX if there is none (return value is eDir_NotSet). * @return the directionality of the string */ @@ -304,7 +304,7 @@ GetDirectionFromText(const PRUnichar* aText, const uint32_t aLength, } if (aFirstStrong) { - *aFirstStrong = PR_UINT32_MAX; + *aFirstStrong = UINT32_MAX; } return eDir_NotSet; } @@ -330,7 +330,7 @@ GetDirectionFromText(const char* aText, const uint32_t aLength, } if (aFirstStrong) { - *aFirstStrong = PR_UINT32_MAX; + *aFirstStrong = UINT32_MAX; } return eDir_NotSet; } diff --git a/content/base/src/WebSocket.cpp b/content/base/src/WebSocket.cpp index 319e65e96e15..fb849320f39c 100644 --- a/content/base/src/WebSocket.cpp +++ b/content/base/src/WebSocket.cpp @@ -42,7 +42,6 @@ #include "xpcpublic.h" #include "nsContentPolicyUtils.h" #include "jsfriendapi.h" -#include "prmem.h" #include "nsDOMFile.h" #include "nsWrapperCacheInlines.h" #include "nsDOMEventTargetHelper.h" diff --git a/content/base/src/nsAttrAndChildArray.cpp b/content/base/src/nsAttrAndChildArray.cpp index 9155184c8917..3af0ca1fd4fc 100644 --- a/content/base/src/nsAttrAndChildArray.cpp +++ b/content/base/src/nsAttrAndChildArray.cpp @@ -10,7 +10,6 @@ #include "nsAttrAndChildArray.h" #include "nsMappedAttributeElement.h" -#include "prmem.h" #include "prbit.h" #include "nsString.h" #include "nsHTMLStyleSheet.h" @@ -100,7 +99,7 @@ nsAttrAndChildArray::~nsAttrAndChildArray() Clear(); - PR_Free(mImpl); + moz_free(mImpl); } nsIContent* @@ -616,11 +615,11 @@ nsAttrAndChildArray::Compact() // Then resize or free buffer uint32_t newSize = attrCount * ATTRSIZE + childCount; if (!newSize && !mImpl->mMappedAttrs) { - PR_Free(mImpl); + moz_free(mImpl); mImpl = nullptr; } else if (newSize < mImpl->mBufferSize) { - mImpl = static_cast(PR_Realloc(mImpl, (newSize + NS_IMPL_EXTRA_SIZE) * sizeof(nsIContent*))); + mImpl = static_cast(moz_realloc(mImpl, (newSize + NS_IMPL_EXTRA_SIZE) * sizeof(nsIContent*))); NS_ASSERTION(mImpl, "failed to reallocate to smaller buffer"); mImpl->mBufferSize = newSize; @@ -757,7 +756,7 @@ nsAttrAndChildArray::GrowBy(uint32_t aGrowSize) } bool needToInitialize = !mImpl; - Impl* newImpl = static_cast(PR_Realloc(mImpl, size * sizeof(void*))); + Impl* newImpl = static_cast(moz_realloc(mImpl, size * sizeof(void*))); NS_ENSURE_TRUE(newImpl, false); mImpl = newImpl; diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index ee43e48e4906..e8071e14fd02 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -122,10 +122,6 @@ #include "nsILoadContext.h" #include "nsTextFragment.h" #include "mozilla/Selection.h" -#include "nsSVGUtils.h" -#include "nsISVGChildFrame.h" -#include "nsRenderingContext.h" -#include "gfxSVGGlyphs.h" #ifdef IBMBIDI #include "nsIBidiKeyboard.h" @@ -6136,7 +6132,7 @@ nsContentUtils::CreateBlobBuffer(JSContext* aCx, jsval& aBlob) { uint32_t blobLen = aData.Length(); - void* blobData = PR_Malloc(blobLen); + void* blobData = moz_malloc(blobLen); nsCOMPtr blob; if (blobData) { memcpy(blobData, aData.BeginReading(), blobLen); @@ -6904,60 +6900,6 @@ nsContentUtils::JSArrayToAtomArray(JSContext* aCx, const JS::Value& aJSArray, return NS_OK; } -/* static */ -bool -nsContentUtils::PaintSVGGlyph(Element *aElement, gfxContext *aContext, - gfxFont::DrawMode aDrawMode, - gfxTextObjectPaint *aObjectPaint) -{ - nsIFrame *frame = aElement->GetPrimaryFrame(); - if (!frame) { - NS_WARNING("No frame for SVG glyph"); - return false; - } - - nsISVGChildFrame *displayFrame = do_QueryFrame(frame); - if (!displayFrame) { - NS_WARNING("Non SVG frame for SVG glyph"); - return false; - } - - nsRenderingContext context; - - context.Init(frame->PresContext()->DeviceContext(), aContext); - context.AddUserData(&gfxTextObjectPaint::sUserDataKey, aObjectPaint, nullptr); - - nsresult rv = displayFrame->PaintSVG(&context, nullptr); - NS_ENSURE_SUCCESS(rv, false); - - return true; -} - -/* static */ -bool -nsContentUtils::GetSVGGlyphExtents(Element *aElement, const gfxMatrix& aSVGToAppSpace, - gfxRect *aResult) -{ - nsIFrame *frame = aElement->GetPrimaryFrame(); - if (!frame) { - NS_WARNING("No frame for SVG glyph"); - return false; - } - - nsISVGChildFrame *displayFrame = do_QueryFrame(frame); - if (!displayFrame) { - NS_WARNING("Non SVG frame for SVG glyph"); - return false; - } - - *aResult = displayFrame->GetBBoxContribution(aSVGToAppSpace, - nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry | - nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry | - nsSVGUtils::eBBoxIncludeMarkers); - - return true; -} - // static void nsContentUtils::GetSelectionInTextControl(Selection* aSelection, diff --git a/content/base/src/nsDOMBlobBuilder.cpp b/content/base/src/nsDOMBlobBuilder.cpp index 24cde932a8a9..78a3326728b1 100644 --- a/content/base/src/nsDOMBlobBuilder.cpp +++ b/content/base/src/nsDOMBlobBuilder.cpp @@ -6,6 +6,7 @@ #include "nsDOMBlobBuilder.h" #include "jsfriendapi.h" #include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/FileBinding.h" #include "nsAutoPtr.h" #include "nsDOMClassInfoID.h" #include "nsIMultiplexInputStream.h" @@ -171,14 +172,17 @@ nsDOMMultipartFile::Initialize(nsISupports* aOwner, uint32_t aArgc, jsval* aArgv) { - return InitInternal(aCx, aArgc, aArgv, GetXPConnectNative); + if (!mIsFile) { + return InitBlob(aCx, aArgc, aArgv, GetXPConnectNative); + } + return InitFile(aCx, aArgc, aArgv); } nsresult -nsDOMMultipartFile::InitInternal(JSContext* aCx, - uint32_t aArgc, - jsval* aArgv, - UnwrapFuncPtr aUnwrapFunc) +nsDOMMultipartFile::InitBlob(JSContext* aCx, + uint32_t aArgc, + jsval* aArgv, + UnwrapFuncPtr aUnwrapFunc) { bool nativeEOL = false; if (aArgc > 1) { @@ -260,6 +264,100 @@ nsDOMMultipartFile::InitInternal(JSContext* aCx, return NS_OK; } +nsresult +nsDOMMultipartFile::InitFile(JSContext* aCx, + uint32_t aArgc, + jsval* aArgv) +{ + nsresult rv; + + NS_ASSERTION(!mImmutable, "Something went wrong ..."); + NS_ENSURE_TRUE(!mImmutable, NS_ERROR_UNEXPECTED); + + if (!nsContentUtils::IsCallerChrome()) { + return NS_ERROR_DOM_SECURITY_ERR; // Real short trip + } + + NS_ENSURE_TRUE(aArgc > 0, NS_ERROR_UNEXPECTED); + + bool nativeEOL = false; + if (aArgc > 1) { + FilePropertyBag d; + if (!d.Init(aCx, nullptr, aArgv[1])) { + return NS_ERROR_TYPE_ERR; + } + mName = d.name; + mContentType = d.type; + nativeEOL = d.endings == EndingTypesValues::Native; + } + + // We expect to get a path to represent as a File object, + // an nsIFile, or an nsIDOMFile. + nsCOMPtr file; + nsCOMPtr domFile; + if (!aArgv[0].isString()) { + // Lets see if it's an nsIFile + if (!aArgv[0].isObject()) { + return NS_ERROR_UNEXPECTED; // We're not interested + } + + JSObject* obj = &aArgv[0].toObject(); + + nsISupports* supports = + nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, obj); + if (!supports) { + return NS_ERROR_UNEXPECTED; + } + + domFile = do_QueryInterface(supports); + file = do_QueryInterface(supports); + if (!domFile && !file) { + return NS_ERROR_UNEXPECTED; + } + } else { + // It's a string + JSString* str = JS_ValueToString(aCx, aArgv[0]); + NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS); + + nsDependentJSString xpcomStr; + if (!xpcomStr.init(aCx, str)) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + rv = NS_NewLocalFile(xpcomStr, false, getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (file) { + bool exists; + rv = file->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(exists, NS_ERROR_FILE_NOT_FOUND); + + bool isDir; + rv = file->IsDirectory(&isDir); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_FALSE(isDir, NS_ERROR_FILE_IS_DIRECTORY); + + if (mName.IsEmpty()) { + file->GetLeafName(mName); + } + + domFile = new nsDOMFileFile(file); + } + + // XXXkhuey this is terrible + if (mContentType.IsEmpty()) { + domFile->GetType(mContentType); + } + + BlobSet blobSet; + blobSet.AppendBlob(domFile); + mBlobs = blobSet.GetBlobs(); + + return NS_OK; +} + nsresult BlobSet::AppendVoidPtr(const void* aData, uint32_t aLength) { diff --git a/content/base/src/nsDOMBlobBuilder.h b/content/base/src/nsDOMBlobBuilder.h index 4d1bd6fdfb39..4b8d56207760 100644 --- a/content/base/src/nsDOMBlobBuilder.h +++ b/content/base/src/nsDOMBlobBuilder.h @@ -54,10 +54,13 @@ public: jsval* aArgv); typedef nsIDOMBlob* (*UnwrapFuncPtr)(JSContext*, JSObject*); - nsresult InitInternal(JSContext* aCx, - uint32_t aArgc, - jsval* aArgv, - UnwrapFuncPtr aUnwrapFunc); + nsresult InitBlob(JSContext* aCx, + uint32_t aArgc, + jsval* aArgv, + UnwrapFuncPtr aUnwrapFunc); + nsresult InitFile(JSContext* aCx, + uint32_t aArgc, + jsval* aArgv); already_AddRefed CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType); @@ -72,6 +75,16 @@ public: static nsresult NewBlob(nsISupports* *aNewObject); + // DOMClassInfo constructor (for File([b1, "foo"], { type: "image/png", + // name: "foo.png" })) + inline static nsresult + NewFile(nsISupports* *aNewObject) + { + // Initialization will set the filename, so we can pass in an empty string + // for now. + return NewFile(EmptyString(), aNewObject); + } + virtual const nsTArray >* GetSubBlobs() const { return &mBlobs; } @@ -119,8 +132,7 @@ protected: if (!bufferLen.isValid()) return false; - // PR_ memory functions are still fallible - void* data = PR_Realloc(mData, bufferLen.value()); + void* data = moz_realloc(mData, bufferLen.value()); if (!data) return false; diff --git a/content/base/src/nsDOMDataChannel.cpp b/content/base/src/nsDOMDataChannel.cpp index 103c790d666a..470464136387 100644 --- a/content/base/src/nsDOMDataChannel.cpp +++ b/content/base/src/nsDOMDataChannel.cpp @@ -335,7 +335,7 @@ nsDOMDataChannel::GetSendParams(nsIVariant* aData, nsCString& aStringOut, uint64_t blobLen; rv = blob->GetSize(&blobLen); NS_ENSURE_SUCCESS(rv, rv); - if (blobLen > PR_UINT32_MAX) { + if (blobLen > UINT32_MAX) { return NS_ERROR_FILE_TOO_BIG; } aOutgoingLength = static_cast(blobLen); diff --git a/content/base/src/nsDOMFile.cpp b/content/base/src/nsDOMFile.cpp index 49eace0c23d9..f556f2a62806 100644 --- a/content/base/src/nsDOMFile.cpp +++ b/content/base/src/nsDOMFile.cpp @@ -36,8 +36,6 @@ #include "mozilla/Preferences.h" #include "mozilla/Attributes.h" -#include "plbase64.h" -#include "prmem.h" #include "mozilla/dom/FileListBinding.h" using namespace mozilla; @@ -470,9 +468,6 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMFileCC) //////////////////////////////////////////////////////////////////////////// // nsDOMFileFile implementation -NS_IMPL_ISUPPORTS_INHERITED1(nsDOMFileFile, nsDOMFile, - nsIJSNativeInitializer) - already_AddRefed nsDOMFileFile::CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType) @@ -481,14 +476,6 @@ nsDOMFileFile::CreateSlice(uint64_t aStart, uint64_t aLength, return t.forget(); } -/* static */ nsresult -nsDOMFileFile::NewFile(nsISupports* *aNewObject) -{ - nsCOMPtr file = do_QueryObject(new nsDOMFileFile()); - file.forget(aNewObject); - return NS_OK; -} - NS_IMETHODIMP nsDOMFileFile::GetMozFullPathInternal(nsAString &aFilename) { @@ -593,71 +580,6 @@ nsDOMFileFile::GetInternalStream(nsIInputStream **aStream) -1, -1, sFileStreamFlags); } -NS_IMETHODIMP -nsDOMFileFile::Initialize(nsISupports* aOwner, - JSContext* aCx, - JSObject* aObj, - uint32_t aArgc, - JS::Value* aArgv) -{ - nsresult rv; - - NS_ASSERTION(!mImmutable, "Something went wrong ..."); - NS_ENSURE_TRUE(!mImmutable, NS_ERROR_UNEXPECTED); - - if (!nsContentUtils::IsCallerChrome()) { - return NS_ERROR_DOM_SECURITY_ERR; // Real short trip - } - - NS_ENSURE_TRUE(aArgc > 0, NS_ERROR_UNEXPECTED); - - // We expect to get a path to represent as a File object, - // or an nsIFile - nsCOMPtr file; - if (!aArgv[0].isString()) { - // Lets see if it's an nsIFile - if (!aArgv[0].isObject()) { - return NS_ERROR_UNEXPECTED; // We're not interested - } - - JSObject* obj = &aArgv[0].toObject(); - - // Is it an nsIFile - file = do_QueryInterface( - nsContentUtils::XPConnect()-> - GetNativeOfWrapper(aCx, obj)); - if (!file) - return NS_ERROR_UNEXPECTED; - } else { - // It's a string - JSString* str = JS_ValueToString(aCx, aArgv[0]); - NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS); - - nsDependentJSString xpcomStr; - if (!xpcomStr.init(aCx, str)) { - return NS_ERROR_XPC_BAD_CONVERT_JS; - } - - rv = NS_NewLocalFile(xpcomStr, false, getter_AddRefs(file)); - NS_ENSURE_SUCCESS(rv, rv); - } - - bool exists; - rv = file->Exists(&exists); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(exists, NS_ERROR_FILE_NOT_FOUND); - - bool isDir; - rv = file->IsDirectory(&isDir); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_FALSE(isDir, NS_ERROR_FILE_IS_DIRECTORY); - - mFile = file; - file->GetLeafName(mName); - - return NS_OK; -} - //////////////////////////////////////////////////////////////////////////// // nsDOMMemoryFile implementation diff --git a/content/base/src/nsDOMFileReader.cpp b/content/base/src/nsDOMFileReader.cpp index a21bbaacff89..225e78ad8e8e 100644 --- a/content/base/src/nsDOMFileReader.cpp +++ b/content/base/src/nsDOMFileReader.cpp @@ -20,9 +20,6 @@ #include "nsNetCID.h" #include "nsNetUtil.h" -#include "plbase64.h" -#include "prmem.h" - #include "nsLayoutCID.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" @@ -39,6 +36,7 @@ #include "nsLayoutStatics.h" #include "nsIScriptObjectPrincipal.h" #include "nsHostObjectProtocolHandler.h" +#include "mozilla/Base64.h" #include "mozilla/Preferences.h" #include "mozilla/dom/EncodingUtils.h" #include "xpcpublic.h" @@ -319,7 +317,7 @@ nsDOMFileReader::DoOnDataAvailable(nsIRequest *aRequest, // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS return NS_ERROR_OUT_OF_MEMORY; } - mFileData = (char *)PR_Realloc(mFileData, aOffset + aCount); + mFileData = (char *)moz_realloc(mFileData, aOffset + aCount); NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY); uint32_t bytesRead = 0; @@ -491,26 +489,11 @@ nsDOMFileReader::GetAsDataURL(nsIDOMBlob *aFile, } aResult.AppendLiteral(";base64,"); - uint32_t totalRead = 0; - while (aDataLen > totalRead) { - uint32_t numEncode = 4096; - uint32_t amtRemaining = aDataLen - totalRead; - if (numEncode > amtRemaining) - numEncode = amtRemaining; + nsCString encodedData; + rv = Base64Encode(Substring(aFileData, aDataLen), encodedData); + NS_ENSURE_SUCCESS(rv, rv); - //Unless this is the end of the file, encode in multiples of 3 - if (numEncode > 3) { - uint32_t leftOver = numEncode % 3; - numEncode -= leftOver; - } - - //Out buffer should be at least 4/3rds the read buf, plus a terminator - char *base64 = PL_Base64Encode(aFileData + totalRead, numEncode, nullptr); - AppendASCIItoUTF16(nsDependentCString(base64), aResult); - PR_Free(base64); - - totalRead += numEncode; - } + AppendASCIItoUTF16(encodedData, aResult); return NS_OK; } diff --git a/content/base/src/nsDOMFileReader.h b/content/base/src/nsDOMFileReader.h index c765c41edb89..b657a72543ec 100644 --- a/content/base/src/nsDOMFileReader.h +++ b/content/base/src/nsDOMFileReader.h @@ -24,7 +24,6 @@ #include "nsCOMPtr.h" #include "nsIStreamLoader.h" #include "nsIChannel.h" -#include "prmem.h" #include "FileIOObject.h" @@ -81,7 +80,7 @@ protected: nsresult ConvertStream(const char *aFileData, uint32_t aDataLen, const char *aCharset, nsAString &aResult); void FreeFileData() { - PR_Free(mFileData); + moz_free(mFileData); mFileData = nullptr; mDataLen = 0; } diff --git a/content/base/src/nsDOMParser.cpp b/content/base/src/nsDOMParser.cpp index c02d3780563d..f17667994504 100644 --- a/content/base/src/nsDOMParser.cpp +++ b/content/base/src/nsDOMParser.cpp @@ -420,7 +420,7 @@ nsDOMParser::InitInternal(nsISupports* aOwner, nsIPrincipal* prin, nsCOMPtr doc; nsCOMPtr window = do_QueryInterface(aOwner); - if (aOwner) { + if (window) { nsCOMPtr domdoc = window->GetExtantDocument(); doc = do_QueryInterface(domdoc); } diff --git a/content/base/src/nsFormData.cpp b/content/base/src/nsFormData.cpp index 291e65188dcc..f5e8e13af262 100644 --- a/content/base/src/nsFormData.cpp +++ b/content/base/src/nsFormData.cpp @@ -6,26 +6,29 @@ #include "nsIVariant.h" #include "nsIInputStream.h" #include "nsIDOMFile.h" -#include "nsContentUtils.h" #include "nsHTMLFormElement.h" +#include "mozilla/dom/FormDataBinding.h" -nsFormData::nsFormData() +using namespace mozilla; +using namespace mozilla::dom; + +nsFormData::nsFormData(nsISupports* aOwner) : nsFormSubmission(NS_LITERAL_CSTRING("UTF-8"), nullptr) + , mOwner(aOwner) { + SetIsDOMBinding(); } // ------------------------------------------------------------------------- // nsISupports -DOMCI_DATA(FormData, nsFormData) - -NS_IMPL_ADDREF(nsFormData) -NS_IMPL_RELEASE(nsFormData) -NS_INTERFACE_MAP_BEGIN(nsFormData) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsFormData, mOwner) +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormData) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormData) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormData) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsIDOMFormData) NS_INTERFACE_MAP_ENTRY(nsIXHRSendable) - NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(FormData) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFormData) NS_INTERFACE_MAP_END @@ -39,28 +42,22 @@ nsFormData::GetEncodedSubmission(nsIURI* aURI, return NS_OK; } -nsresult -nsFormData::AddNameValuePair(const nsAString& aName, - const nsAString& aValue) +void +nsFormData::Append(const nsAString& aName, const nsAString& aValue) { FormDataTuple* data = mFormData.AppendElement(); data->name = aName; data->stringValue = aValue; data->valueIsFile = false; - - return NS_OK; } -nsresult -nsFormData::AddNameFilePair(const nsAString& aName, - nsIDOMBlob* aBlob) +void +nsFormData::Append(const nsAString& aName, nsIDOMBlob* aBlob) { FormDataTuple* data = mFormData.AppendElement(); data->name = aName; data->fileValue = aBlob; data->valueIsFile = true; - - return NS_OK; } // ------------------------------------------------------------------------- @@ -84,7 +81,8 @@ nsFormData::Append(const nsAString& aName, nsIVariant* aValue) nsCOMPtr domBlob = do_QueryInterface(supports); if (domBlob) { - return AddNameFilePair(aName, domBlob); + Append(aName, domBlob); + return NS_OK; } } @@ -96,7 +94,27 @@ nsFormData::Append(const nsAString& aName, nsIVariant* aValue) nsString valAsString; valAsString.Adopt(stringData, stringLen); - return AddNameValuePair(aName, valAsString); + Append(aName, valAsString); + return NS_OK; +} + +/* virtual */ JSObject* +nsFormData::WrapObject(JSContext* aCx, JSObject* aScope, bool* aTriedToWrap) +{ + return FormDataBinding::Wrap(aCx, aScope, this, aTriedToWrap); +} + +/* static */ already_AddRefed +nsFormData::Constructor(nsISupports* aGlobal, + const Optional& aFormElement, + ErrorResult& aRv) +{ + nsRefPtr formData = new nsFormData(aGlobal); + if (aFormElement.WasPassed()) { + MOZ_ASSERT(aFormElement.Value()); + aRv = aFormElement.Value()->WalkFormElements(formData); + } + return formData.forget(); } // ------------------------------------------------------------------------- @@ -124,35 +142,3 @@ nsFormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength, return NS_OK; } - - -// ------------------------------------------------------------------------- -// nsIJSNativeInitializer - -NS_IMETHODIMP -nsFormData::Initialize(nsISupports* aOwner, - JSContext* aCx, - JSObject* aObj, - uint32_t aArgc, - jsval* aArgv) -{ - if (aArgc > 0) { - if (JSVAL_IS_PRIMITIVE(aArgv[0])) { - return NS_ERROR_UNEXPECTED; - } - nsCOMPtr formCont = do_QueryInterface( - nsContentUtils::XPConnect()-> - GetNativeOfWrapper(aCx, JSVAL_TO_OBJECT(aArgv[0]))); - - if (!formCont || !formCont->IsHTML(nsGkAtoms::form)) { - return NS_ERROR_UNEXPECTED; - } - - nsresult rv = static_cast(formCont.get())-> - WalkFormElements(this); - NS_ENSURE_SUCCESS(rv, rv); - } - - - return NS_OK; -} diff --git a/content/base/src/nsFormData.h b/content/base/src/nsFormData.h index 751b12ed11fe..c2a83166d25c 100644 --- a/content/base/src/nsFormData.h +++ b/content/base/src/nsFormData.h @@ -8,34 +8,72 @@ #include "nsIDOMFormData.h" #include "nsIXMLHttpRequest.h" #include "nsFormSubmission.h" -#include "nsIJSNativeInitializer.h" +#include "nsWrapperCache.h" #include "nsTArray.h" +#include "mozilla/ErrorResult.h" +class nsHTMLFormElement; class nsIDOMFile; +namespace mozilla { +class ErrorResult; + +namespace dom { +template class Optional; +} // namespace dom +} // namespace mozilla + class nsFormData : public nsIDOMFormData, public nsIXHRSendable, - public nsIJSNativeInitializer, - public nsFormSubmission + public nsFormSubmission, + public nsWrapperCache { public: - nsFormData(); + nsFormData(nsISupports* aOwner = nullptr); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsFormData, + nsIDOMFormData) - NS_DECL_ISUPPORTS NS_DECL_NSIDOMFORMDATA NS_DECL_NSIXHRSENDABLE + // nsWrapperCache + virtual JSObject* + WrapObject(JSContext* aCx, JSObject* aScope, bool* aTriedToWrap) MOZ_OVERRIDE; + + // WebIDL + nsISupports* + GetParentObject() const + { + return mOwner; + } + static already_AddRefed + Constructor(nsISupports* aGlobal, + const mozilla::dom::Optional& aFormElement, + mozilla::ErrorResult& aRv); + void Append(const nsAString& aName, const nsAString& aValue); + void Append(const nsAString& aName, nsIDOMBlob* aBlob); + // nsFormSubmission virtual nsresult GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream); virtual nsresult AddNameValuePair(const nsAString& aName, - const nsAString& aValue); + const nsAString& aValue) + { + Append(aName, aValue); + return NS_OK; + } virtual nsresult AddNameFilePair(const nsAString& aName, - nsIDOMBlob* aBlob); + nsIDOMBlob* aBlob) + { + Append(aName, aBlob); + return NS_OK; + } - NS_IMETHOD Initialize(nsISupports* aOwner, JSContext* aCx, JSObject* aObj, - uint32_t aArgc, jsval* aArgv); private: + nsCOMPtr mOwner; + struct FormDataTuple { nsString name; @@ -43,7 +81,7 @@ private: nsCOMPtr fileValue; bool valueIsFile; }; - + nsTArray mFormData; }; diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index c00f0a5f5df3..841957ee26ee 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -79,9 +79,10 @@ #include "nsIPermissionManager.h" #include "nsMimeTypes.h" #include "nsIHttpChannelInternal.h" +#include "nsFormData.h" +#include "nsStreamListenerWrapper.h" #include "nsWrapperCacheInlines.h" -#include "nsStreamListenerWrapper.h" using namespace mozilla; using namespace mozilla::dom; @@ -2666,11 +2667,9 @@ nsXMLHttpRequest::GetRequestBody(nsIVariant* aVariant, } case nsXMLHttpRequest::RequestBody::FormData: { - nsresult rv; - nsCOMPtr sendable = do_QueryInterface(value.mFormData, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - return ::GetRequestBody(sendable, aResult, aContentLength, aContentType, aCharset); + MOZ_ASSERT(value.mFormData); + return ::GetRequestBody(value.mFormData, aResult, aContentLength, + aContentType, aCharset); } case nsXMLHttpRequest::RequestBody::InputStream: { diff --git a/content/base/src/nsXMLHttpRequest.h b/content/base/src/nsXMLHttpRequest.h index 13eb7c38725e..b5028b45703c 100644 --- a/content/base/src/nsXMLHttpRequest.h +++ b/content/base/src/nsXMLHttpRequest.h @@ -52,7 +52,7 @@ class nsILoadGroup; class AsyncVerifyRedirectCallbackForwarder; class nsIUnicodeDecoder; -class nsIDOMFormData; +class nsFormData; class nsXHREventTarget : public nsDOMEventTargetHelper, public nsIXMLHttpRequestEventTarget @@ -288,9 +288,9 @@ private: { mValue.mString = &aString; } - RequestBody(nsIDOMFormData* aFormData) : mType(FormData) + RequestBody(nsFormData& aFormData) : mType(FormData) { - mValue.mFormData = aFormData; + mValue.mFormData = &aFormData; } RequestBody(nsIInputStream* aStream) : mType(InputStream) { @@ -311,7 +311,7 @@ private: nsIDOMBlob* mBlob; nsIDocument* mDocument; const nsAString* mString; - nsIDOMFormData* mFormData; + nsFormData* mFormData; nsIInputStream* mStream; }; @@ -376,9 +376,8 @@ public: aRv = Send(RequestBody(aString)); } } - void Send(nsIDOMFormData* aFormData, ErrorResult& aRv) + void Send(nsFormData& aFormData, ErrorResult& aRv) { - NS_ASSERTION(aFormData, "Null should go to string version"); aRv = Send(RequestBody(aFormData)); } void Send(nsIInputStream* aStream, ErrorResult& aRv) diff --git a/content/canvas/src/WebGLContext.cpp b/content/canvas/src/WebGLContext.cpp index 9a74df6d25dd..3effa278014f 100644 --- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -159,6 +159,7 @@ WebGLContext::WebGLContext() mGLMaxTextureUnits = 0; mGLMaxTextureSize = 0; mGLMaxCubeMapTextureSize = 0; + mGLMaxRenderbufferSize = 0; mGLMaxTextureImageUnits = 0; mGLMaxVertexTextureImageUnits = 0; mGLMaxVaryingVectors = 0; diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index 63b2a8effc34..63aed2c16d42 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -858,6 +858,7 @@ protected: int32_t mGLMaxTextureUnits; int32_t mGLMaxTextureSize; int32_t mGLMaxCubeMapTextureSize; + int32_t mGLMaxRenderbufferSize; int32_t mGLMaxTextureImageUnits; int32_t mGLMaxVertexTextureImageUnits; int32_t mGLMaxVaryingVectors; @@ -1127,6 +1128,8 @@ protected: template JSObject* WebGLObjectAsJSObject(JSContext *cx, const WebGLObjectType *, ErrorResult& rv) const; + void ReattachTextureToAnyFramebufferToWorkAroundBugs(WebGLTexture *tex, WebGLint level); + #ifdef XP_MACOSX // see bug 713305. This RAII helper guarantees that we're on the discrete GPU, during its lifetime // Debouncing note: we don't want to switch GPUs too frequently, so try to not create and destroy diff --git a/content/canvas/src/WebGLContextGL.cpp b/content/canvas/src/WebGLContextGL.cpp index 5e3ab126dab8..ca492942b288 100644 --- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -748,6 +748,9 @@ WebGLContext::CopyTexSubImage2D_base(WebGLenum target, gl->fCopyTexSubImage2D(target, level, actual_xoffset, actual_yoffset, actual_x, actual_y, actual_width, actual_height); } + + if (!sub) + ReattachTextureToAnyFramebufferToWorkAroundBugs(tex, level); } void @@ -1962,15 +1965,12 @@ WebGLContext::GetParameter(JSContext* cx, WebGLenum pname, ErrorResult& rv) case LOCAL_GL_UNPACK_ALIGNMENT: case LOCAL_GL_PACK_ALIGNMENT: case LOCAL_GL_SUBPIXEL_BITS: - case LOCAL_GL_MAX_TEXTURE_SIZE: - case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE: case LOCAL_GL_SAMPLE_BUFFERS: case LOCAL_GL_SAMPLES: case LOCAL_GL_MAX_VERTEX_ATTRIBS: case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: case LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: case LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS: - case LOCAL_GL_MAX_RENDERBUFFER_SIZE: case LOCAL_GL_RED_BITS: case LOCAL_GL_GREEN_BITS: case LOCAL_GL_BLUE_BITS: @@ -1993,6 +1993,15 @@ WebGLContext::GetParameter(JSContext* cx, WebGLenum pname, ErrorResult& rv) return JS::NullValue(); } + case LOCAL_GL_MAX_TEXTURE_SIZE: + return JS::Int32Value(mGLMaxTextureSize); + + case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE: + return JS::Int32Value(mGLMaxCubeMapTextureSize); + + case LOCAL_GL_MAX_RENDERBUFFER_SIZE: + return JS::Int32Value(mGLMaxRenderbufferSize); + case LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS: return JS::Int32Value(mGLMaxVertexUniformVectors); @@ -3373,8 +3382,8 @@ WebGLContext::RenderbufferStorage(WebGLenum target, WebGLenum internalformat, We if (width < 0 || height < 0) return ErrorInvalidValue("renderbufferStorage: width and height must be >= 0"); - if (!mBoundRenderbuffer || !mBoundRenderbuffer->GLName()) - return ErrorInvalidOperation("renderbufferStorage called on renderbuffer 0"); + if (width > mGLMaxRenderbufferSize || height > mGLMaxRenderbufferSize) + return ErrorInvalidValue("renderbufferStorage: width or height exceeds maximum renderbuffer size"); // certain OpenGL ES renderbuffer formats may not exist on desktop OpenGL WebGLenum internalformatForGL = internalformat; @@ -4183,7 +4192,8 @@ WebGLContext::CompileShader(WebGLShader *shader) targetShaderSourceLanguage, &resources); - int compileOptions = SH_ATTRIBUTES_UNIFORMS; + int compileOptions = SH_ATTRIBUTES_UNIFORMS | + SH_ENFORCE_PACKING_RESTRICTIONS; if (useShaderSourceTranslation) { compileOptions |= SH_OBJECT_CODE | SH_MAP_LONG_VARIABLE_NAMES; @@ -4345,6 +4355,8 @@ WebGLContext::CompressedTexImage2D(WebGLenum target, WebGLint level, WebGLenum i gl->fCompressedTexImage2D(target, level, internalformat, width, height, border, byteLength, view.Data()); tex->SetImageInfo(target, level, width, height, internalformat, LOCAL_GL_UNSIGNED_BYTE); + + ReattachTextureToAnyFramebufferToWorkAroundBugs(tex, level); } void @@ -4857,13 +4869,15 @@ WebGLContext::TexImage2D_base(WebGLenum target, WebGLint level, WebGLenum intern free(tempZeroData); } - + if (error) { GenerateWarning("texImage2D generated error %s", ErrorName(error)); return; } tex->SetImageInfo(target, level, width, height, format, type); + + ReattachTextureToAnyFramebufferToWorkAroundBugs(tex, level); } void @@ -5276,3 +5290,43 @@ InternalFormatForFormatAndType(WebGLenum format, WebGLenum type, bool isGLES2) return 0; } +void +WebGLContext::ReattachTextureToAnyFramebufferToWorkAroundBugs(WebGLTexture *tex, + WebGLint level) +{ + MOZ_ASSERT(tex); + + if (!gl->WorkAroundDriverBugs()) + return; + + if (!mIsMesa) + return; + + MakeContextCurrent(); + + for(WebGLFramebuffer *framebuffer = mFramebuffers.getFirst(); + framebuffer; + framebuffer = framebuffer->getNext()) + { + if (framebuffer->ColorAttachment().Texture() == tex) { + framebuffer->FramebufferTexture2D( + LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, + tex->Target(), tex, level); + } + if (framebuffer->DepthAttachment().Texture() == tex) { + framebuffer->FramebufferTexture2D( + LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, + tex->Target(), tex, level); + } + if (framebuffer->StencilAttachment().Texture() == tex) { + framebuffer->FramebufferTexture2D( + LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, + tex->Target(), tex, level); + } + if (framebuffer->DepthStencilAttachment().Texture() == tex) { + framebuffer->FramebufferTexture2D( + LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT, + tex->Target(), tex, level); + } + } +} diff --git a/content/canvas/src/WebGLContextValidate.cpp b/content/canvas/src/WebGLContextValidate.cpp index a03f46eab0f2..c11e6245d49b 100644 --- a/content/canvas/src/WebGLContextValidate.cpp +++ b/content/canvas/src/WebGLContextValidate.cpp @@ -912,11 +912,13 @@ WebGLContext::InitAndValidateGL() if (MinCapabilityMode()) { mGLMaxTextureSize = MINVALUE_GL_MAX_TEXTURE_SIZE; mGLMaxCubeMapTextureSize = MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE; + mGLMaxRenderbufferSize = MINVALUE_GL_MAX_RENDERBUFFER_SIZE; mGLMaxTextureImageUnits = MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS; mGLMaxVertexTextureImageUnits = MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS; } else { gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mGLMaxTextureSize); gl->fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mGLMaxCubeMapTextureSize); + gl->fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mGLMaxRenderbufferSize); gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS, &mGLMaxTextureImageUnits); gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mGLMaxVertexTextureImageUnits); } diff --git a/content/html/content/src/nsGenericHTMLElement.cpp b/content/html/content/src/nsGenericHTMLElement.cpp index 3b60b7375477..fd349a83e7f2 100644 --- a/content/html/content/src/nsGenericHTMLElement.cpp +++ b/content/html/content/src/nsGenericHTMLElement.cpp @@ -379,8 +379,18 @@ static bool IsOffsetParent(nsIFrame* aFrame) { nsIAtom* frameType = aFrame->GetType(); - return (IS_TABLE_CELL(frameType) || - frameType == nsGkAtoms::tableFrame); + + if (IS_TABLE_CELL(frameType) || frameType == nsGkAtoms::tableFrame) { + // Per the IDL for Element, only td, th, and table are acceptable offsetParents + // apart from body or positioned elements; we need to check the content type as + // well as the frame type so we ignore anonymous tables created by an element + // with display: table-cell with no actual table + nsIContent* content = aFrame->GetContent(); + + return content->IsHTML(nsGkAtoms::table) || content->IsHTML(nsGkAtoms::td) + || content->IsHTML(nsGkAtoms::th); + } + return false; } Element* diff --git a/content/html/content/test/Makefile.in b/content/html/content/test/Makefile.in index 4b3ad3e7fb58..ad67d2e9f68d 100644 --- a/content/html/content/test/Makefile.in +++ b/content/html/content/test/Makefile.in @@ -270,6 +270,7 @@ MOCHITEST_FILES = \ test_bug786564.html \ test_bug797113.html \ test_bug787134.html \ + test_bug803677.html \ test_iframe_sandbox_inheritance.html \ file_iframe_sandbox_a_if1.html \ file_iframe_sandbox_a_if2.html \ diff --git a/content/html/content/test/test_bug803677.html b/content/html/content/test/test_bug803677.html new file mode 100644 index 000000000000..fd754ba001bd --- /dev/null +++ b/content/html/content/test/test_bug803677.html @@ -0,0 +1,50 @@ + + + + + + Test for Bug 803677 + + + + + + + +Mozilla Bug 803677 +

+
+

1

+

2

+

3

+

4

+

7

+

8

+

9

+
+
+
+
+ + diff --git a/content/smil/nsSMILAnimationController.cpp b/content/smil/nsSMILAnimationController.cpp index 456d01e9927b..6de9c056abbe 100644 --- a/content/smil/nsSMILAnimationController.cpp +++ b/content/smil/nsSMILAnimationController.cpp @@ -14,6 +14,7 @@ #include "nsIDOMSVGAnimationElement.h" #include "nsSMILTimedElement.h" +using namespace mozilla; using namespace mozilla::dom; //---------------------------------------------------------------------- @@ -366,14 +367,8 @@ nsSMILAnimationController::DoSample(bool aSkipUnchangedContainers) mResampleNeeded = false; // Set running sample flag -- do this before flushing styles so that when we // flush styles we don't end up requesting extra samples + AutoRestore autoRestoreRunningSample(mRunningSample); mRunningSample = true; - nsCOMPtr kungFuDeathGrip(mDocument); // keeps 'this' alive too - mDocument->FlushPendingNotifications(Flush_Style); - - // WARNING: - // WARNING: the above flush may have destroyed the pres shell and/or - // WARNING: frames and other layout related objects. - // WARNING: // STEP 1: Bring model up to date // (i) Rewind elements where necessary @@ -441,13 +436,26 @@ nsSMILAnimationController::DoSample(bool aSkipUnchangedContainers) mLastCompositorTable->EnumerateEntries(DoClearAnimationEffects, nullptr); } + // return early if there are no active animations to avoid a style flush + if (currentCompositorTable->Count() == 0) { + mLastCompositorTable = nullptr; + return; + } + + nsCOMPtr kungFuDeathGrip(mDocument); // keeps 'this' alive too + mDocument->FlushPendingNotifications(Flush_Style); + + // WARNING: + // WARNING: the above flush may have destroyed the pres shell and/or + // WARNING: frames and other layout related objects. + // WARNING: + // STEP 5: Compose currently-animated attributes. // XXXdholbert: This step traverses our animation targets in an effectively // random order. For animation from/to 'inherit' values to work correctly // when the inherited value is *also* being animated, we really should be // traversing our animated nodes in an ancestors-first order (bug 501183) currentCompositorTable->EnumerateEntries(DoComposeAttribute, nullptr); - mRunningSample = false; // Update last compositor table mLastCompositorTable = currentCompositorTable.forget(); diff --git a/content/xul/templates/src/nsXULTreeBuilder.cpp b/content/xul/templates/src/nsXULTreeBuilder.cpp index 2617d17ebe98..3e2441a3290c 100644 --- a/content/xul/templates/src/nsXULTreeBuilder.cpp +++ b/content/xul/templates/src/nsXULTreeBuilder.cpp @@ -240,7 +240,7 @@ protected: /** * The builder observers. */ - nsCOMPtr mObservers; + nsCOMArray mObservers; }; //---------------------------------------------------------------------- @@ -282,22 +282,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXULTreeBuilder, nsXULTemplateB NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers) NS_IMPL_CYCLE_COLLECTION_UNLINK_END -static bool TraverseObservers(nsISupports* aElement, void *aData) -{ - nsCycleCollectionTraversalCallback *cb = - static_cast(aData); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mObservers[i]"); - cb->NoteXPCOMChild(aElement); - return true; -} - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBoxObject) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPersistStateStore) - if (tmp->mObservers) { - tmp->mObservers->EnumerateForwards(TraverseObservers, &cb); - } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END DOMCI_DATA(XULTreeBuilder, nsXULTreeBuilder) @@ -361,20 +350,13 @@ nsXULTreeBuilder::GetIndexOfResource(nsIRDFResource* aResource, int32_t* aResult NS_IMETHODIMP nsXULTreeBuilder::AddObserver(nsIXULTreeBuilderObserver* aObserver) { - nsresult rv; - if (!mObservers) { - rv = NS_NewISupportsArray(getter_AddRefs(mObservers)); - if (NS_FAILED(rv)) - return rv; - } - - return mObservers->AppendElement(aObserver); + return mObservers.AppendObject(aObserver) ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsXULTreeBuilder::RemoveObserver(nsIXULTreeBuilderObserver* aObserver) { - return mObservers ? mObservers->RemoveElement(aObserver) : NS_ERROR_FAILURE; + return mObservers.RemoveObject(aObserver) ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP @@ -855,14 +837,11 @@ nsXULTreeBuilder::ToggleOpenState(int32_t aIndex) return rv; } - if (mObservers) { - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) - observer->OnToggleOpenState(aIndex); - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) + observer->OnToggleOpenState(aIndex); } if (mPersistStateStore) { @@ -908,17 +887,14 @@ nsXULTreeBuilder::CycleHeader(nsITreeColumn* aCol) nsCOMPtr element; aCol->GetElement(getter_AddRefs(element)); - if (mObservers) { - nsAutoString id; - aCol->GetId(id); + nsAutoString id; + aCol->GetId(id); - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) - observer->OnCycleHeader(id.get(), element); - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) + observer->OnCycleHeader(id.get(), element); } return Sort(element); @@ -927,14 +903,11 @@ nsXULTreeBuilder::CycleHeader(nsITreeColumn* aCol) NS_IMETHODIMP nsXULTreeBuilder::SelectionChanged() { - if (mObservers) { - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) - observer->OnSelectionChanged(); - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) + observer->OnSelectionChanged(); } return NS_OK; @@ -944,17 +917,15 @@ NS_IMETHODIMP nsXULTreeBuilder::CycleCell(int32_t aRow, nsITreeColumn* aCol) { NS_ENSURE_ARG_POINTER(aCol); - if (mObservers) { - nsAutoString id; - aCol->GetId(id); - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) - observer->OnCycleCell(aRow, id.get()); - } + nsAutoString id; + aCol->GetId(id); + + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) + observer->OnCycleCell(aRow, id.get()); } return NS_OK; @@ -1029,14 +1000,11 @@ nsXULTreeBuilder::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString NS_IMETHODIMP nsXULTreeBuilder::PerformAction(const PRUnichar* aAction) { - if (mObservers) { - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) - observer->OnPerformAction(aAction); - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) + observer->OnPerformAction(aAction); } return NS_OK; @@ -1045,14 +1013,11 @@ nsXULTreeBuilder::PerformAction(const PRUnichar* aAction) NS_IMETHODIMP nsXULTreeBuilder::PerformActionOnRow(const PRUnichar* aAction, int32_t aRow) { - if (mObservers) { - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) - observer->OnPerformActionOnRow(aAction, aRow); - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) + observer->OnPerformActionOnRow(aAction, aRow); } return NS_OK; @@ -1062,17 +1027,14 @@ NS_IMETHODIMP nsXULTreeBuilder::PerformActionOnCell(const PRUnichar* aAction, int32_t aRow, nsITreeColumn* aCol) { NS_ENSURE_ARG_POINTER(aCol); - if (mObservers) { - nsAutoString id; - aCol->GetId(id); + nsAutoString id; + aCol->GetId(id); - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) - observer->OnPerformActionOnCell(aAction, aRow, id.get()); - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) + observer->OnPerformActionOnCell(aAction, aRow, id.get()); } return NS_OK; @@ -1083,8 +1045,7 @@ void nsXULTreeBuilder::NodeWillBeDestroyed(const nsINode* aNode) { nsCOMPtr kungFuDeathGrip(this); - if (mObservers) - mObservers->Clear(); + mObservers.Clear(); nsXULTemplateBuilder::NodeWillBeDestroyed(aNode); } @@ -1918,16 +1879,13 @@ nsXULTreeBuilder::CanDrop(int32_t index, int32_t orientation, nsIDOMDataTransfer* dataTransfer, bool *_retval) { *_retval = false; - if (mObservers) { - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) { - observer->CanDrop(index, orientation, dataTransfer, _retval); - if (*_retval) - break; - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) { + observer->CanDrop(index, orientation, dataTransfer, _retval); + if (*_retval) + break; } } @@ -1937,17 +1895,14 @@ nsXULTreeBuilder::CanDrop(int32_t index, int32_t orientation, NS_IMETHODIMP nsXULTreeBuilder::Drop(int32_t row, int32_t orient, nsIDOMDataTransfer* dataTransfer) { - if (mObservers) { - uint32_t count; - mObservers->Count(&count); - for (uint32_t i = 0; i < count; ++i) { - nsCOMPtr observer = do_QueryElementAt(mObservers, i); - if (observer) { - bool canDrop = false; - observer->CanDrop(row, orient, dataTransfer, &canDrop); - if (canDrop) - observer->OnDrop(row, orient, dataTransfer); - } + uint32_t count = mObservers.Count(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr observer = mObservers.SafeObjectAt(i); + if (observer) { + bool canDrop = false; + observer->CanDrop(row, orient, dataTransfer, &canDrop); + if (canDrop) + observer->OnDrop(row, orient, dataTransfer); } } diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 370c2d4f5421..c35db8ec8107 100644 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -206,6 +206,7 @@ this.DOMApplicationRegistry = { // Installs a 3rd party packaged app. installPreinstalledPackage: function installPreinstalledPackage(aId) { +#ifdef MOZ_WIDGET_GONK let app = this.webapps[aId]; let baseDir; try { @@ -267,6 +268,7 @@ this.DOMApplicationRegistry = { } finally { zipReader.close(); } +#endif }, // Implements the core of bug 787439 @@ -937,9 +939,11 @@ this.DOMApplicationRegistry = { } let manifest = new ManifestHelper(aJSON, app.installOrigin); - this.downloadPackage(manifest, { manifestURL: aManifestURL, - origin: app.origin }, isUpdate, - function(aId, aManifest) { + this.downloadPackage(manifest, { + manifestURL: aManifestURL, + origin: app.origin, + downloadSize: app.downloadSize + }, isUpdate, function(aId, aManifest) { // Success! Keep the zip in of TmpD, we'll move it out when // applyDownload() will be called. let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true); diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 1ef320e80e52..435705287a84 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -752,7 +752,7 @@ Navigator::Vibrate(const jsval& aPattern, JSContext* cx) nsCOMPtr win = do_QueryReferent(mWindow); NS_ENSURE_TRUE(win, NS_OK); - nsIDOMDocument* domDoc = win->GetExtantDocument(); + nsCOMPtr domDoc = win->GetExtantDocument(); NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); bool hidden = true; diff --git a/dom/base/ObjectWrapper.jsm b/dom/base/ObjectWrapper.jsm index 7521c351371d..de0cb988335e 100644 --- a/dom/base/ObjectWrapper.jsm +++ b/dom/base/ObjectWrapper.jsm @@ -13,14 +13,16 @@ this.EXPORTED_SYMBOLS = ["ObjectWrapper"]; // Makes sure that we expose correctly chrome JS objects to content. this.ObjectWrapper = { - getObjectKind: function objWrapper_getobjectkind(aObject) { + getObjectKind: function objWrapper_getObjectKind(aObject) { if (!aObject) { return "null"; } if (Array.isArray(aObject)) { return "array"; - } else if (aObject.mozSlice && (typeof aObject.mozSlice == "function")) { + } else if (aObject instanceof Ci.nsIDOMFile) { + return "file"; + } else if (aObject instanceof Ci.nsIDOMBlob) { return "blob"; } else if (typeof aObject == "object") { return "object"; @@ -30,48 +32,35 @@ this.ObjectWrapper = { }, wrap: function objWrapper_wrap(aObject, aCtxt) { - if (!aObject) { - return null; - } - // First check wich kind of object we have. let kind = this.getObjectKind(aObject); - if (kind == "array") { + if (kind == "null") { + return null; + } else if (kind == "array") { let res = Cu.createArrayIn(aCtxt); aObject.forEach(function(aObj) { res.push(this.wrap(aObj, aCtxt)); }, this); return res; + } else if (kind == "file") { + return new aCtxt.File(aObject, + { name: aObject.name, + type: aObject.type }); } else if (kind == "blob") { return new aCtxt.Blob([aObject]); } else if (kind == "primitive") { return aObject; } - // Fall-through, we now have a dictionnary object. + // Fall-through, we now have a dictionnary object. let res = Cu.createObjectIn(aCtxt); let propList = { }; for (let prop in aObject) { - let value; - let objProp = aObject[prop]; - let propKind = this.getObjectKind(objProp); - if (propKind == "array") { - value = Cu.createArrayIn(aCtxt); - objProp.forEach(function(aObj) { - value.push(this.wrap(aObj, aCtxt)); - }, this); - } else if (propKind == "blob") { - value = new aCtxt.Blob([objProp]); - } else if (propKind == "object") { - value = this.wrap(objProp, aCtxt); - } else { - value = objProp; - } propList[prop] = { enumerable: true, configurable: true, writable: true, - value: value + value: this.wrap(aObject[prop], aCtxt) } } Object.defineProperties(res, propList); diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 562a0e794c82..bb97ba818bb4 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -427,7 +427,6 @@ #include "nsDOMFile.h" #include "nsDOMFileReader.h" -#include "nsIDOMFormData.h" #include "ArchiveReader.h" #include "ArchiveRequest.h" @@ -1566,9 +1565,6 @@ static nsDOMClassInfoData sClassInfoData[] = { NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ChromeMessageSender, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) - NS_DEFINE_CLASSINFO_DATA(FormData, nsDOMGenericSH, - DOM_DEFAULT_SCRIPTABLE_FLAGS) - NS_DEFINE_CLASSINFO_DATA(DesktopNotification, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) NS_DEFINE_CLASSINFO_DATA(DesktopNotificationCenter, nsDOMGenericSH, @@ -1709,7 +1705,6 @@ static nsDOMClassInfoData sClassInfoData[] = { NS_DEFINE_CONTRACT_CTOR(FileReader, NS_FILEREADER_CONTRACTID) NS_DEFINE_CONTRACT_CTOR(ArchiveReader, NS_ARCHIVEREADER_CONTRACTID) -NS_DEFINE_CONTRACT_CTOR(FormData, NS_FORMDATA_CONTRACTID) NS_DEFINE_CONTRACT_CTOR(XPathEvaluator, NS_XPATH_EVALUATOR_CONTRACTID) NS_DEFINE_CONTRACT_CTOR(XSLTProcessor, "@mozilla.org/document-transformer;1?type=xslt") @@ -1757,7 +1752,7 @@ struct nsConstructorFuncMapData static const nsConstructorFuncMapData kConstructorFuncMap[] = { NS_DEFINE_CONSTRUCTOR_FUNC_DATA(Blob, nsDOMMultipartFile::NewBlob) - NS_DEFINE_CONSTRUCTOR_FUNC_DATA(File, nsDOMFileFile::NewFile) + NS_DEFINE_CONSTRUCTOR_FUNC_DATA(File, nsDOMMultipartFile::NewFile) NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(Event) NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(UIEvent) NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(MouseEvent) @@ -1774,7 +1769,6 @@ static const nsConstructorFuncMapData kConstructorFuncMap[] = NS_DEFINE_CONSTRUCTOR_FUNC_DATA(MozSmsFilter, sms::SmsFilter::NewSmsFilter) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(FileReader, FileReaderCtor) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(ArchiveReader, ArchiveReaderCtor) - NS_DEFINE_CONSTRUCTOR_FUNC_DATA(FormData, FormDataCtor) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(XPathEvaluator, XPathEvaluatorCtor) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(XSLTProcessor, XSLTProcessorCtor) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(EventSource, EventSourceCtor) @@ -4201,10 +4195,6 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_ENTRY(nsIMessageSender) DOM_CLASSINFO_MAP_END - DOM_CLASSINFO_MAP_BEGIN(FormData, nsIDOMFormData) - DOM_CLASSINFO_MAP_ENTRY(nsIDOMFormData) - DOM_CLASSINFO_MAP_END - DOM_CLASSINFO_MAP_BEGIN(DesktopNotification, nsIDOMDesktopNotification) DOM_CLASSINFO_MAP_ENTRY(nsIDOMDesktopNotification) DOM_CLASSINFO_MAP_END diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index f9ad3d312a40..0af7ac8e40e0 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -448,8 +448,6 @@ DOMCI_CLASS(ContentFrameMessageManager) DOMCI_CLASS(ChromeMessageBroadcaster) DOMCI_CLASS(ChromeMessageSender) -DOMCI_CLASS(FormData) - DOMCI_CLASS(DesktopNotification) DOMCI_CLASS(DesktopNotificationCenter) diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 952572cf2b48..b8c9ca330186 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -2546,6 +2546,13 @@ nsDOMWindowUtils::PreventFurtherDialogs() return NS_OK; } +static nsIDOMBlob* +GetXPConnectNative(JSContext* aCx, JSObject* aObj) { + nsCOMPtr blob = do_QueryInterface( + nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, aObj)); + return blob; +} + static nsresult GetFileOrBlob(const nsAString& aName, const jsval& aBlobParts, const jsval& aParameters, JSContext* aCx, @@ -2567,12 +2574,12 @@ GetFileOrBlob(const nsAString& aName, const jsval& aBlobParts, } NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr initializer = do_QueryInterface(file); - NS_ASSERTION(initializer, "what?"); + nsDOMMultipartFile* domFile = + static_cast(static_cast(file.get())); jsval args[2] = { aBlobParts, aParameters }; - rv = initializer->Initialize(nullptr, aCx, nullptr, aOptionalArgCount, args); + rv = domFile->InitBlob(aCx, aOptionalArgCount, args, GetXPConnectNative); NS_ENSURE_SUCCESS(rv, rv); file.forget(aResult); diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 06532be91f0e..34a07fd19993 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -245,8 +245,13 @@ DOMInterfaces = { }, 'FormData': [ +{ + 'nativeType': 'nsFormData' +}, { 'workers': True, + 'skipGen': True, + 'nativeType': 'JSObject' }], 'GainNode': [ @@ -743,6 +748,7 @@ def addExternalHTMLElement(element): headerFile=nativeElement + '.h') addExternalHTMLElement('HTMLCanvasElement') +addExternalHTMLElement('HTMLFormElement') addExternalHTMLElement('HTMLImageElement') addExternalHTMLElement('HTMLMenuElement') addExternalHTMLElement('HTMLOptionElement') diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index c43994e621f9..073957794dda 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2196,6 +2196,7 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, if not isOptional: typeName = CGWrapper(typeName, pre="const ") + # NOTE: Keep this in sync with variadic conversions as needed templateBody = ("""JSObject* seq = &${val}.toObject();\n if (!IsArrayLike(cx, seq)) { %s @@ -3042,9 +3043,6 @@ class CGArgumentConverter(CGThing): invalidEnumValueFatal=True, lenientFloatCode=None): CGThing.__init__(self) self.argument = argument - if argument.variadic: - raise TypeError("We don't support variadic arguments yet " + - str(argument.location)) assert(not argument.defaultValue or argument.optional) replacer = { @@ -3074,19 +3072,70 @@ class CGArgumentConverter(CGThing): self.lenientFloatCode = lenientFloatCode def define(self): - return instantiateJSToNativeConversionTemplate( - getJSToNativeConversionTemplate(self.argument.type, - self.descriptorProvider, - isOptional=(self.argcAndIndex is not None), - invalidEnumValueFatal=self.invalidEnumValueFatal, - defaultValue=self.argument.defaultValue, - treatNullAs=self.argument.treatNullAs, - treatUndefinedAs=self.argument.treatUndefinedAs, - isEnforceRange=self.argument.enforceRange, - isClamp=self.argument.clamp, - lenientFloatCode=self.lenientFloatCode), - self.replacementVariables, - self.argcAndIndex).define() + typeConversion = getJSToNativeConversionTemplate( + self.argument.type, + self.descriptorProvider, + isOptional=(self.argcAndIndex is not None and + not self.argument.variadic), + invalidEnumValueFatal=self.invalidEnumValueFatal, + defaultValue=self.argument.defaultValue, + treatNullAs=self.argument.treatNullAs, + treatUndefinedAs=self.argument.treatUndefinedAs, + isEnforceRange=self.argument.enforceRange, + isClamp=self.argument.clamp, + lenientFloatCode=self.lenientFloatCode, + isMember=self.argument.variadic) + + if not self.argument.variadic: + return instantiateJSToNativeConversionTemplate( + typeConversion, + self.replacementVariables, + self.argcAndIndex).define() + + # Variadic arguments get turned into a sequence. + (elementTemplate, elementDeclType, + elementHolderType, dealWithOptional) = typeConversion + if dealWithOptional: + raise TypeError("Shouldn't have optional things in variadics") + if elementHolderType is not None: + raise TypeError("Shouldn't need holders for variadics") + + replacer = dict(self.argcAndIndex, **self.replacementVariables) + replacer["seqType"] = CGWrapper(elementDeclType, pre="Sequence< ", post=" >").define() + replacer["elemType"] = elementDeclType.define() + + # NOTE: Keep this in sync with sequence conversions as needed + variadicConversion = string.Template("""const ${seqType} ${declName}; +if (${argc} > ${index}) { + ${seqType}& arr = const_cast< ${seqType}& >(${declName}); + if (!arr.SetCapacity(${argc} - ${index})) { + JS_ReportOutOfMemory(cx); + return false; + } + for (uint32_t variadicArg = ${index}; variadicArg < ${argc}; ++variadicArg) { + ${elemType}& slot = *arr.AppendElement(); +""").substitute(replacer) + + val = string.Template("${argv}[variadicArg]").substitute(replacer) + variadicConversion += CGIndenter(CGGeneric( + string.Template(elementTemplate).substitute( + { + "val" : val, + "valPtr": "&" + val, + "declName" : "slot", + # We only need holderName here to handle isExternal() + # interfaces, which use an internal holder for the + # conversion even when forceOwningType ends up true. + "holderName": "tempHolder", + # Use the same ${obj} as for the variadic arg itself + "obj": replacer["obj"] + } + )), 4).define() + + variadicConversion += ("\n" + " }\n" + "}") + return variadicConversion def getWrapTemplateForType(type, descriptorProvider, result, successCode, isCreator, exceptionCode): @@ -3777,16 +3826,44 @@ class CGMethodCall(CGThing): distinguishingIndex = method.distinguishingIndexForArgCount(argCount) - for (_, args) in possibleSignatures: + def distinguishingArgument(signature): + args = signature[1] + if distinguishingIndex < len(args): + return args[distinguishingIndex] + assert args[-1].variadic + return args[-1] + + def distinguishingType(signature): + return distinguishingArgument(signature).type + + for sig in possibleSignatures: # We should not have "any" args at distinguishingIndex, # since we have multiple possible signatures remaining, # but "any" is never distinguishable from anything else. - assert not args[distinguishingIndex].type.isAny() + assert not distinguishingType(sig).isAny() # We can't handle unions at the distinguishing index. - if args[distinguishingIndex].type.isUnion(): + if distinguishingType(sig).isUnion(): raise TypeError("No support for unions as distinguishing " "arguments yet: %s", - args[distinguishingIndex].location) + distinguishingArgument(sig).location) + # We don't support variadics as the distinguishingArgument yet. + # If you want to add support, consider this case: + # + # void(long... foo); + # void(long bar, Int32Array baz); + # + # in which we have to convert argument 0 to long before picking + # an overload... but all the variadic stuff needs to go into a + # single array in case we pick that overload, so we have to have + # machinery for converting argument 0 to long and then either + # placing it in the variadic bit or not. Or something. We may + # be able to loosen this restriction if the variadic arg is in + # fact at distinguishingIndex, perhaps. Would need to + # double-check. + if distinguishingArgument(sig).variadic: + raise TypeError("No support for variadics as distinguishing " + "arguments yet: %s", + distinguishingArgument(sig).location) # Convert all our arguments up to the distinguishing index. # Doesn't matter which of the possible signatures we use, since @@ -3816,9 +3893,6 @@ class CGMethodCall(CGThing): return True return False - def distinguishingType(signature): - return signature[1][distinguishingIndex].type - def tryCall(signature, indent, isDefinitelyObject=False, isNullOrUndefined=False): assert not isDefinitelyObject or not isNullOrUndefined @@ -6818,7 +6892,7 @@ class CGBindingRoot(CGThing): class CGNativeMember(ClassMethod): def __init__(self, descriptor, member, name, signature, extendedAttrs, breakAfter=True, passCxAsNeeded=True, visibility="public", - jsObjectsArePtr=False): + jsObjectsArePtr=False, variadicIsSequence=False): """ If jsObjectsArePtr is true, typed arrays and "object" will be passed as JSObject* @@ -6830,6 +6904,7 @@ class CGNativeMember(ClassMethod): self.extendedAttrs) self.passCxAsNeeded = passCxAsNeeded self.jsObjectsArePtr = jsObjectsArePtr + self.variadicIsSequence = variadicIsSequence breakAfterSelf = "\n" if breakAfter else "" ClassMethod.__init__(self, name, self.getReturnType(signature[0], False), @@ -7110,7 +7185,8 @@ class CGNativeMember(ClassMethod): decl = CGWrapper(decl, pre="Nullable< ", post=" >") ref = True if variadic: - decl = CGWrapper(decl, pre="nsTArray< ", post=" >") + arrayType = "Sequence" if self.variadicIsSequence else "nsTArray" + decl = CGWrapper(decl, pre="%s< " % arrayType, post=" >") ref = True elif optional: # Note: All variadic args claim to be optional, but we can just use @@ -7140,7 +7216,8 @@ class CGExampleMethod(CGNativeMember): method), signature, descriptor.getExtendedAttributes(method), - breakAfter) + breakAfter=breakAfter, + variadicIsSequence=True) def define(self, cgClass): return '' diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 469290b4610c..3f6ad656fcb3 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -2607,24 +2607,32 @@ class IDLMethod(IDLInterfaceMember, IDLScope): return [overload for overload in self._overloads if len(overload.arguments) == argc or (len(overload.arguments) > argc and - overload.arguments[argc].optional)] + overload.arguments[argc].optional) or + (len(overload.arguments) < argc and + len(overload.arguments) > 0 and + overload.arguments[-1].variadic)] def signaturesForArgCount(self, argc): return [(overload.returnType, overload.arguments) for overload in self.overloadsForArgCount(argc)] def locationsForArgCount(self, argc): - return [overload.location for overload in self._overloads if - len(overload.arguments) == argc or - (len(overload.arguments) > argc and - overload.arguments[argc].optional)] + return [overload.location for overload in self.overloadsForArgCount(argc)] def distinguishingIndexForArgCount(self, argc): def isValidDistinguishingIndex(idx, signatures): for (firstSigIndex, (firstRetval, firstArgs)) in enumerate(signatures[:-1]): for (secondRetval, secondArgs) in signatures[firstSigIndex+1:]: - firstType = firstArgs[idx].type - secondType = secondArgs[idx].type + if idx < len(firstArgs): + firstType = firstArgs[idx].type + else: + assert(firstArgs[-1].variadic) + firstType = firstArgs[-1].type + if idx < len(secondArgs): + secondType = secondArgs[idx].type + else: + assert(secondArgs[-1].variadic) + secondType = secondArgs[-1].type if not firstType.isDistinguishableFrom(secondType): return False return True diff --git a/dom/bindings/parser/tests/test_overload.py b/dom/bindings/parser/tests/test_overload.py index 59d9be54e534..e9a03958b6f5 100644 --- a/dom/bindings/parser/tests/test_overload.py +++ b/dom/bindings/parser/tests/test_overload.py @@ -8,6 +8,9 @@ def WebIDLTest(parser, harness): boolean abitharder(TestOverloads foo); boolean abitharder(boolean foo); void abitharder(ArrayBuffer? foo); + void withVariadics(long... numbers); + void withVariadics(TestOverloads iface); + void withVariadics(long num, TestOverloads iface); }; """) @@ -20,7 +23,7 @@ def WebIDLTest(parser, harness): "Should be an IDLInterface") harness.check(iface.identifier.QName(), "::TestOverloads", "Interface has the right QName") harness.check(iface.identifier.name, "TestOverloads", "Interface has the right name") - harness.check(len(iface.members), 2, "Expect %s members" % 2) + harness.check(len(iface.members), 3, "Expect %s members" % 3) member = iface.members[0] harness.check(member.identifier.QName(), "::TestOverloads::basic", "Method has the right QName") diff --git a/dom/bindings/parser/tests/test_variadic_constraints.py b/dom/bindings/parser/tests/test_variadic_constraints.py index 9cba22c58425..e9d060892e63 100644 --- a/dom/bindings/parser/tests/test_variadic_constraints.py +++ b/dom/bindings/parser/tests/test_variadic_constraints.py @@ -37,3 +37,16 @@ def WebIDLTest(parser, harness): threw = True harness.ok(threw, "Should have thrown.") + + threw = False + try: + results = parser.parse(""" + interface VariadicConstraints4 { + void foo(byte... arg1 = 0); + }; + """) + + except: + threw = True + + harness.ok(threw, "Should have thrown on variadic argument with default value.") diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 0a68810477de..d2c872d0ad72 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -154,6 +154,7 @@ public: void PassOptionalByteWithDefault(int8_t); void PassNullableByte(const Nullable&); void PassOptionalNullableByte(const Optional< Nullable >&); + void PassVariadicByte(const Sequence&); int16_t ReadonlyShort(); int16_t WritableShort(); @@ -381,6 +382,7 @@ public: void PassOptionalStringWithDefaultValue(const nsAString&); void PassOptionalNullableString(const Optional&); void PassOptionalNullableStringWithDefaultValue(const nsAString&); + void PassVariadicString(const Sequence&); // Enumerated types void PassEnum(TestEnum); @@ -473,6 +475,14 @@ public: static bool StaticAttribute(nsISupports*); static void SetStaticAttribute(nsISupports*, bool); + // Overload resolution tests + bool Overload1(TestInterface&); + TestInterface* Overload1(const nsAString&, TestInterface&); + + // Variadic handling + void PassVariadicThirdArg(const nsAString&, int32_t, + const Sequence >&); + // Miscellania int32_t AttrWithLenientThis(); void SetAttrWithLenientThis(int32_t); @@ -522,6 +532,7 @@ private: void PassOptionalByte(const Optional&) MOZ_DELETE; template void PassOptionalByteWithDefault(T) MOZ_DELETE; + void PassVariadicByte(Sequence&) MOZ_DELETE; void SetReadonlyShort(int16_t) MOZ_DELETE; template @@ -631,7 +642,7 @@ private: void PassOptionalStringWithDefaultValue(nsAString&) MOZ_DELETE; void PassOptionalNullableString(Optional&) MOZ_DELETE; void PassOptionalNullableStringWithDefaultValue(nsAString&) MOZ_DELETE; - + void PassVariadicString(Sequence&) MOZ_DELETE; }; class TestIndexedGetterInterface : public nsISupports, diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index 8130791f84e2..d2e394fd4cdf 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -100,6 +100,7 @@ interface TestInterface { void passOptionalByteWithDefault(optional byte arg = 0); void passNullableByte(byte? arg); void passOptionalNullableByte(optional byte? arg); + void passVariadicByte(byte... arg); readonly attribute short readonlyShort; attribute short writableShort; @@ -333,6 +334,7 @@ interface TestInterface { void passOptionalStringWithDefaultValue(optional DOMString arg = "abc"); void passOptionalNullableString(optional DOMString? arg); void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null); + void passVariadicString(DOMString... arg); // Enumerated types void passEnum(TestEnum arg); @@ -424,6 +426,14 @@ interface TestInterface { static attribute boolean staticAttribute; static void staticMethod(boolean arg); + // Overload resolution tests + //void overload1(DOMString... strs); + boolean overload1(TestInterface arg); + TestInterface overload1(DOMString strs, TestInterface arg); + + // Variadic handling + void passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3); + // Miscellania [LenientThis] attribute long attrWithLenientThis; [Unforgeable] readonly attribute long unforgeableAttr; diff --git a/dom/bindings/test/TestExampleGen.webidl b/dom/bindings/test/TestExampleGen.webidl index 7edab7a28cd0..78673d58d054 100644 --- a/dom/bindings/test/TestExampleGen.webidl +++ b/dom/bindings/test/TestExampleGen.webidl @@ -21,6 +21,7 @@ interface TestExampleInterface { void passOptionalByteWithDefault(optional byte arg = 0); void passNullableByte(byte? arg); void passOptionalNullableByte(optional byte? arg); + void passVariadicByte(byte... arg); readonly attribute short readonlyShort; attribute short writableShort; @@ -254,6 +255,7 @@ interface TestExampleInterface { void passOptionalStringWithDefaultValue(optional DOMString arg = "abc"); void passOptionalNullableString(optional DOMString? arg); void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null); + void passVariadicString(DOMString... arg); // Enumerated types void passEnum(TestEnum arg); @@ -345,6 +347,14 @@ interface TestExampleInterface { static attribute boolean staticAttribute; static void staticMethod(boolean arg); + // Overload resolution tests + //void overload1(DOMString... strs); + boolean overload1(TestInterface arg); + TestInterface overload1(DOMString strs, TestInterface arg); + + // Variadic handling + void passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3); + // Miscellania [LenientThis] attribute long attrWithLenientThis; [Unforgeable] readonly attribute long unforgeableAttr; diff --git a/dom/bluetooth/linux/BluetoothDBusService.cpp b/dom/bluetooth/linux/BluetoothDBusService.cpp index 03166dbea66e..170cecf80a36 100644 --- a/dom/bluetooth/linux/BluetoothDBusService.cpp +++ b/dom/bluetooth/linux/BluetoothDBusService.cpp @@ -151,6 +151,7 @@ static const char* sBluetoothDBusSignals[] = static nsAutoPtr gThreadConnection; static nsDataHashtable sPairingReqTable; static nsDataHashtable sAuthorizeReqTable; +static bool sIsPairing = false; typedef void (*UnpackFunc)(DBusMessage*, DBusError*, BluetoothValue&, nsAString&); class DistributeBluetoothSignalTask : public nsRunnable { @@ -819,8 +820,12 @@ RunDBusCallback(DBusMessage* aMsg, void* aBluetoothReplyRunnable, void GetObjectPathCallback(DBusMessage* aMsg, void* aBluetoothReplyRunnable) { - RunDBusCallback(aMsg, aBluetoothReplyRunnable, - UnpackObjectPathMessage); + if (sIsPairing) { + RunDBusCallback(aMsg, aBluetoothReplyRunnable, + UnpackObjectPathMessage); + + sIsPairing = false; + } } void @@ -1566,6 +1571,8 @@ BluetoothDBusService::StopInternal() sAuthorizeReqTable.EnumerateRead(UnrefDBusMessages, nullptr); sAuthorizeReqTable.Clear(); + sIsPairing = false; + StopDBus(); return NS_OK; } @@ -2087,6 +2094,21 @@ BluetoothDBusService::CreatePairedDeviceInternal(const nsAString& aAdapterPath, return NS_ERROR_FAILURE; } + + /** + * FIXME: Bug 820274 + * + * If the user turns off Bluetooth in the middle of pairing process, the + * callback function GetObjectPathCallback (see the third argument of the + * function call above) may still be called while enabling next time by + * dbus daemon. To prevent this from happening, added a flag to distinguish + * if Bluetooth has been turned off. Nevertheless, we need a check if there + * is a better solution. + * + * Please see Bug 818696 for more information. + */ + sIsPairing = true; + runnable.forget(); return NS_OK; } diff --git a/dom/camera/GonkCameraControl.cpp b/dom/camera/GonkCameraControl.cpp index fd244cfbaec2..588409bb1340 100644 --- a/dom/camera/GonkCameraControl.cpp +++ b/dom/camera/GonkCameraControl.cpp @@ -29,6 +29,7 @@ #include "nsThread.h" #include #include "mozilla/FileUtils.h" +#include "mozilla/Services.h" #include "nsAlgorithm.h" #include #include "nsPrintfCString.h" diff --git a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json b/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json index e09cfc5bb52e..fcaaa1ecaa09 100644 --- a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json +++ b/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json @@ -2,14 +2,5 @@ "XMLHttpRequest interface constructor": true, "XMLHttpRequest interface: operation open(DOMString,DOMString,boolean,DOMString,DOMString)": true, "XMLHttpRequest interface: operation send(union)": true, - "FormData interface: existence and properties of interface object": true, - "FormData interface constructor": true, - "FormData interface: existence and properties of interface prototype object": true, - "FormData interface: existence and properties of interface prototype object's \"constructor\" property": true, - "Stringification of new FormData()": "debug", - "FormData interface: calling append(DOMString,Blob,DOMString) on new FormData() with too few arguments must throw TypeError": true, - "FormData interface: calling append(DOMString,DOMString) on new FormData() with too few arguments must throw TypeError": true, - "Stringification of new FormData(form)": "debug", - "FormData interface: calling append(DOMString,Blob,DOMString) on new FormData(form) with too few arguments must throw TypeError": true, - "FormData interface: calling append(DOMString,DOMString) on new FormData(form) with too few arguments must throw TypeError": true + "FormData interface constructor": true } diff --git a/dom/locales/en-US/chrome/accessibility/AccessFu.properties b/dom/locales/en-US/chrome/accessibility/AccessFu.properties index 1f3eaffae4a9..ed7482fcd15c 100644 --- a/dom/locales/en-US/chrome/accessibility/AccessFu.properties +++ b/dom/locales/en-US/chrome/accessibility/AccessFu.properties @@ -113,4 +113,22 @@ stateTraversed = visited # App modes editingMode = editing -navigationMode = navigating \ No newline at end of file +navigationMode = navigating + +# Quick navigation modes +quicknav_Simple = Default +quicknav_Anchor = Anchors +quicknav_Button = Buttons +quicknav_Combobox = Combo boxes +quicknav_Entry = Entries +quicknav_FormElement = Form elements +quicknav_Graphic = Images +quicknav_Heading = Headings +quicknav_ListItem = List items +quicknav_Link = Links +quicknav_List = Lists +quicknav_PageTab = Page tabs +quicknav_RadioButton = Radio buttons +quicknav_Separator = Separators +quicknav_Table = Tables +quicknav_Checkbox = Check boxes \ No newline at end of file diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index 19826eaf56ce..8366ba54f48c 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -23,9 +23,11 @@ const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}"); // a page is torn down. (Maps inner window ID to an array of PC objects). function GlobalPCList() { this._list = []; + this._networkdown = false; // XXX Need to query current state somehow Services.obs.addObserver(this, "inner-window-destroyed", true); Services.obs.addObserver(this, "profile-change-net-teardown", true); Services.obs.addObserver(this, "network:offline-about-to-go-offline", true); + Services.obs.addObserver(this, "network:offline-status-changed", true); } GlobalPCList.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, @@ -89,6 +91,15 @@ GlobalPCList.prototype = { pc._pc = null; }); }; + this._networkdown = true; + } + else if (topic == "network:offline-status-changed") { + if (data == "offline") { + // this._list shold be empty here + this._networkdown = true; + } else if (data == "online") { + this._networkdown = false; + } } }, }; @@ -228,6 +239,9 @@ PeerConnection.prototype = { if (this._win) { throw new Error("Constructor already called"); } + if (_globalPCList._networkdown) { + throw new Error("Can't create RTPPeerConnections when the network is down"); + } this._pc = Cc["@mozilla.org/peerconnection;1"]. createInstance(Ci.IPeerConnection); diff --git a/dom/system/OSFileConstants.cpp b/dom/system/OSFileConstants.cpp index 63fc6b1864b2..e9cef3c3377f 100644 --- a/dom/system/OSFileConstants.cpp +++ b/dom/system/OSFileConstants.cpp @@ -28,6 +28,8 @@ // Used to provide information on the OS #include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" #include "nsDirectoryServiceUtils.h" #include "nsIXULRuntime.h" #include "nsXPCOMCIDInternal.h" @@ -91,6 +93,7 @@ nsresult GetPathToSpecialDir(const char *aKey, nsString& aOutPath) nsCOMPtr file; nsresult rv = NS_GetSpecialDirectory(aKey, getter_AddRefs(file)); if (NS_FAILED(rv) || !file) { + aOutPath.SetIsVoid(true); return rv; } @@ -101,6 +104,46 @@ nsresult GetPathToSpecialDir(const char *aKey, nsString& aOutPath) return rv; } +/** + * In some cases, OSFileConstants may be instantiated before the + * profile is setup. In such cases, |OS.Constants.Path.profileDir| and + * |OS.Constants.Path.localProfileDir| are undefined. However, we want + * to ensure that this does not break existing code, so that future + * workers spawned after the profile is setup have these constants. + * + * For this purpose, we register an observer to set |gPaths->profileDir| + * and |gPaths->localProfileDir| once the profile is setup. + */ +class DelayedPathSetter MOZ_FINAL: public nsIObserver +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + DelayedPathSetter() {} +}; + +NS_IMPL_ISUPPORTS1(DelayedPathSetter, nsIObserver) + +NS_IMETHODIMP +DelayedPathSetter::Observe(nsISupports*, const char * aTopic, const PRUnichar*) +{ + if (gPaths == nullptr) { + // Initialization of gPaths has not taken place, something is wrong, + // don't make things worse. + return NS_OK; + } + nsresult rv = GetPathToSpecialDir(NS_APP_USER_PROFILE_50_DIR, gPaths->profileDir); + if (NS_FAILED(rv)) { + return rv; + } + rv = GetPathToSpecialDir(NS_APP_USER_PROFILE_LOCAL_50_DIR, gPaths->localProfileDir); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + /** * Perform the part of initialization that can only be * executed on the main thread. @@ -134,12 +177,32 @@ nsresult InitOSFileConstants() return rv; } + // Setup profileDir and localProfileDir immediately if possible (we + // assume that NS_APP_USER_PROFILE_50_DIR and + // NS_APP_USER_PROFILE_LOCAL_50_DIR are set simultaneously) + rv = GetPathToSpecialDir(NS_APP_USER_PROFILE_50_DIR, paths->profileDir); + if (NS_SUCCEEDED(rv)) { + rv = GetPathToSpecialDir(NS_APP_USER_PROFILE_LOCAL_50_DIR, paths->localProfileDir); + } + + // Otherwise, delay setup of profileDir/localProfileDir until they + // become available. + if (NS_FAILED(rv)) { + nsCOMPtr obsService = do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + nsRefPtr pathSetter = new DelayedPathSetter(); + rv = obsService->AddObserver(pathSetter, "profile-do-change", false); + if (NS_FAILED(rv)) { + return rv; + } + } + // For other directories, ignore errors (they may be undefined on // some platforms or in non-Firefox embeddings of Gecko). GetPathToSpecialDir(NS_OS_TEMP_DIR, paths->tmpDir); - GetPathToSpecialDir(NS_APP_USER_PROFILE_50_DIR, paths->profileDir); - GetPathToSpecialDir(NS_APP_USER_PROFILE_LOCAL_50_DIR, paths->localProfileDir); gPaths = paths.forget(); return NS_OK; @@ -679,11 +742,15 @@ bool DefineOSFileConstants(JSContext *cx, JSObject *global) return false; } - if (!SetStringProperty(cx, objPath, "profileDir", gPaths->profileDir)) { + // Configure profileDir only if it is available at this stage + if (!gPaths->profileDir.IsVoid() + && !SetStringProperty(cx, objPath, "profileDir", gPaths->profileDir)) { return false; } - if (!SetStringProperty(cx, objPath, "localProfileDir", gPaths->localProfileDir)) { + // Configure localProfileDir only if it is available at this stage + if (!gPaths->localProfileDir.IsVoid() + && !SetStringProperty(cx, objPath, "localProfileDir", gPaths->localProfileDir)) { return false; } diff --git a/dom/system/gonk/SystemWorkerManager.cpp b/dom/system/gonk/SystemWorkerManager.cpp index 5b1b5fd10519..e0e3de2fb4bf 100644 --- a/dom/system/gonk/SystemWorkerManager.cpp +++ b/dom/system/gonk/SystemWorkerManager.cpp @@ -31,6 +31,7 @@ #include "TimeZoneSettingObserver.h" #endif #include "mozilla/ipc/Ril.h" +#include "nsIObserverService.h" #include "nsContentUtils.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js index d2a3a560a9c4..c401a9dd62ca 100644 --- a/dom/system/gonk/ril_consts.js +++ b/dom/system/gonk/ril_consts.js @@ -422,6 +422,7 @@ this.ICC_EF_CBMIR = 0x6f50; this.ICC_EF_AD = 0x6fad; this.ICC_EF_PHASE = 0x6fae; this.ICC_EF_PNN = 0x6fc5; +this.ICC_EF_OPL = 0x6fc6; this.ICC_EF_MBDN = 0x6fc7; this.ICC_EF_EXT6 = 0x6fc8; // Ext record for EF[MBDN] this.ICC_EF_MBI = 0x6fc9; @@ -581,6 +582,11 @@ this.COMPREHENSIONTLV_TAG_BROADCAST_NETWORK_INFO = 0x7a; this.SPDI_TAG_SPDI = 0xa3; this.SPDI_TAG_PLMN_LIST = 0x80; +// MM INFORMATION message content IEIs +// See 3GPP TS 24.008 table 9.2.18 +this.PNN_IEI_FULL_NETWORK_NAME = 0x43; +this.PNN_IEI_SHORT_NETWORK_NAME = 0x45; + // Device identifiers, see TS 11.14, clause 12.7 this.STK_DEVICE_ID_KEYPAD = 0x01; this.STK_DEVICE_ID_DISPLAY = 0x02; @@ -988,6 +994,8 @@ this.GECKO_ICC_SERVICES = { DATA_DOWNLOAD_SMS_PP: 26, CBMIR: 30, BDN: 31, + PNN: 51, + OPL: 52, SPDI: 56 }, usim: { @@ -998,6 +1006,8 @@ this.GECKO_ICC_SERVICES = { CBMIR: 16, SPN: 19, DATA_DOWNLOAD_SMS_PP: 28, + PNN: 45, + OPL: 46, SPDI: 51 } }; diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 18b9f57afbd2..7e7e323f8fff 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -1671,6 +1671,219 @@ let RIL = { (serviceTable[index] & bitmask)) != 0; }, + /** + * Choose network names using EF_OPL and EF_PNN + * See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM, + * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM. + */ + updateNetworkName: function updateNetworkName() { + let iccInfoPriv = this.iccInfoPrivate; + let iccInfo = this.iccInfo; + + // We won't update network name if voice registration isn't ready + // or PNN file haven't been retrieved. + if (!iccInfoPriv.PNN || + !this.voiceRegistrationState.cell || + this.voiceRegistrationState.cell.gsmLocationAreaCode == -1) { + return null; + } + + let pnnEntry; + let lac = this.voiceRegistrationState.cell.gsmLocationAreaCode; + let mcc = this.operator.mcc; + let mnc = this.operator.mnc; + + // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, + // the ME shall use this EF_OPL in association with the EF_PNN in place + // of any network name stored within the ME's internal list and any network + // name received when registered to the PLMN. + if (iccInfoPriv.OPL) { + for (let i in iccInfoPriv.OPL) { + let opl = iccInfoPriv.OPL[i]; + // Try to match the MCC/MNC. + if (mcc != opl.mcc || mnc != opl.mnc) { + continue; + } + // Try to match the location area code. If current local area code is + // covered by lac range that specified in the OPL entry, use the PNN + // that specified in the OPL entry. + if ((opl.lacTacStart == 0x0 && opl.lacTacEnd == 0xFFFE) || + (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) { + if (opl.pnnRecordId == 0) { + // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, + // A value of '00' indicates that the name is to be taken from other + // sources. + return null; + } + pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1] + break; + } + } + } + + // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41, + // the first record in this EF is used for the default network name when + // registered to the HPLMN. + // If we haven't get pnnEntry assigned, we should try to assign default + // value to it. + if (!pnnEntry && mcc == iccInfo.mcc && mnc == iccInfo.mnc) { + pnnEntry = iccInfoPriv.PNN[0] + } + + if (DEBUG) { + if (pnnEntry) { + debug("updateNetworkName: Network names will be overriden: longName = " + + pnnEntry.fullName + ", shortName = " + pnnEntry.shortName); + } else { + debug("updateNetworkName: Network names will not be overriden"); + } + } + + if (pnnEntry) { + return [pnnEntry.fullName, pnnEntry.shortName]; + } + return null; + }, + + /** + * Read OPL (Operator PLMN List) from USIM. + * + * See 3GPP TS 31.102 Sec. 4.2.59 for USIM + * 3GPP TS 51.011 Sec. 10.3.42 for SIM. + */ + getOPL: function getOPL() { + let opl = []; + function callback(options) { + let len = Buf.readUint32(); + // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined + // in 3GPP TS 23.003, Sec 4.1 + // +-------------+---------+ + // | Octet 1 - 3 | MCC/MNC | + // +-------------+---------+ + // | Octet 4 - 7 | LAC | + // +-------------+---------+ + let mccMnc = [GsmPDUHelper.readHexOctet(), + GsmPDUHelper.readHexOctet(), + GsmPDUHelper.readHexOctet()]; + if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) { + let oplElement = {}; + let semiOctets = []; + for (let i = 0; i < mccMnc.length; i++) { + semiOctets.push((mccMnc[i] & 0xf0) >> 4); + semiOctets.push(mccMnc[i] & 0x0f); + } + let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], + semiOctets[5], semiOctets[4], semiOctets[2]]; + let buf = ""; + for (let i = 0; i < reformat.length; i++) { + if (reformat[i] != 0xF) { + buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); + } + if (i === 2) { + // 0-2: MCC + oplElement.mcc = parseInt(buf); + buf = ""; + } else if (i === 5) { + // 3-5: MNC + oplElement.mnc = parseInt(buf); + } + } + // LAC/TAC + oplElement.lacTacStart = + (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); + oplElement.lacTacEnd = + (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); + // PLMN Network Name Record Identifier + oplElement.pnnRecordId = GsmPDUHelper.readHexOctet(); + if (DEBUG) { + debug("OPL: [" + (opl.length + 1) + "]: " + JSON.stringify(oplElement)); + } + opl.push(oplElement); + } + Buf.readStringDelimiter(len); + if (options.p1 < options.totalRecords) { + options.p1++; + this.iccIO(options); + } else { + this.iccInfoPrivate.OPL = opl; + } + } + + this.iccIO({ + command: ICC_COMMAND_GET_RESPONSE, + fileId: ICC_EF_OPL, + pathId: this._getPathIdForICCRecord(ICC_EF_OPL), + p1: 0, // For GET_RESPONSE, p1 = 0 + p2: 0, // For GET_RESPONSE, p2 = 0 + p3: GET_RESPONSE_EF_SIZE_BYTES, + data: null, + pin2: null, + type: EF_TYPE_LINEAR_FIXED, + callback: callback, + }); + }, + + /** + * Read PNN (PLMN Network Name) from USIM. + * + * See 3GPP TS 31.102 Sec. 4.2.58 for USIM + * 3GPP TS 51.011 Sec. 10.3.41 for SIM. + */ + getPNN: function getPNN() { + let pnn = []; + function callback(options) { + let pnnElement = this.iccInfoPrivate.PNN = {}; + let len = Buf.readUint32(); + let readLen = 0; + while (len > readLen) { + let tlvTag = GsmPDUHelper.readHexOctet(); + readLen = readLen + 2; // 1 Hex octet + if (tlvTag == 0xFF) { + // Unused byte + continue; + } + let tlvLen = GsmPDUHelper.readHexOctet(); + let name; + switch (tlvTag) { + case PNN_IEI_FULL_NETWORK_NAME: + pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen); + break; + case PNN_IEI_SHORT_NETWORK_NAME: + pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen); + break; + default: + Buf.seekIncoming(PDU_HEX_OCTET_SIZE * tlvLen); + } + readLen += (tlvLen * 2 + 2); + } + if (DEBUG) { + debug("PNN: [" + (pnn.length + 1) + "]: " + JSON.stringify(pnnElement)); + } + Buf.readStringDelimiter(len); + pnn.push(pnnElement); + + if (options.p1 < options.totalRecords) { + options.p1++; + this.iccIO(options); + } else { + this.iccInfoPrivate.PNN = pnn; + } + } + + this.iccIO({ + command: ICC_COMMAND_GET_RESPONSE, + fileId: ICC_EF_PNN, + pathId: this._getPathIdForICCRecord(ICC_EF_PNN), + p1: 0, // For GET_RESPONSE, p1 = 0 + p2: 0, // For GET_RESPONSE, p2 = 0 + p3: GET_RESPONSE_EF_SIZE_BYTES, + data: null, + pin2: null, + type: EF_TYPE_LINEAR_FIXED, + callback: callback, + }); + }, + /** * Read the (U)SIM Service Table from the ICC. */ @@ -1705,6 +1918,20 @@ let RIL = { if (DEBUG) debug("SPDI: SPDI service is not available"); } + if (this.isICCServiceAvailable("PNN")) { + if (DEBUG) debug("PNN: PNN is available"); + this.getPNN(); + } else { + if (DEBUG) debug("PNN: PNN is not available"); + } + + if (this.isICCServiceAvailable("OPL")) { + if (DEBUG) debug("OPL: OPL is available"); + this.getOPL(); + } else { + if (DEBUG) debug("OPL: OPL is not available"); + } + if (this.isICCServiceAvailable("CBMI")) { this.getCBMI(); } else { @@ -2543,7 +2770,8 @@ let RIL = { * String containing the processId for the SmsRequestManager. */ sendSMS: function sendSMS(options) { - //TODO: verify values on 'options' + options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT; + options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT; if (!options.retryCount) { options.retryCount = 0; @@ -3643,6 +3871,8 @@ let RIL = { case ICC_EF_SPDI: case ICC_EF_CBMI: case ICC_EF_CBMIR: + case ICC_EF_OPL: + case ICC_EF_PNN: return EF_PATH_MF_SIM + EF_PATH_ADF_USIM; default: @@ -3968,8 +4198,15 @@ let RIL = { this.operator.shortName !== shortName || thisTuple !== networkTuple) { - this.operator.longName = longName; - this.operator.shortName = shortName; + let networkName = this.updateNetworkName(); + if (networkName) { + this.operator.longName = networkName[0]; + this.operator.shortName = networkName[1]; + } else { + this.operator.longName = longName; + this.operator.shortName = shortName; + } + this.operator.mcc = 0; this.operator.mnc = 0; @@ -5970,7 +6207,7 @@ RIL[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() debug("DateTimeZone string " + dateString); let now = Date.now(); - + let year = parseInt(dateString.substr(0, 2), 10); let month = parseInt(dateString.substr(3, 2), 10); let day = parseInt(dateString.substr(6, 2), 10); @@ -6821,16 +7058,18 @@ let GsmPDUHelper = { this.writeHexOctet(options.segmentSeq & 0xFF); } - if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { - this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); - this.writeHexOctet(1); - this.writeHexOctet(options.langIndex); - } + if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { + if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { + this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); + this.writeHexOctet(1); + this.writeHexOctet(options.langIndex); + } - if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { - this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT); - this.writeHexOctet(1); - this.writeHexOctet(options.langShiftIndex); + if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { + this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT); + this.writeHexOctet(1); + this.writeHexOctet(options.langShiftIndex); + } } }, @@ -7848,6 +8087,60 @@ let GsmPDUHelper = { return msg; }, + + /** + * Read network name. + * + * @param len Length of the information element. + * @return + * { + * networkName: network name. + * shouldIncludeCi: Should Country's initials included in text string. + * } + * @see TS 24.008 clause 10.5.3.5a. + */ + readNetworkName: function readNetworkName(len) { + // According to TS 24.008 Sec. 10.5.3.5a, the first octet is: + // bit 8: must be 1. + // bit 5-7: Text encoding. + // 000 - GSM default alphabet. + // 001 - UCS2 (16 bit). + // else - reserved. + // bit 4: MS should add the letters for Country's Initials and a space + // to the text string if this bit is true. + // bit 1-3: number of spare bits in last octet. + + let codingInfo = GsmPDUHelper.readHexOctet(); + if (!(codingInfo & 0x80)) { + return null; + } + + let textEncoding = (codingInfo & 0x70) >> 4, + shouldIncludeCountryInitials = !!(codingInfo & 0x08), + spareBits = codingInfo & 0x07; + let resultString; + + switch (textEncoding) { + case 0: + // GSM Default alphabet. + resultString = GsmPDUHelper.readSeptetsToString( + ((len - 1) * 8 - spareBits) / 7, 0, + PDU_NL_IDENTIFIER_DEFAULT, + PDU_NL_IDENTIFIER_DEFAULT); + break; + case 1: + // UCS2 encoded. + resultString = this.readUCS2String(len - 1); + break; + default: + // Not an available text coding. + return null; + } + + // TODO - Bug 820286: According to shouldIncludeCountryInitials, add + // country initials to the resulting string. + return resultString; + } }; let StkCommandParamsFactory = { diff --git a/dom/system/gonk/tests/test_ril_worker_icc.js b/dom/system/gonk/tests/test_ril_worker_icc.js index edd8eb19ba27..5823e8c99278 100644 --- a/dom/system/gonk/tests/test_ril_worker_icc.js +++ b/dom/system/gonk/tests/test_ril_worker_icc.js @@ -623,6 +623,7 @@ add_test(function test_spn_display_condition() { [0, 123, 456, 123, 457, false, true], ], run_next_test); }); + /** * Verify Proactive Command : More Time */ @@ -652,6 +653,143 @@ add_test(function test_stk_proactive_command_more_time() { run_next_test(); }); +add_test(function read_network_name() { + let worker = newUint8Worker(); + let helper = worker.GsmPDUHelper; + let buf = worker.Buf; + + // Returning length of byte. + function writeNetworkName(isUCS2, requireCi, name) { + let codingOctet = 0x80; + let len; + if (requireCi) { + codingOctet |= 0x08; + } + + if (isUCS2) { + codingOctet |= 0x10; + len = name.length * 2; + } else { + let spare = (8 - (name.length * 7) % 8) % 8; + codingOctet |= spare; + len = Math.ceil(name.length * 7 / 8); + } + helper.writeHexOctet(codingOctet); + + if (isUCS2) { + helper.writeUCS2String(name); + } else { + helper.writeStringAsSeptets(name, 0, 0, 0); + } + + return len + 1; // codingOctet. + } + + function testNetworkName(isUCS2, requireCi, name) { + let len = writeNetworkName(isUCS2, requireCi, name); + do_check_eq(helper.readNetworkName(len), name); + } + + testNetworkName( true, true, "Test Network Name1"); + testNetworkName( true, false, "Test Network Name2"); + testNetworkName(false, true, "Test Network Name3"); + testNetworkName(false, false, "Test Network Name4"); + + run_next_test(); +}); + +add_test(function test_update_network_name() { + let RIL = newWorker({ + postRILMessage: function fakePostRILMessage(data) { + // Do nothing + }, + postMessage: function fakePostMessage(message) { + // Do nothing + } + }).RIL; + + function testNetworkNameIsNull(operatorMcc, operatorMnc) { + RIL.operator.mcc = operatorMcc; + RIL.operator.mnc = operatorMnc; + do_check_eq(RIL.updateNetworkName(), null); + } + + function testNetworkName(operatorMcc, operatorMnc, + expectedLongName, expectedShortName) { + RIL.operator.mcc = operatorMcc; + RIL.operator.mnc = operatorMnc; + let result = RIL.updateNetworkName(); + + do_check_eq(result[0], expectedLongName); + do_check_eq(result[1], expectedShortName); + } + + // Before EF_OPL and EF_PNN have been loaded. + do_check_eq(RIL.updateNetworkName(), null); + + // Set HPLMN + RIL.iccInfo.mcc = 123; + RIL.iccInfo.mnc = 456; + + RIL.voiceRegistrationState = { + cell: { + gsmLocationAreaCode: 0x1000 + } + }; + RIL.operator = {}; + + // Set EF_PNN + RIL.iccInfoPrivate = { + PNN: [ + {"fullName": "PNN1Long", "shortName": "PNN1Short"}, + {"fullName": "PNN2Long", "shortName": "PNN2Short"}, + {"fullName": "PNN3Long", "shortName": "PNN3Short"}, + {"fullName": "PNN4Long", "shortName": "PNN4Short"} + ] + }; + + // EF_OPL isn't available and current isn't in HPLMN, + testNetworkNameIsNull(123, 457); + + // EF_OPL isn't available and current is in HPLMN, + // the first record of PNN should be returned. + testNetworkName(123, 456, "PNN1Long", "PNN1Short"); + + // Set EF_OPL + RIL.iccInfoPrivate.OPL = [ + { + "mcc": 123, + "mnc": 456, + "lacTacStart": 0, + "lacTacEnd": 0xFFFE, + "pnnRecordId": 4 + }, + { + "mcc": 123, + "mnc": 457, + "lacTacStart": 0, + "lacTacEnd": 0x0010, + "pnnRecordId": 3 + }, + { + "mcc": 123, + "mnc": 457, + "lacTacStart": 0, + "lacTacEnd": 0x1010, + "pnnRecordId": 2 + } + ]; + + // Both EF_PNN and EF_OPL are presented, and current PLMN is HPLMN, + testNetworkName(123, 456, "PNN4Long", "PNN4Short"); + + // Current PLMN is not HPLMN, and according to LAC, we should get + // the second PNN record. + testNetworkName(123, 457, "PNN2Long", "PNN2Short"); + + run_next_test(); +}); + /** * Verify Proactive Command : Provide Local Information */ diff --git a/dom/system/gonk/tests/test_ril_worker_sms.js b/dom/system/gonk/tests/test_ril_worker_sms.js index 50a9b9642da5..2c4ea2122952 100644 --- a/dom/system/gonk/tests/test_ril_worker_sms.js +++ b/dom/system/gonk/tests/test_ril_worker_sms.js @@ -714,6 +714,52 @@ for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) { } test_receiving_ucs2_alphabets(ucs2str); +// Bug 820220: B2G SMS: wrong order and truncated content in multi-part messages +add_test(function test_sendSMS_UCS2_without_langIndex_langShiftIndex_defined() { + let worker = newWriteHexOctetAsUint8Worker(); + + worker.Buf.sendParcel = function () { + // Each sendParcel() call represents one outgoing segment of a multipart + // SMS message. Here, we have the first segment send, so it's "Hello " + // only. + // + // 4(parcel size) + 4(request type) + 4(token) + // + 4(two messages) + 4(null SMSC) + 4(message string length) + // + 1(first octet) + 1(message reference) + // + 2(DA len, TOA) + 4(addr) + // + 1(pid) + 1(dcs) + // + 1(UDL) + 6(UDHL, type, len, ref, max, seq) + // + 12(2 * strlen("Hello ")) + // + 4(two delimitors) = 57 + // + // If we have additional 6(type, len, langIndex, type len, langShiftIndex) + // octets here, then bug 809553 is not fixed. + do_check_eq(this.outgoingIndex, 57); + + run_next_test(); + }; + + worker.RIL.sendSMS({ + number: "1", + segmentMaxSeq: 2, + fullBody: "Hello World!", + dcs: PDU_DCS_MSG_CODING_16BITS_ALPHABET, + segmentRef16Bit: false, + userDataHeaderLength: 5, + strict7BitEncoding: false, + requestStatusReport: true, + segments: [ + { + body: "Hello ", + encodedBodyLength: 12, + }, { + body: "World!", + encodedBodyLength: 12, + } + ], + }); +}); + /** * Verify GsmPDUHelper#readAddress */ diff --git a/dom/time/TimeChangeObserver.cpp b/dom/time/TimeChangeObserver.cpp index 453dc86854e6..dd0ed48bfe0b 100644 --- a/dom/time/TimeChangeObserver.cpp +++ b/dom/time/TimeChangeObserver.cpp @@ -5,6 +5,7 @@ #include "TimeChangeObserver.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "nsPIDOMWindow.h" #include "nsDOMEvent.h" diff --git a/dom/webidl/File.webidl b/dom/webidl/File.webidl new file mode 100644 index 000000000000..f4d085e29fe5 --- /dev/null +++ b/dom/webidl/File.webidl @@ -0,0 +1,9 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +dictionary FilePropertyBag : BlobPropertyBag { + DOMString name = ""; +}; diff --git a/dom/webidl/FormData.webidl b/dom/webidl/FormData.webidl new file mode 100644 index 000000000000..505f5c56e861 --- /dev/null +++ b/dom/webidl/FormData.webidl @@ -0,0 +1,18 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * The origin of this IDL file is + * http://xhr.spec.whatwg.org + */ + +interface HTMLFormElement; + +[Constructor(optional HTMLFormElement form)] +interface FormData { + // Not supported (bug 739174) + // void append(DOMString name, Blob value, optional DOMString filename); + void append(DOMString name, Blob value); + void append(DOMString name, DOMString value); +}; diff --git a/dom/webidl/WebIDL.mk b/dom/webidl/WebIDL.mk index a4fd17761d98..59fa38025e0f 100644 --- a/dom/webidl/WebIDL.mk +++ b/dom/webidl/WebIDL.mk @@ -33,9 +33,11 @@ webidl_files = \ EventHandler.webidl \ EventListener.webidl \ EventTarget.webidl \ + File.webidl \ FileHandle.webidl \ FileList.webidl \ FileReaderSync.webidl \ + FormData.webidl \ Function.webidl \ GainNode.webidl \ HTMLCollection.webidl \ diff --git a/dom/webidl/XMLHttpRequest.webidl b/dom/webidl/XMLHttpRequest.webidl index 1e584f314a19..b91eb3ee6cc6 100644 --- a/dom/webidl/XMLHttpRequest.webidl +++ b/dom/webidl/XMLHttpRequest.webidl @@ -12,7 +12,6 @@ interface Document; interface Blob; -interface FormData; interface InputStream; interface MozChannel; interface IID; diff --git a/dom/workers/File.cpp b/dom/workers/File.cpp index 674fc9d92365..047c0ca61cdf 100644 --- a/dom/workers/File.cpp +++ b/dom/workers/File.cpp @@ -88,8 +88,7 @@ private: Construct(JSContext* aCx, unsigned aArgc, jsval* aVp) { nsRefPtr file = new nsDOMMultipartFile(); - nsresult rv = file->InitInternal(aCx, aArgc, JS_ARGV(aCx, aVp), - Unwrap); + nsresult rv = file->InitBlob(aCx, aArgc, JS_ARGV(aCx, aVp), Unwrap); if (NS_FAILED(rv)) { ThrowDOMExceptionForNSResult(aCx, rv); return false; diff --git a/dom/workers/XMLHttpRequest.h b/dom/workers/XMLHttpRequest.h index 6db72400f6ae..9bcb44434c5a 100644 --- a/dom/workers/XMLHttpRequest.h +++ b/dom/workers/XMLHttpRequest.h @@ -178,6 +178,12 @@ public: void Send(JSObject* aBody, ErrorResult& aRv); + void + Send(JSObject& aBody, ErrorResult& aRv) + { + Send(&aBody, aRv); + } + void Send(ArrayBuffer& aBody, ErrorResult& aRv) { return Send(aBody.Obj(), aRv); diff --git a/gfx/angle/Makefile.in b/gfx/angle/Makefile.in index cf5965b0c79a..78333c37f353 100644 --- a/gfx/angle/Makefile.in +++ b/gfx/angle/Makefile.in @@ -76,6 +76,7 @@ CPPSRCS = \ Lexer.cpp \ Preprocessor.cpp \ Token.cpp \ + VariablePacker.cpp \ $(NULL) # flex/yacc generated files diff --git a/gfx/angle/angle-1317.patch b/gfx/angle/angle-1317.patch new file mode 100644 index 000000000000..ae01eb734e9f --- /dev/null +++ b/gfx/angle/angle-1317.patch @@ -0,0 +1,732 @@ +# HG changeset patch +# Parent ef5d80327785b28df0bd778acc86f4987ba5a678 + +diff --git a/gfx/angle/Makefile.in b/gfx/angle/Makefile.in +--- a/gfx/angle/Makefile.in ++++ b/gfx/angle/Makefile.in +@@ -71,16 +71,17 @@ CPPSRCS = \ + ForLoopUnroll.cpp \ + MapLongVariableNames.cpp \ + spooky.cpp \ + BuiltInFunctionEmulator.cpp \ + Input.cpp \ + Lexer.cpp \ + Preprocessor.cpp \ + Token.cpp \ ++ VariablePacker.cpp \ + $(NULL) + + # flex/yacc generated files + CPPSRCS += \ + glslang_lex.cpp \ + glslang_tab.cpp \ + $(NULL) + +diff --git a/gfx/angle/include/GLSLANG/ShaderLang.h b/gfx/angle/include/GLSLANG/ShaderLang.h +--- a/gfx/angle/include/GLSLANG/ShaderLang.h ++++ b/gfx/angle/include/GLSLANG/ShaderLang.h +@@ -137,17 +137,20 @@ typedef enum { + SH_TIMING_RESTRICTIONS = 0x0200, + + // This flag prints the dependency graph that is used to enforce timing + // restrictions on fragment shaders. + // This flag only has an effect if all of the following are true: + // - The shader spec is SH_WEBGL_SPEC. + // - The compile options contain the SH_TIMING_RESTRICTIONS flag. + // - The shader type is SH_FRAGMENT_SHADER. +- SH_DEPENDENCY_GRAPH = 0x0400 ++ SH_DEPENDENCY_GRAPH = 0x0400, ++ ++ // Enforce the GLSL 1.017 Appendix A section 7 packing restrictions. ++ SH_ENFORCE_PACKING_RESTRICTIONS = 0x0800, + } ShCompileOptions; + + // + // Driver must call this first, once, before doing any other + // compiler operations. + // If the function succeeds, the return value is nonzero, else zero. + // + COMPILER_EXPORT int ShInitialize(); +diff --git a/gfx/angle/src/compiler/Compiler.cpp b/gfx/angle/src/compiler/Compiler.cpp +--- a/gfx/angle/src/compiler/Compiler.cpp ++++ b/gfx/angle/src/compiler/Compiler.cpp +@@ -9,16 +9,17 @@ + #include "compiler/ForLoopUnroll.h" + #include "compiler/Initialize.h" + #include "compiler/InitializeParseContext.h" + #include "compiler/MapLongVariableNames.h" + #include "compiler/ParseHelper.h" + #include "compiler/RenameFunction.h" + #include "compiler/ShHandle.h" + #include "compiler/ValidateLimitations.h" ++#include "compiler/VariablePacker.h" + #include "compiler/depgraph/DependencyGraph.h" + #include "compiler/depgraph/DependencyGraphOutput.h" + #include "compiler/timing/RestrictFragmentShaderTiming.h" + #include "compiler/timing/RestrictVertexShaderTiming.h" + + bool isWebGLBasedSpec(ShShaderSpec spec) + { + return spec == SH_WEBGL_SPEC || spec == SH_CSS_SHADERS_SPEC; +@@ -108,16 +109,19 @@ TCompiler::TCompiler(ShShaderType type, + TCompiler::~TCompiler() + { + ASSERT(longNameMap); + longNameMap->Release(); + } + + bool TCompiler::Init(const ShBuiltInResources& resources) + { ++ maxUniformVectors = (shaderType == SH_VERTEX_SHADER) ? ++ resources.MaxVertexUniformVectors : ++ resources.MaxFragmentUniformVectors; + TScopedPoolAllocator scopedAlloc(&allocator, false); + + // Generate built-in symbol table. + if (!InitBuiltInSymbolTable(resources)) + return false; + InitExtensionBehavior(resources, extensionBehavior); + + return true; +@@ -187,18 +191,25 @@ bool TCompiler::compile(const char* cons + builtInFunctionEmulator.MarkBuiltInFunctionsForEmulation(root); + + // Call mapLongVariableNames() before collectAttribsUniforms() so in + // collectAttribsUniforms() we already have the mapped symbol names and + // we could composite mapped and original variable names. + if (success && (compileOptions & SH_MAP_LONG_VARIABLE_NAMES)) + mapLongVariableNames(root); + +- if (success && (compileOptions & SH_ATTRIBUTES_UNIFORMS)) ++ if (success && (compileOptions & SH_ATTRIBUTES_UNIFORMS)) { + collectAttribsUniforms(root); ++ if (compileOptions & SH_ENFORCE_PACKING_RESTRICTIONS) { ++ success = enforcePackingRestrictions(); ++ if (!success) { ++ infoSink.info.message(EPrefixError, "too many uniforms"); ++ } ++ } ++ } + + if (success && (compileOptions & SH_INTERMEDIATE_TREE)) + intermediate.outputTree(root); + + if (success && (compileOptions & SH_OBJECT_CODE)) + translate(root); + } + +@@ -305,16 +316,22 @@ bool TCompiler::enforceVertexShaderTimin + } + + void TCompiler::collectAttribsUniforms(TIntermNode* root) + { + CollectAttribsUniforms collect(attribs, uniforms); + root->traverse(&collect); + } + ++bool TCompiler::enforcePackingRestrictions() ++{ ++ VariablePacker packer; ++ return packer.CheckVariablesWithinPackingLimits(maxUniformVectors, uniforms); ++} ++ + void TCompiler::mapLongVariableNames(TIntermNode* root) + { + ASSERT(longNameMap); + MapLongVariableNames map(longNameMap); + root->traverse(&map); + } + + int TCompiler::getMappedNameMaxLength() const +diff --git a/gfx/angle/src/compiler/ShHandle.h b/gfx/angle/src/compiler/ShHandle.h +--- a/gfx/angle/src/compiler/ShHandle.h ++++ b/gfx/angle/src/compiler/ShHandle.h +@@ -83,32 +83,37 @@ protected: + // functionality mandated in GLSL 1.0 spec Appendix A. + bool validateLimitations(TIntermNode* root); + // Collect info for all attribs and uniforms. + void collectAttribsUniforms(TIntermNode* root); + // Map long variable names into shorter ones. + void mapLongVariableNames(TIntermNode* root); + // Translate to object code. + virtual void translate(TIntermNode* root) = 0; ++ // Returns true if, after applying the packing rules in the GLSL 1.017 spec ++ // Appendix A, section 7, the shader does not use too many uniforms. ++ bool enforcePackingRestrictions(); + // Returns true if the shader passes the restrictions that aim to prevent timing attacks. + bool enforceTimingRestrictions(TIntermNode* root, bool outputGraph); + // Returns true if the shader does not use samplers. + bool enforceVertexShaderTimingRestrictions(TIntermNode* root); + // Returns true if the shader does not use sampler dependent values to affect control + // flow or in operations whose time can depend on the input values. + bool enforceFragmentShaderTimingRestrictions(const TDependencyGraph& graph); + // Get built-in extensions with default behavior. + const TExtensionBehavior& getExtensionBehavior() const; + + const BuiltInFunctionEmulator& getBuiltInFunctionEmulator() const; + + private: + ShShaderType shaderType; + ShShaderSpec shaderSpec; + ++ int maxUniformVectors; ++ + // Built-in symbol table for the given language, spec, and resources. + // It is preserved from compile-to-compile. + TSymbolTable symbolTable; + // Built-in extensions with default behavior. + TExtensionBehavior extensionBehavior; + + BuiltInFunctionEmulator builtInFunctionEmulator; + +diff --git a/gfx/angle/src/compiler/VariableInfo.cpp b/gfx/angle/src/compiler/VariableInfo.cpp +--- a/gfx/angle/src/compiler/VariableInfo.cpp ++++ b/gfx/angle/src/compiler/VariableInfo.cpp +@@ -133,16 +133,26 @@ void getUserDefinedVariableInfo(const TT + const TType* fieldType = (*structure)[i].type; + getVariableInfo(*fieldType, + name + "." + fieldType->getFieldName(), + mappedName + "." + fieldType->getFieldName(), + infoList); + } + } + ++TVariableInfo::TVariableInfo() ++{ ++} ++ ++TVariableInfo::TVariableInfo(ShDataType type, int size) ++ : type(type), ++ size(size) ++{ ++} ++ + CollectAttribsUniforms::CollectAttribsUniforms(TVariableInfoList& attribs, + TVariableInfoList& uniforms) + : mAttribs(attribs), + mUniforms(uniforms) + { + } + + // We are only interested in attribute and uniform variable declaration. +diff --git a/gfx/angle/src/compiler/VariableInfo.h b/gfx/angle/src/compiler/VariableInfo.h +--- a/gfx/angle/src/compiler/VariableInfo.h ++++ b/gfx/angle/src/compiler/VariableInfo.h +@@ -8,16 +8,19 @@ + #define COMPILER_VARIABLE_INFO_H_ + + #include "GLSLANG/ShaderLang.h" + #include "compiler/intermediate.h" + + // Provides information about a variable. + // It is currently being used to store info about active attribs and uniforms. + struct TVariableInfo { ++ TVariableInfo(ShDataType type, int size); ++ TVariableInfo(); ++ + TPersistString name; + TPersistString mappedName; + ShDataType type; + int size; + }; + typedef std::vector TVariableInfoList; + + // Traverses intermediate tree to collect all attributes and uniforms. +diff --git a/gfx/angle/src/compiler/VariablePacker.cpp b/gfx/angle/src/compiler/VariablePacker.cpp +new file mode 100644 +--- /dev/null ++++ b/gfx/angle/src/compiler/VariablePacker.cpp +@@ -0,0 +1,297 @@ ++// ++// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++// ++#include "compiler/VariablePacker.h" ++ ++#include ++#include "compiler/ShHandle.h" ++ ++namespace { ++int GetSortOrder(ShDataType type) ++{ ++ switch (type) { ++ case SH_FLOAT_MAT4: ++ return 0; ++ case SH_FLOAT_MAT2: ++ return 1; ++ case SH_FLOAT_VEC4: ++ case SH_INT_VEC4: ++ case SH_BOOL_VEC4: ++ return 2; ++ case SH_FLOAT_MAT3: ++ return 3; ++ case SH_FLOAT_VEC3: ++ case SH_INT_VEC3: ++ case SH_BOOL_VEC3: ++ return 4; ++ case SH_FLOAT_VEC2: ++ case SH_INT_VEC2: ++ case SH_BOOL_VEC2: ++ return 5; ++ case SH_FLOAT: ++ case SH_INT: ++ case SH_BOOL: ++ case SH_SAMPLER_2D: ++ case SH_SAMPLER_CUBE: ++ case SH_SAMPLER_EXTERNAL_OES: ++ case SH_SAMPLER_2D_RECT_ARB: ++ return 6; ++ default: ++ ASSERT(false); ++ return 7; ++ } ++} ++} // namespace ++ ++int VariablePacker::GetNumComponentsPerRow(ShDataType type) ++{ ++ switch (type) { ++ case SH_FLOAT_MAT4: ++ case SH_FLOAT_MAT2: ++ case SH_FLOAT_VEC4: ++ case SH_INT_VEC4: ++ case SH_BOOL_VEC4: ++ return 4; ++ case SH_FLOAT_MAT3: ++ case SH_FLOAT_VEC3: ++ case SH_INT_VEC3: ++ case SH_BOOL_VEC3: ++ return 3; ++ case SH_FLOAT_VEC2: ++ case SH_INT_VEC2: ++ case SH_BOOL_VEC2: ++ return 2; ++ case SH_FLOAT: ++ case SH_INT: ++ case SH_BOOL: ++ case SH_SAMPLER_2D: ++ case SH_SAMPLER_CUBE: ++ case SH_SAMPLER_EXTERNAL_OES: ++ case SH_SAMPLER_2D_RECT_ARB: ++ return 1; ++ default: ++ ASSERT(false); ++ return 5; ++ } ++} ++ ++int VariablePacker::GetNumRows(ShDataType type) ++{ ++ switch (type) { ++ case SH_FLOAT_MAT4: ++ return 4; ++ case SH_FLOAT_MAT3: ++ return 3; ++ case SH_FLOAT_MAT2: ++ return 1; ++ case SH_FLOAT_VEC4: ++ case SH_INT_VEC4: ++ case SH_BOOL_VEC4: ++ case SH_FLOAT_VEC3: ++ case SH_INT_VEC3: ++ case SH_BOOL_VEC3: ++ case SH_FLOAT_VEC2: ++ case SH_INT_VEC2: ++ case SH_BOOL_VEC2: ++ case SH_FLOAT: ++ case SH_INT: ++ case SH_BOOL: ++ case SH_SAMPLER_2D: ++ case SH_SAMPLER_CUBE: ++ case SH_SAMPLER_EXTERNAL_OES: ++ case SH_SAMPLER_2D_RECT_ARB: ++ return 1; ++ default: ++ ASSERT(false); ++ return 100000; ++ } ++} ++ ++struct TVariableInfoComparer { ++ bool operator()(const TVariableInfo& lhs, const TVariableInfo& rhs) const ++ { ++ int lhsSortOrder = GetSortOrder(lhs.type); ++ int rhsSortOrder = GetSortOrder(rhs.type); ++ if (lhsSortOrder != rhsSortOrder) { ++ return lhsSortOrder < rhsSortOrder; ++ } ++ // Sort by largest first. ++ return lhs.size > rhs.size; ++ } ++}; ++ ++unsigned VariablePacker::makeColumnFlags(int column, int numComponentsPerRow) ++{ ++ return ((kColumnMask << (kNumColumns - numComponentsPerRow)) & ++ kColumnMask) >> column; ++} ++ ++void VariablePacker::fillColumns(int topRow, int numRows, int column, int numComponentsPerRow) ++{ ++ unsigned columnFlags = makeColumnFlags(column, numComponentsPerRow); ++ for (int r = 0; r < numRows; ++r) { ++ int row = topRow + r; ++ ASSERT((rows_[row] & columnFlags) == 0); ++ rows_[row] |= columnFlags; ++ } ++} ++ ++bool VariablePacker::searchColumn(int column, int numRows, int* destRow, int* destSize) ++{ ++ ASSERT(destRow); ++ ++ for (; topNonFullRow_ < maxRows_ && rows_[topNonFullRow_] == kColumnMask; ++ ++topNonFullRow_) { ++ } ++ ++ for (; bottomNonFullRow_ >= 0 && rows_[bottomNonFullRow_] == kColumnMask; ++ --bottomNonFullRow_) { ++ } ++ ++ if (bottomNonFullRow_ - topNonFullRow_ + 1 < numRows) { ++ return false; ++ } ++ ++ unsigned columnFlags = makeColumnFlags(column, 1); ++ int topGoodRow = 0; ++ int smallestGoodTop = -1; ++ int smallestGoodSize = maxRows_ + 1; ++ int bottomRow = bottomNonFullRow_ + 1; ++ bool found = false; ++ for (int row = topNonFullRow_; row <= bottomRow; ++row) { ++ bool rowEmpty = row < bottomRow ? ((rows_[row] & columnFlags) == 0) : false; ++ if (rowEmpty) { ++ if (!found) { ++ topGoodRow = row; ++ found = true; ++ } ++ } else { ++ if (found) { ++ int size = row - topGoodRow; ++ if (size >= numRows && size < smallestGoodSize) { ++ smallestGoodSize = size; ++ smallestGoodTop = topGoodRow; ++ } ++ } ++ found = false; ++ } ++ } ++ if (smallestGoodTop < 0) { ++ return false; ++ } ++ ++ *destRow = smallestGoodTop; ++ if (destSize) { ++ *destSize = smallestGoodSize; ++ } ++ return true; ++} ++ ++bool VariablePacker::CheckVariablesWithinPackingLimits(int maxVectors, const TVariableInfoList& in_variables) ++{ ++ ASSERT(maxVectors > 0); ++ maxRows_ = maxVectors; ++ topNonFullRow_ = 0; ++ bottomNonFullRow_ = maxRows_ - 1; ++ TVariableInfoList variables(in_variables); ++ ++ // As per GLSL 1.017 Appendix A, Section 7 variables are packed in specific ++ // order by type, then by size of array, largest first. ++ std::sort(variables.begin(), variables.end(), TVariableInfoComparer()); ++ rows_.clear(); ++ rows_.resize(maxVectors, 0); ++ ++ // Packs the 4 column variables. ++ size_t ii = 0; ++ for (; ii < variables.size(); ++ii) { ++ const TVariableInfo& variable = variables[ii]; ++ if (GetNumComponentsPerRow(variable.type) != 4) { ++ break; ++ } ++ topNonFullRow_ += GetNumRows(variable.type) * variable.size; ++ } ++ ++ if (topNonFullRow_ > maxRows_) { ++ return false; ++ } ++ ++ // Packs the 3 column variables. ++ int num3ColumnRows = 0; ++ for (; ii < variables.size(); ++ii) { ++ const TVariableInfo& variable = variables[ii]; ++ if (GetNumComponentsPerRow(variable.type) != 3) { ++ break; ++ } ++ num3ColumnRows += GetNumRows(variable.type) * variable.size; ++ } ++ ++ if (topNonFullRow_ + num3ColumnRows > maxRows_) { ++ return false; ++ } ++ ++ fillColumns(topNonFullRow_, num3ColumnRows, 0, 3); ++ ++ // Packs the 2 column variables. ++ int top2ColumnRow = topNonFullRow_ + num3ColumnRows; ++ int twoColumnRowsAvailable = maxRows_ - top2ColumnRow; ++ int rowsAvailableInColumns01 = twoColumnRowsAvailable; ++ int rowsAvailableInColumns23 = twoColumnRowsAvailable; ++ for (; ii < variables.size(); ++ii) { ++ const TVariableInfo& variable = variables[ii]; ++ if (GetNumComponentsPerRow(variable.type) != 2) { ++ break; ++ } ++ int numRows = GetNumRows(variable.type) * variable.size; ++ if (numRows <= rowsAvailableInColumns01) { ++ rowsAvailableInColumns01 -= numRows; ++ } else if (numRows <= rowsAvailableInColumns23) { ++ rowsAvailableInColumns23 -= numRows; ++ } else { ++ return false; ++ } ++ } ++ ++ int numRowsUsedInColumns01 = ++ twoColumnRowsAvailable - rowsAvailableInColumns01; ++ int numRowsUsedInColumns23 = ++ twoColumnRowsAvailable - rowsAvailableInColumns23; ++ fillColumns(top2ColumnRow, numRowsUsedInColumns01, 0, 2); ++ fillColumns(maxRows_ - numRowsUsedInColumns23, numRowsUsedInColumns23, ++ 2, 2); ++ ++ // Packs the 1 column variables. ++ for (; ii < variables.size(); ++ii) { ++ const TVariableInfo& variable = variables[ii]; ++ ASSERT(1 == GetNumComponentsPerRow(variable.type)); ++ int numRows = GetNumRows(variable.type) * variable.size; ++ int smallestColumn = -1; ++ int smallestSize = maxRows_ + 1; ++ int topRow = -1; ++ for (int column = 0; column < kNumColumns; ++column) { ++ int row = 0; ++ int size = 0; ++ if (searchColumn(column, numRows, &row, &size)) { ++ if (size < smallestSize) { ++ smallestSize = size; ++ smallestColumn = column; ++ topRow = row; ++ } ++ } ++ } ++ ++ if (smallestColumn < 0) { ++ return false; ++ } ++ ++ fillColumns(topRow, numRows, smallestColumn, 1); ++ } ++ ++ ASSERT(variables.size() == ii); ++ ++ return true; ++} ++ ++ ++ +diff --git a/gfx/angle/src/compiler/VariablePacker.h b/gfx/angle/src/compiler/VariablePacker.h +new file mode 100644 +--- /dev/null ++++ b/gfx/angle/src/compiler/VariablePacker.h +@@ -0,0 +1,41 @@ ++// ++// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++// ++ ++#ifndef _VARIABLEPACKER_INCLUDED_ ++#define _VARIABLEPACKER_INCLUDED_ ++ ++#include ++#include "compiler/ShHandle.h" ++ ++class VariablePacker { ++ public: ++ // Returns true if the passed in variables pack in maxVectors following ++ // the packing rules from the GLSL 1.017 spec, Appendix A, section 7. ++ bool CheckVariablesWithinPackingLimits( ++ int maxVectors, ++ const TVariableInfoList& in_variables); ++ ++ // Gets how many components in a row a data type takes. ++ static int GetNumComponentsPerRow(ShDataType type); ++ ++ // Gets how many rows a data type takes. ++ static int GetNumRows(ShDataType type); ++ ++ private: ++ static const int kNumColumns = 4; ++ static const unsigned kColumnMask = (1 << kNumColumns) - 1; ++ ++ unsigned makeColumnFlags(int column, int numComponentsPerRow); ++ void fillColumns(int topRow, int numRows, int column, int numComponentsPerRow); ++ bool searchColumn(int column, int numRows, int* destRow, int* destSize); ++ ++ int topNonFullRow_; ++ int bottomNonFullRow_; ++ int maxRows_; ++ std::vector rows_; ++}; ++ ++#endif // _VARIABLEPACKER_INCLUDED_ +diff --git a/gfx/angle/src/libGLESv2/Makefile.in b/gfx/angle/src/libGLESv2/Makefile.in +--- a/gfx/angle/src/libGLESv2/Makefile.in ++++ b/gfx/angle/src/libGLESv2/Makefile.in +@@ -84,16 +84,17 @@ CPPSRCS = \ + ForLoopUnroll.cpp \ + MapLongVariableNames.cpp \ + spooky.cpp \ + BuiltInFunctionEmulator.cpp \ + Input.cpp \ + Lexer.cpp \ + Preprocessor.cpp \ + Token.cpp \ ++ VariablePacker.cpp \ + $(NULL) + + # flex/yacc generated files + CPPSRCS += \ + glslang_lex.cpp \ + glslang_tab.cpp \ + $(NULL) + +diff --git a/gfx/angle/tests/build_tests.gyp b/gfx/angle/tests/build_tests.gyp +--- a/gfx/angle/tests/build_tests.gyp ++++ b/gfx/angle/tests/build_tests.gyp +@@ -58,16 +58,35 @@ + 'preprocessor_tests/pragma_test.cpp', + 'preprocessor_tests/PreprocessorTest.cpp', + 'preprocessor_tests/PreprocessorTest.h', + 'preprocessor_tests/space_test.cpp', + 'preprocessor_tests/token_test.cpp', + 'preprocessor_tests/version_test.cpp', + ], + }, ++ { ++ 'target_name': 'compiler_tests', ++ 'type': 'executable', ++ 'dependencies': [ ++ '../src/build_angle.gyp:translator_common', ++ 'gtest', ++ 'gmock', ++ ], ++ 'include_dirs': [ ++ '../include', ++ '../src', ++ '../third_party/googletest/include', ++ '../third_party/googlemock/include', ++ ], ++ 'sources': [ ++ '../third_party/googlemock/src/gmock_main.cc', ++ 'compiler_tests/VariablePacker_test.cpp', ++ ], ++ }, + ], + } + + # Local Variables: + # tab-width:2 + # indent-tabs-mode:nil + # End: + # vim: set expandtab tabstop=2 shiftwidth=2: +diff --git a/gfx/angle/tests/compiler_tests/VariablePacker_test.cpp b/gfx/angle/tests/compiler_tests/VariablePacker_test.cpp +new file mode 100644 +--- /dev/null ++++ b/gfx/angle/tests/compiler_tests/VariablePacker_test.cpp +@@ -0,0 +1,85 @@ ++// ++// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++// ++#include "compiler/VariablePacker.h" ++#include "gtest/gtest.h" ++ ++TEST(VariablePacking, Pack) { ++ VariablePacker packer; ++ TVariableInfoList vars; ++ const int kMaxRows = 16; ++ // test no vars. ++ EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); ++ ++ ShDataType types[] = { ++ SH_FLOAT_MAT4, // 0 ++ SH_FLOAT_MAT2, // 1 ++ SH_FLOAT_VEC4, // 2 ++ SH_INT_VEC4, // 3 ++ SH_BOOL_VEC4, // 4 ++ SH_FLOAT_MAT3, // 5 ++ SH_FLOAT_VEC3, // 6 ++ SH_INT_VEC3, // 7 ++ SH_BOOL_VEC3, // 8 ++ SH_FLOAT_VEC2, // 9 ++ SH_INT_VEC2, // 10 ++ SH_BOOL_VEC2, // 11 ++ SH_FLOAT, // 12 ++ SH_INT, // 13 ++ SH_BOOL, // 14 ++ SH_SAMPLER_2D, // 15 ++ SH_SAMPLER_CUBE, // 16 ++ SH_SAMPLER_EXTERNAL_OES, // 17 ++ SH_SAMPLER_2D_RECT_ARB, // 18 ++ }; ++ ++ for (size_t tt = 0; tt < sizeof(types) / sizeof(types[0]); ++tt) { ++ ShDataType type = types[tt]; ++ int num_rows = VariablePacker::GetNumRows(type); ++ int num_components_per_row = VariablePacker::GetNumComponentsPerRow(type); ++ // Check 1 of the type. ++ vars.clear(); ++ vars.push_back(TVariableInfo(type, 1)); ++ EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); ++ ++ // Check exactly the right amount of 1 type as an array. ++ int num_vars = kMaxRows / num_rows; ++ vars.clear(); ++ vars.push_back(TVariableInfo(type, num_vars)); ++ EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); ++ ++ // test too many ++ vars.clear(); ++ vars.push_back(TVariableInfo(type, num_vars + 1)); ++ EXPECT_FALSE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); ++ ++ // Check exactly the right amount of 1 type as individual vars. ++ num_vars = kMaxRows / num_rows * ++ ((num_components_per_row > 2) ? 1 : (4 / num_components_per_row)); ++ vars.clear(); ++ for (int ii = 0; ii < num_vars; ++ii) { ++ vars.push_back(TVariableInfo(type, 1)); ++ } ++ EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); ++ ++ // Check 1 too many. ++ vars.push_back(TVariableInfo( type, 1)); ++ EXPECT_FALSE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); ++ } ++ ++ // Test example from GLSL ES 3.0 spec chapter 11. ++ vars.clear(); ++ vars.push_back(TVariableInfo(SH_FLOAT_VEC4, 1)); ++ vars.push_back(TVariableInfo(SH_FLOAT_MAT3, 1)); ++ vars.push_back(TVariableInfo(SH_FLOAT_MAT3, 1)); ++ vars.push_back(TVariableInfo(SH_FLOAT_VEC2, 6)); ++ vars.push_back(TVariableInfo(SH_FLOAT_VEC2, 4)); ++ vars.push_back(TVariableInfo(SH_FLOAT_VEC2, 1)); ++ vars.push_back(TVariableInfo(SH_FLOAT, 3)); ++ vars.push_back(TVariableInfo(SH_FLOAT, 2)); ++ vars.push_back(TVariableInfo(SH_FLOAT, 1)); ++ EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); ++} ++ diff --git a/gfx/angle/include/GLSLANG/ShaderLang.h b/gfx/angle/include/GLSLANG/ShaderLang.h index 9a4a36dbf30b..d925029a2c6a 100644 --- a/gfx/angle/include/GLSLANG/ShaderLang.h +++ b/gfx/angle/include/GLSLANG/ShaderLang.h @@ -142,7 +142,10 @@ typedef enum { // - The shader spec is SH_WEBGL_SPEC. // - The compile options contain the SH_TIMING_RESTRICTIONS flag. // - The shader type is SH_FRAGMENT_SHADER. - SH_DEPENDENCY_GRAPH = 0x0400 + SH_DEPENDENCY_GRAPH = 0x0400, + + // Enforce the GLSL 1.017 Appendix A section 7 packing restrictions. + SH_ENFORCE_PACKING_RESTRICTIONS = 0x0800, } ShCompileOptions; // diff --git a/gfx/angle/src/compiler/Compiler.cpp b/gfx/angle/src/compiler/Compiler.cpp index cab8056bcee5..9e7f75c33a54 100644 --- a/gfx/angle/src/compiler/Compiler.cpp +++ b/gfx/angle/src/compiler/Compiler.cpp @@ -14,6 +14,7 @@ #include "compiler/RenameFunction.h" #include "compiler/ShHandle.h" #include "compiler/ValidateLimitations.h" +#include "compiler/VariablePacker.h" #include "compiler/depgraph/DependencyGraph.h" #include "compiler/depgraph/DependencyGraphOutput.h" #include "compiler/timing/RestrictFragmentShaderTiming.h" @@ -113,6 +114,9 @@ TCompiler::~TCompiler() bool TCompiler::Init(const ShBuiltInResources& resources) { + maxUniformVectors = (shaderType == SH_VERTEX_SHADER) ? + resources.MaxVertexUniformVectors : + resources.MaxFragmentUniformVectors; TScopedPoolAllocator scopedAlloc(&allocator, false); // Generate built-in symbol table. @@ -192,8 +196,15 @@ bool TCompiler::compile(const char* const shaderStrings[], if (success && (compileOptions & SH_MAP_LONG_VARIABLE_NAMES)) mapLongVariableNames(root); - if (success && (compileOptions & SH_ATTRIBUTES_UNIFORMS)) + if (success && (compileOptions & SH_ATTRIBUTES_UNIFORMS)) { collectAttribsUniforms(root); + if (compileOptions & SH_ENFORCE_PACKING_RESTRICTIONS) { + success = enforcePackingRestrictions(); + if (!success) { + infoSink.info.message(EPrefixError, "too many uniforms"); + } + } + } if (success && (compileOptions & SH_INTERMEDIATE_TREE)) intermediate.outputTree(root); @@ -310,6 +321,12 @@ void TCompiler::collectAttribsUniforms(TIntermNode* root) root->traverse(&collect); } +bool TCompiler::enforcePackingRestrictions() +{ + VariablePacker packer; + return packer.CheckVariablesWithinPackingLimits(maxUniformVectors, uniforms); +} + void TCompiler::mapLongVariableNames(TIntermNode* root) { ASSERT(longNameMap); diff --git a/gfx/angle/src/compiler/ShHandle.h b/gfx/angle/src/compiler/ShHandle.h index 26528b8dbcba..6ba302ad044e 100644 --- a/gfx/angle/src/compiler/ShHandle.h +++ b/gfx/angle/src/compiler/ShHandle.h @@ -88,6 +88,9 @@ protected: void mapLongVariableNames(TIntermNode* root); // Translate to object code. virtual void translate(TIntermNode* root) = 0; + // Returns true if, after applying the packing rules in the GLSL 1.017 spec + // Appendix A, section 7, the shader does not use too many uniforms. + bool enforcePackingRestrictions(); // Returns true if the shader passes the restrictions that aim to prevent timing attacks. bool enforceTimingRestrictions(TIntermNode* root, bool outputGraph); // Returns true if the shader does not use samplers. @@ -104,6 +107,8 @@ private: ShShaderType shaderType; ShShaderSpec shaderSpec; + int maxUniformVectors; + // Built-in symbol table for the given language, spec, and resources. // It is preserved from compile-to-compile. TSymbolTable symbolTable; diff --git a/gfx/angle/src/compiler/VariableInfo.cpp b/gfx/angle/src/compiler/VariableInfo.cpp index 1e9486f8d339..3ff283627b8b 100644 --- a/gfx/angle/src/compiler/VariableInfo.cpp +++ b/gfx/angle/src/compiler/VariableInfo.cpp @@ -138,6 +138,16 @@ void getUserDefinedVariableInfo(const TType& type, } } +TVariableInfo::TVariableInfo() +{ +} + +TVariableInfo::TVariableInfo(ShDataType type, int size) + : type(type), + size(size) +{ +} + CollectAttribsUniforms::CollectAttribsUniforms(TVariableInfoList& attribs, TVariableInfoList& uniforms) : mAttribs(attribs), diff --git a/gfx/angle/src/compiler/VariableInfo.h b/gfx/angle/src/compiler/VariableInfo.h index 667acafcccc6..fdcc08f5b511 100644 --- a/gfx/angle/src/compiler/VariableInfo.h +++ b/gfx/angle/src/compiler/VariableInfo.h @@ -13,6 +13,9 @@ // Provides information about a variable. // It is currently being used to store info about active attribs and uniforms. struct TVariableInfo { + TVariableInfo(ShDataType type, int size); + TVariableInfo(); + TPersistString name; TPersistString mappedName; ShDataType type; diff --git a/gfx/angle/src/compiler/VariablePacker.cpp b/gfx/angle/src/compiler/VariablePacker.cpp new file mode 100644 index 000000000000..2f0c4bc2a268 --- /dev/null +++ b/gfx/angle/src/compiler/VariablePacker.cpp @@ -0,0 +1,297 @@ +// +// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +#include "compiler/VariablePacker.h" + +#include +#include "compiler/ShHandle.h" + +namespace { +int GetSortOrder(ShDataType type) +{ + switch (type) { + case SH_FLOAT_MAT4: + return 0; + case SH_FLOAT_MAT2: + return 1; + case SH_FLOAT_VEC4: + case SH_INT_VEC4: + case SH_BOOL_VEC4: + return 2; + case SH_FLOAT_MAT3: + return 3; + case SH_FLOAT_VEC3: + case SH_INT_VEC3: + case SH_BOOL_VEC3: + return 4; + case SH_FLOAT_VEC2: + case SH_INT_VEC2: + case SH_BOOL_VEC2: + return 5; + case SH_FLOAT: + case SH_INT: + case SH_BOOL: + case SH_SAMPLER_2D: + case SH_SAMPLER_CUBE: + case SH_SAMPLER_EXTERNAL_OES: + case SH_SAMPLER_2D_RECT_ARB: + return 6; + default: + ASSERT(false); + return 7; + } +} +} // namespace + +int VariablePacker::GetNumComponentsPerRow(ShDataType type) +{ + switch (type) { + case SH_FLOAT_MAT4: + case SH_FLOAT_MAT2: + case SH_FLOAT_VEC4: + case SH_INT_VEC4: + case SH_BOOL_VEC4: + return 4; + case SH_FLOAT_MAT3: + case SH_FLOAT_VEC3: + case SH_INT_VEC3: + case SH_BOOL_VEC3: + return 3; + case SH_FLOAT_VEC2: + case SH_INT_VEC2: + case SH_BOOL_VEC2: + return 2; + case SH_FLOAT: + case SH_INT: + case SH_BOOL: + case SH_SAMPLER_2D: + case SH_SAMPLER_CUBE: + case SH_SAMPLER_EXTERNAL_OES: + case SH_SAMPLER_2D_RECT_ARB: + return 1; + default: + ASSERT(false); + return 5; + } +} + +int VariablePacker::GetNumRows(ShDataType type) +{ + switch (type) { + case SH_FLOAT_MAT4: + return 4; + case SH_FLOAT_MAT3: + return 3; + case SH_FLOAT_MAT2: + return 1; + case SH_FLOAT_VEC4: + case SH_INT_VEC4: + case SH_BOOL_VEC4: + case SH_FLOAT_VEC3: + case SH_INT_VEC3: + case SH_BOOL_VEC3: + case SH_FLOAT_VEC2: + case SH_INT_VEC2: + case SH_BOOL_VEC2: + case SH_FLOAT: + case SH_INT: + case SH_BOOL: + case SH_SAMPLER_2D: + case SH_SAMPLER_CUBE: + case SH_SAMPLER_EXTERNAL_OES: + case SH_SAMPLER_2D_RECT_ARB: + return 1; + default: + ASSERT(false); + return 100000; + } +} + +struct TVariableInfoComparer { + bool operator()(const TVariableInfo& lhs, const TVariableInfo& rhs) const + { + int lhsSortOrder = GetSortOrder(lhs.type); + int rhsSortOrder = GetSortOrder(rhs.type); + if (lhsSortOrder != rhsSortOrder) { + return lhsSortOrder < rhsSortOrder; + } + // Sort by largest first. + return lhs.size > rhs.size; + } +}; + +unsigned VariablePacker::makeColumnFlags(int column, int numComponentsPerRow) +{ + return ((kColumnMask << (kNumColumns - numComponentsPerRow)) & + kColumnMask) >> column; +} + +void VariablePacker::fillColumns(int topRow, int numRows, int column, int numComponentsPerRow) +{ + unsigned columnFlags = makeColumnFlags(column, numComponentsPerRow); + for (int r = 0; r < numRows; ++r) { + int row = topRow + r; + ASSERT((rows_[row] & columnFlags) == 0); + rows_[row] |= columnFlags; + } +} + +bool VariablePacker::searchColumn(int column, int numRows, int* destRow, int* destSize) +{ + ASSERT(destRow); + + for (; topNonFullRow_ < maxRows_ && rows_[topNonFullRow_] == kColumnMask; + ++topNonFullRow_) { + } + + for (; bottomNonFullRow_ >= 0 && rows_[bottomNonFullRow_] == kColumnMask; + --bottomNonFullRow_) { + } + + if (bottomNonFullRow_ - topNonFullRow_ + 1 < numRows) { + return false; + } + + unsigned columnFlags = makeColumnFlags(column, 1); + int topGoodRow = 0; + int smallestGoodTop = -1; + int smallestGoodSize = maxRows_ + 1; + int bottomRow = bottomNonFullRow_ + 1; + bool found = false; + for (int row = topNonFullRow_; row <= bottomRow; ++row) { + bool rowEmpty = row < bottomRow ? ((rows_[row] & columnFlags) == 0) : false; + if (rowEmpty) { + if (!found) { + topGoodRow = row; + found = true; + } + } else { + if (found) { + int size = row - topGoodRow; + if (size >= numRows && size < smallestGoodSize) { + smallestGoodSize = size; + smallestGoodTop = topGoodRow; + } + } + found = false; + } + } + if (smallestGoodTop < 0) { + return false; + } + + *destRow = smallestGoodTop; + if (destSize) { + *destSize = smallestGoodSize; + } + return true; +} + +bool VariablePacker::CheckVariablesWithinPackingLimits(int maxVectors, const TVariableInfoList& in_variables) +{ + ASSERT(maxVectors > 0); + maxRows_ = maxVectors; + topNonFullRow_ = 0; + bottomNonFullRow_ = maxRows_ - 1; + TVariableInfoList variables(in_variables); + + // As per GLSL 1.017 Appendix A, Section 7 variables are packed in specific + // order by type, then by size of array, largest first. + std::sort(variables.begin(), variables.end(), TVariableInfoComparer()); + rows_.clear(); + rows_.resize(maxVectors, 0); + + // Packs the 4 column variables. + size_t ii = 0; + for (; ii < variables.size(); ++ii) { + const TVariableInfo& variable = variables[ii]; + if (GetNumComponentsPerRow(variable.type) != 4) { + break; + } + topNonFullRow_ += GetNumRows(variable.type) * variable.size; + } + + if (topNonFullRow_ > maxRows_) { + return false; + } + + // Packs the 3 column variables. + int num3ColumnRows = 0; + for (; ii < variables.size(); ++ii) { + const TVariableInfo& variable = variables[ii]; + if (GetNumComponentsPerRow(variable.type) != 3) { + break; + } + num3ColumnRows += GetNumRows(variable.type) * variable.size; + } + + if (topNonFullRow_ + num3ColumnRows > maxRows_) { + return false; + } + + fillColumns(topNonFullRow_, num3ColumnRows, 0, 3); + + // Packs the 2 column variables. + int top2ColumnRow = topNonFullRow_ + num3ColumnRows; + int twoColumnRowsAvailable = maxRows_ - top2ColumnRow; + int rowsAvailableInColumns01 = twoColumnRowsAvailable; + int rowsAvailableInColumns23 = twoColumnRowsAvailable; + for (; ii < variables.size(); ++ii) { + const TVariableInfo& variable = variables[ii]; + if (GetNumComponentsPerRow(variable.type) != 2) { + break; + } + int numRows = GetNumRows(variable.type) * variable.size; + if (numRows <= rowsAvailableInColumns01) { + rowsAvailableInColumns01 -= numRows; + } else if (numRows <= rowsAvailableInColumns23) { + rowsAvailableInColumns23 -= numRows; + } else { + return false; + } + } + + int numRowsUsedInColumns01 = + twoColumnRowsAvailable - rowsAvailableInColumns01; + int numRowsUsedInColumns23 = + twoColumnRowsAvailable - rowsAvailableInColumns23; + fillColumns(top2ColumnRow, numRowsUsedInColumns01, 0, 2); + fillColumns(maxRows_ - numRowsUsedInColumns23, numRowsUsedInColumns23, + 2, 2); + + // Packs the 1 column variables. + for (; ii < variables.size(); ++ii) { + const TVariableInfo& variable = variables[ii]; + ASSERT(1 == GetNumComponentsPerRow(variable.type)); + int numRows = GetNumRows(variable.type) * variable.size; + int smallestColumn = -1; + int smallestSize = maxRows_ + 1; + int topRow = -1; + for (int column = 0; column < kNumColumns; ++column) { + int row = 0; + int size = 0; + if (searchColumn(column, numRows, &row, &size)) { + if (size < smallestSize) { + smallestSize = size; + smallestColumn = column; + topRow = row; + } + } + } + + if (smallestColumn < 0) { + return false; + } + + fillColumns(topRow, numRows, smallestColumn, 1); + } + + ASSERT(variables.size() == ii); + + return true; +} + + + diff --git a/gfx/angle/src/compiler/VariablePacker.h b/gfx/angle/src/compiler/VariablePacker.h new file mode 100644 index 000000000000..8987066cc37c --- /dev/null +++ b/gfx/angle/src/compiler/VariablePacker.h @@ -0,0 +1,41 @@ +// +// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +#ifndef _VARIABLEPACKER_INCLUDED_ +#define _VARIABLEPACKER_INCLUDED_ + +#include +#include "compiler/ShHandle.h" + +class VariablePacker { + public: + // Returns true if the passed in variables pack in maxVectors following + // the packing rules from the GLSL 1.017 spec, Appendix A, section 7. + bool CheckVariablesWithinPackingLimits( + int maxVectors, + const TVariableInfoList& in_variables); + + // Gets how many components in a row a data type takes. + static int GetNumComponentsPerRow(ShDataType type); + + // Gets how many rows a data type takes. + static int GetNumRows(ShDataType type); + + private: + static const int kNumColumns = 4; + static const unsigned kColumnMask = (1 << kNumColumns) - 1; + + unsigned makeColumnFlags(int column, int numComponentsPerRow); + void fillColumns(int topRow, int numRows, int column, int numComponentsPerRow); + bool searchColumn(int column, int numRows, int* destRow, int* destSize); + + int topNonFullRow_; + int bottomNonFullRow_; + int maxRows_; + std::vector rows_; +}; + +#endif // _VARIABLEPACKER_INCLUDED_ diff --git a/gfx/angle/src/libGLESv2/Makefile.in b/gfx/angle/src/libGLESv2/Makefile.in index 5b67295195e2..31704c0a8639 100644 --- a/gfx/angle/src/libGLESv2/Makefile.in +++ b/gfx/angle/src/libGLESv2/Makefile.in @@ -89,6 +89,7 @@ CPPSRCS = \ Lexer.cpp \ Preprocessor.cpp \ Token.cpp \ + VariablePacker.cpp \ $(NULL) # flex/yacc generated files diff --git a/gfx/angle/tests/build_tests.gyp b/gfx/angle/tests/build_tests.gyp index dc02d7578211..32391ad55a5d 100644 --- a/gfx/angle/tests/build_tests.gyp +++ b/gfx/angle/tests/build_tests.gyp @@ -63,6 +63,25 @@ 'preprocessor_tests/version_test.cpp', ], }, + { + 'target_name': 'compiler_tests', + 'type': 'executable', + 'dependencies': [ + '../src/build_angle.gyp:translator_common', + 'gtest', + 'gmock', + ], + 'include_dirs': [ + '../include', + '../src', + '../third_party/googletest/include', + '../third_party/googlemock/include', + ], + 'sources': [ + '../third_party/googlemock/src/gmock_main.cc', + 'compiler_tests/VariablePacker_test.cpp', + ], + }, ], } diff --git a/gfx/angle/tests/compiler_tests/VariablePacker_test.cpp b/gfx/angle/tests/compiler_tests/VariablePacker_test.cpp new file mode 100644 index 000000000000..6405ea70f75f --- /dev/null +++ b/gfx/angle/tests/compiler_tests/VariablePacker_test.cpp @@ -0,0 +1,85 @@ +// +// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +#include "compiler/VariablePacker.h" +#include "gtest/gtest.h" + +TEST(VariablePacking, Pack) { + VariablePacker packer; + TVariableInfoList vars; + const int kMaxRows = 16; + // test no vars. + EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); + + ShDataType types[] = { + SH_FLOAT_MAT4, // 0 + SH_FLOAT_MAT2, // 1 + SH_FLOAT_VEC4, // 2 + SH_INT_VEC4, // 3 + SH_BOOL_VEC4, // 4 + SH_FLOAT_MAT3, // 5 + SH_FLOAT_VEC3, // 6 + SH_INT_VEC3, // 7 + SH_BOOL_VEC3, // 8 + SH_FLOAT_VEC2, // 9 + SH_INT_VEC2, // 10 + SH_BOOL_VEC2, // 11 + SH_FLOAT, // 12 + SH_INT, // 13 + SH_BOOL, // 14 + SH_SAMPLER_2D, // 15 + SH_SAMPLER_CUBE, // 16 + SH_SAMPLER_EXTERNAL_OES, // 17 + SH_SAMPLER_2D_RECT_ARB, // 18 + }; + + for (size_t tt = 0; tt < sizeof(types) / sizeof(types[0]); ++tt) { + ShDataType type = types[tt]; + int num_rows = VariablePacker::GetNumRows(type); + int num_components_per_row = VariablePacker::GetNumComponentsPerRow(type); + // Check 1 of the type. + vars.clear(); + vars.push_back(TVariableInfo(type, 1)); + EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); + + // Check exactly the right amount of 1 type as an array. + int num_vars = kMaxRows / num_rows; + vars.clear(); + vars.push_back(TVariableInfo(type, num_vars)); + EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); + + // test too many + vars.clear(); + vars.push_back(TVariableInfo(type, num_vars + 1)); + EXPECT_FALSE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); + + // Check exactly the right amount of 1 type as individual vars. + num_vars = kMaxRows / num_rows * + ((num_components_per_row > 2) ? 1 : (4 / num_components_per_row)); + vars.clear(); + for (int ii = 0; ii < num_vars; ++ii) { + vars.push_back(TVariableInfo(type, 1)); + } + EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); + + // Check 1 too many. + vars.push_back(TVariableInfo( type, 1)); + EXPECT_FALSE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); + } + + // Test example from GLSL ES 3.0 spec chapter 11. + vars.clear(); + vars.push_back(TVariableInfo(SH_FLOAT_VEC4, 1)); + vars.push_back(TVariableInfo(SH_FLOAT_MAT3, 1)); + vars.push_back(TVariableInfo(SH_FLOAT_MAT3, 1)); + vars.push_back(TVariableInfo(SH_FLOAT_VEC2, 6)); + vars.push_back(TVariableInfo(SH_FLOAT_VEC2, 4)); + vars.push_back(TVariableInfo(SH_FLOAT_VEC2, 1)); + vars.push_back(TVariableInfo(SH_FLOAT, 3)); + vars.push_back(TVariableInfo(SH_FLOAT, 2)); + vars.push_back(TVariableInfo(SH_FLOAT, 1)); + EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars)); +} + diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 17197535c857..54352bec7215 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -295,7 +295,8 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) "NVIDIA", "ATI", "Qualcomm", - "Imagination" + "Imagination", + "nouveau" }; mVendor = VendorOther; for (int i = 0; i < VendorOther; ++i) { @@ -514,11 +515,20 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) #ifdef XP_MACOSX if (mWorkAroundDriverBugs && mVendor == VendorIntel) { - // see bug 737182 for 2D textures, bug 684822 for cube map textures. + // see bug 737182 for 2D textures, bug 684882 for cube map textures. mMaxTextureSize = NS_MIN(mMaxTextureSize, 4096); mMaxCubeMapTextureSize = NS_MIN(mMaxCubeMapTextureSize, 512); // for good measure, we align renderbuffers on what we do for 2D textures mMaxRenderbufferSize = NS_MIN(mMaxRenderbufferSize, 4096); + mNeedsTextureSizeChecks = true; + } +#endif +#ifdef MOZ_X11 + if (mWorkAroundDriverBugs && + mVendor == VendorNouveau) { + // see bug 814716. Clamp MaxCubeMapTextureSize at 2K for Nouveau. + mMaxCubeMapTextureSize = NS_MIN(mMaxCubeMapTextureSize, 2048); + mNeedsTextureSizeChecks = true; } #endif diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index d8a327b26d41..01ced29a70ed 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -165,6 +165,7 @@ public: mMaxCubeMapTextureSize(0), mMaxTextureImageSize(0), mMaxRenderbufferSize(0), + mNeedsTextureSizeChecks(false), mWorkAroundDriverBugs(true) #ifdef DEBUG , mGLError(LOCAL_GL_NO_ERROR) @@ -341,6 +342,7 @@ public: VendorATI, VendorQualcomm, VendorImagination, + VendorNouveau, VendorOther }; @@ -1330,7 +1332,6 @@ public: static bool ListHasExtension(const GLubyte *extensions, const char *extension); - GLint GetMaxTextureSize() { return mMaxTextureSize; } GLint GetMaxTextureImageSize() { return mMaxTextureImageSize; } void SetFlipped(bool aFlipped) { mFlipped = aFlipped; } @@ -1581,16 +1582,17 @@ protected: GLint mMaxCubeMapTextureSize; GLint mMaxTextureImageSize; GLint mMaxRenderbufferSize; + bool mNeedsTextureSizeChecks; bool mWorkAroundDriverBugs; bool IsTextureSizeSafeToPassToDriver(GLenum target, GLsizei width, GLsizei height) const { -#ifdef XP_MACOSX - if (mWorkAroundDriverBugs && - mVendor == VendorIntel) { - // see bug 737182 for 2D textures, bug 684822 for cube map textures. - // some drivers handle incorrectly some large texture sizes that are below the + if (mNeedsTextureSizeChecks) { + // some drivers incorrectly handle some large texture sizes that are below the // max texture size that they report. So we check ourselves against our own values // (mMax[CubeMap]TextureSize). + // see bug 737182 for Mac Intel 2D textures + // see bug 684882 for Mac Intel cube map textures + // see bug 814716 for Mesa Nouveau GLsizei maxSize = target == LOCAL_GL_TEXTURE_CUBE_MAP || (target >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X && target <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z) @@ -1598,7 +1600,6 @@ protected: : mMaxTextureSize; return width <= maxSize && height <= maxSize; } -#endif return true; } diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp index bd39c2f034be..f622d555498d 100644 --- a/gfx/gl/GLTextureImage.cpp +++ b/gfx/gl/GLTextureImage.cpp @@ -200,8 +200,11 @@ TiledTextureImage::TiledTextureImage(GLContext* aGL, , mGL(aGL) , mTextureState(Created) { - mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) - ? 256 : mGL->GetMaxTextureSize(); + if (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) { + mTileSize = 256; + } else { + mGL->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*) &mTileSize); + } if (aSize.width != 0 && aSize.height != 0) { Resize(aSize); } diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 338007f0d7e3..d6d1f4576bbc 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -289,7 +289,8 @@ Layer::Layer(LayerManager* aManager, void* aImplData) : mUseClipRect(false), mUseTileSourceRect(false), mIsFixedPosition(false), - mDebugColorIndex(0) + mDebugColorIndex(0), + mAnimationGeneration(0) {} Layer::~Layer() diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index ae4be2e91df4..455560d6b748 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -804,6 +804,9 @@ public: AnimationArray& GetAnimations() { return mAnimations; } InfallibleTArray& GetAnimationData() { return mAnimationData; } + uint64_t GetAnimationGeneration() { return mAnimationGeneration; } + void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; } + /** * DRAWING PHASE ONLY * @@ -1108,6 +1111,9 @@ protected: bool mIsFixedPosition; gfxPoint mAnchor; DebugOnly mDebugColorIndex; + // If this layer is used for OMTA, then this counter is used to ensure we + // stay in sync with the animation manager + uint64_t mAnimationGeneration; }; /** diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 6500011ffa8e..2d475c47fafd 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -818,7 +818,7 @@ CompositorParent::TransformShadowTree(TimeStamp aCurrentFrame) // NB: we must sample animations *before* sampling pan/zoom // transforms. - wantNextFrame |= SampleAnimations(root, mLastCompose); + wantNextFrame |= SampleAnimations(root, aCurrentFrame); const FrameMetrics& metrics = container->GetFrameMetrics(); // We must apply the resolution scale before a pan/zoom transform, so we call diff --git a/gfx/layers/opengl/CanvasLayerOGL.cpp b/gfx/layers/opengl/CanvasLayerOGL.cpp index 6616fb9fde05..ecfd3915f359 100644 --- a/gfx/layers/opengl/CanvasLayerOGL.cpp +++ b/gfx/layers/opengl/CanvasLayerOGL.cpp @@ -162,7 +162,8 @@ CanvasLayerOGL::Initialize(const Data& aData) // Check the maximum texture size supported by GL. glTexImage2D supports // images of up to 2 + GL_MAX_TEXTURE_SIZE - GLint texSize = gl()->GetMaxTextureSize(); + GLint texSize; + gl()->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &texSize); if (mBounds.width > (2 + texSize) || mBounds.height > (2 + texSize)) { mDelayedUpdates = true; MakeTextureIfNeeded(gl(), mTexture); diff --git a/gfx/layers/opengl/LayerManagerOGL.cpp b/gfx/layers/opengl/LayerManagerOGL.cpp index 12ac2010221f..fc6ec1a97d04 100644 --- a/gfx/layers/opengl/LayerManagerOGL.cpp +++ b/gfx/layers/opengl/LayerManagerOGL.cpp @@ -62,7 +62,9 @@ LayerManagerOGL::Initialize(bool force) int32_t LayerManagerOGL::GetMaxTextureSize() const { - return mGLContext->GetMaxTextureSize(); + int32_t maxSize; + mGLContext->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &maxSize); + return maxSize; } void diff --git a/gfx/thebes/gfxSVGGlyphs.cpp b/gfx/thebes/gfxSVGGlyphs.cpp index 0bbf54bf9e37..62e4e122ea66 100644 --- a/gfx/thebes/gfxSVGGlyphs.cpp +++ b/gfx/thebes/gfxSVGGlyphs.cpp @@ -43,7 +43,6 @@ #include "nsIDOMNodeList.h" #include "nsString.h" #include "nsIDocument.h" -#include "nsPrintfCString.h" #include "nsICategoryManager.h" #include "nsIDocumentLoaderFactory.h" #include "nsIContentViewer.h" @@ -56,11 +55,11 @@ #include "nsXMLContentSink.h" #include "nsNetUtil.h" #include "nsIInputStream.h" -#include "nsContentUtils.h" #include "nsStringStream.h" #include "nsStreamUtils.h" #include "nsIPrincipal.h" #include "Element.h" +#include "nsSVGUtils.h" #define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml") #define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8") @@ -230,7 +229,7 @@ gfxSVGGlyphs::RenderGlyph(gfxContext *aContext, uint32_t aGlyphId, Element *glyph = mGlyphIdMap.Get(aGlyphId); NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); - return nsContentUtils::PaintSVGGlyph(glyph, aContext, aDrawMode, aObjectPaint); + return nsSVGUtils::PaintSVGGlyph(glyph, aContext, aDrawMode, aObjectPaint); } bool @@ -240,7 +239,7 @@ gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace Element *glyph = mGlyphIdMap.Get(aGlyphId); NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); - return nsContentUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult); + return nsSVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult); } Element * diff --git a/js/src/config/config.mk b/js/src/config/config.mk index b14bb36846ab..8c4f6e479d8a 100644 --- a/js/src/config/config.mk +++ b/js/src/config/config.mk @@ -76,9 +76,9 @@ LIBXUL_DIST ?= $(DIST) # build products (typelibs, components, chrome). # # If XPI_NAME is set, the files will be shipped to $(DIST)/xpi-stage/$(XPI_NAME) -# If DIST_SUBDIR is set, the files will be shipped to $(DIST)/$(DIST_SUBDIR) -# Otherwise, the default $(DIST)/bin will be used. -FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(if $(DIST_SUBDIR),$(DIST)/bin/$(DIST_SUBDIR),$(DIST)/bin)) +# instead of $(DIST)/bin. In both cases, if DIST_SUBDIR is set, the files will be +# shipped to a $(DIST_SUBDIR) subdirectory. +FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%) ifdef XPI_NAME DEFINES += -DXPI_NAME=$(XPI_NAME) diff --git a/js/src/config/makefiles/autotargets.mk b/js/src/config/makefiles/autotargets.mk index 2c118a083516..e401ef288387 100644 --- a/js/src/config/makefiles/autotargets.mk +++ b/js/src/config/makefiles/autotargets.mk @@ -56,18 +56,18 @@ mkdir_deps =$(foreach dir,$(getargv),$(call slash_strip,$(dir)/.mkdir.done)) ####################### %/.mkdir.done: # mkdir -p -p => mkdir -p - $(subst $(space)-p,$(null),$(MKDIR)) -p $(dir $@) + $(subst $(space)-p,$(null),$(MKDIR)) -p "$(dir $@)" # Make the timestamp old enough for not being a problem with symbolic links # targets depending on it. Use Jan 3, 1980 to accomodate any timezone where # 198001010000 would translate to something older than FAT epoch. - @touch -t 198001030000 $@ + @$(TOUCH) -t 198001030000 "$@" # A handful of makefiles are attempting "mkdir dot". Likely not intended # or stale logic so add a stub target to handle the request and warn for now. .mkdir.done: ifndef NOWARN_AUTOTARGETS # { @echo "WARNING: $(MKDIR) -dot- requested by $(MAKE) -C $(CURDIR) $(MAKECMDGOALS)" - @touch -t 198001030000 $@ + @$(TOUCH) -t 198001030000 $@ endif #} INCLUDED_AUTOTARGETS_MK = 1 diff --git a/js/src/config/rules.mk b/js/src/config/rules.mk index 96706e5178cb..f55fb4fa4b7c 100644 --- a/js/src/config/rules.mk +++ b/js/src/config/rules.mk @@ -1544,7 +1544,7 @@ define install_file_template $(or $(3),libs):: $(2)/$(notdir $(1)) $(call install_cmd_override,$(2)/$(notdir $(1))) $(2)/$(notdir $(1)): $(1) $$(call mkdir_deps,$(2)) - $$(call install_cmd,$(4) $$< $${@D}) + $$(call install_cmd,$(4) "$$<" "$${@D}") endef $(foreach category,$(INSTALL_TARGETS),\ $(if $($(category)_DEST),,$(error Missing $(category)_DEST))\ @@ -1591,8 +1591,8 @@ $(foreach category,$(INSTALL_TARGETS),\ # makefile_target, extra_flags) define preprocess_file_template $(2): $(1) $$(call mkdir_deps,$(dir $(2))) $$(GLOBAL_DEPS) - $$(RM) $$@ - $$(PYTHON) $$(topsrcdir)/config/Preprocessor.py $(4) $$(DEFINES) $$(ACDEFINES) $$(XULPPFLAGS) $$< > $$@ + $$(RM) "$$@" + $$(PYTHON) $$(topsrcdir)/config/Preprocessor.py $(4) $$(DEFINES) $$(ACDEFINES) $$(XULPPFLAGS) "$$<" > "$$@" $(3):: $(2) endef diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index 4b2276a996be..2ed82c8487fa 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -833,8 +833,6 @@ CodeGenerator::visitCallGeneric(LCallGeneric *call) // Known-target case is handled by LCallKnown. JS_ASSERT(!call->hasSingleTarget()); - // Unknown constructor case is handled by LCallConstructor. - JS_ASSERT(!call->mir()->isConstructing()); // Generate an ArgumentsRectifier. IonCompartment *ion = gen->ionCompartment(); @@ -905,6 +903,16 @@ CodeGenerator::visitCallGeneric(LCallGeneric *call) return false; masm.bind(&end); + + // If the return value of the constructing function is Primitive, + // replace the return value with the Object from CreateThis. + if (call->mir()->isConstructing()) { + Label notPrimitive; + masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, ¬Primitive); + masm.loadValue(Address(StackPointer, unusedStack), JSReturnOperand); + masm.bind(¬Primitive); + } + dropArguments(call->numStackArgs() + 1); return true; } @@ -926,6 +934,10 @@ CodeGenerator::visitCallKnown(LCallKnown *call) masm.checkStackAlignment(); + // Make sure the function has a JSScript + if (target->isInterpretedLazy() && !target->getOrCreateScript(cx)) + return false; + // If the function is known to be uncompilable, only emit the call to InvokeFunction. ExecutionMode executionMode = gen->info().executionMode(); RootedScript targetScript(cx, target->nonLazyScript()); @@ -995,39 +1007,6 @@ CodeGenerator::visitCallKnown(LCallKnown *call) return true; } -typedef bool (*InvokeConstructorFn)(JSContext *, JSObject *, uint32_t, Value *, Value *); -static const VMFunction InvokeConstructorInfo = - FunctionInfo(ion::InvokeConstructor); - -bool -CodeGenerator::visitCallConstructor(LCallConstructor *call) -{ - JS_ASSERT(call->mir()->isConstructing()); - - // Holds the function object. - const LAllocation *callee = call->getFunction(); - Register calleereg = ToRegister(callee); - - uint32_t callargslot = call->argslot(); - uint32_t unusedStack = StackOffsetOfPassedArg(callargslot); - - // Nestle %esp up to the argument vector. - masm.freeStack(unusedStack); - - pushArg(StackPointer); // argv. - pushArg(Imm32(call->numActualArgs())); // argc. - pushArg(calleereg); // JSFunction *. - - if (!callVM(InvokeConstructorInfo, call)) - return false; - - // Un-nestle %esp from the argument vector. No prefix was pushed. - masm.reserveStack(unusedStack); - - dropArguments(call->numStackArgs() + 1); - return true; -} - bool CodeGenerator::emitCallInvokeFunction(LApplyArgsGeneric *apply, Register extraStackSize) { @@ -1842,10 +1821,8 @@ static const VMFunction NewGCThingInfo = FunctionInfo(js::ion::NewGCThing); bool -CodeGenerator::visitCreateThis(LCreateThis *lir) +CodeGenerator::visitCreateThisWithTemplate(LCreateThisWithTemplate *lir) { - JS_ASSERT(lir->mir()->hasTemplateObject()); - JSObject *templateObject = lir->mir()->getTemplateObject(); gc::AllocKind allocKind = templateObject->getAllocKind(); int thingSize = (int)gc::Arena::thingSize(allocKind); @@ -1872,12 +1849,10 @@ static const VMFunction CreateThisInfo = FunctionInfo(js_CreateThisForFunctionWithProto); bool -CodeGenerator::visitCreateThisVM(LCreateThisVM *lir) +CodeGenerator::emitCreateThisVM(LInstruction *lir, + const LAllocation *proto, + const LAllocation *callee) { - const LAllocation *proto = lir->getPrototype(); - const LAllocation *callee = lir->getCallee(); - - // Push arguments. if (proto->isConstant()) pushArg(ImmGCPtr(&proto->toConstant()->toObject())); else @@ -1888,11 +1863,40 @@ CodeGenerator::visitCreateThisVM(LCreateThisVM *lir) else pushArg(ToRegister(callee)); - if (!callVM(CreateThisInfo, lir)) + return callVM(CreateThisInfo, lir); +} + +bool +CodeGenerator::visitCreateThisV(LCreateThisV *lir) +{ + Label done, vm; + + const LAllocation *proto = lir->getPrototype(); + const LAllocation *callee = lir->getCallee(); + + // When callee could be a native, put MagicValue in return operand. + // Use the VMCall when callee turns out to not be a native. + masm.branchIfInterpreted(ToRegister(callee), &vm); + masm.moveValue(MagicValue(JS_IS_CONSTRUCTING), GetValueOutput(lir)); + masm.jump(&done); + + masm.bind(&vm); + if (!emitCreateThisVM(lir, proto, callee)) return false; + + masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, GetValueOutput(lir)); + + masm.bind(&done); + return true; } +bool +CodeGenerator::visitCreateThisO(LCreateThisO *lir) +{ + return emitCreateThisVM(lir, lir->getPrototype(), lir->getCallee()); +} + bool CodeGenerator::visitReturnFromCtor(LReturnFromCtor *lir) { diff --git a/js/src/ion/CodeGenerator.h b/js/src/ion/CodeGenerator.h index 67c6527d1585..ed4d595afce8 100644 --- a/js/src/ion/CodeGenerator.h +++ b/js/src/ion/CodeGenerator.h @@ -79,7 +79,6 @@ class CodeGenerator : public CodeGeneratorSpecific uint32_t argc, uint32_t unusedStack); bool visitCallGeneric(LCallGeneric *call); bool visitCallKnown(LCallKnown *call); - bool visitCallConstructor(LCallConstructor *call); bool emitCallInvokeFunction(LApplyArgsGeneric *apply, Register extraStackSize); void emitPushArguments(LApplyArgsGeneric *apply, Register extraStackSpace); void emitPopArguments(LApplyArgsGeneric *apply, Register extraStackSize); @@ -96,8 +95,10 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitNewCallObject(LNewCallObject *lir); bool visitNewStringObject(LNewStringObject *lir); bool visitInitProp(LInitProp *lir); - bool visitCreateThis(LCreateThis *lir); - bool visitCreateThisVM(LCreateThisVM *lir); + bool emitCreateThisVM(LInstruction *lir, const LAllocation *proto, const LAllocation *callee); + bool visitCreateThisV(LCreateThisV *lir); + bool visitCreateThisO(LCreateThisO *lir); + bool visitCreateThisWithTemplate(LCreateThisWithTemplate *lir); bool visitReturnFromCtor(LReturnFromCtor *lir); bool visitArrayLength(LArrayLength *lir); bool visitTypedArrayLength(LTypedArrayLength *lir); diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index 78487087f07d..1cc7db6e0b32 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -62,7 +62,7 @@ IonBuilder::abort(const char *message, ...) va_start(ap, message); abortFmt(message, ap); va_end(ap); - IonSpew(IonSpew_Abort, "aborted @ %s:%d", script_->filename, PCToLineNumber(script_, pc)); + IonSpew(IonSpew_Abort, "aborted @ %s:%d", script()->filename, PCToLineNumber(script(), pc)); #endif return false; } @@ -72,7 +72,7 @@ IonBuilder::spew(const char *message) { // Don't call PCToLineNumber in release builds. #ifdef DEBUG - IonSpew(IonSpew_MIR, "%s @ %s:%d", message, script_->filename, PCToLineNumber(script_, pc)); + IonSpew(IonSpew_MIR, "%s @ %s:%d", message, script()->filename, PCToLineNumber(script(), pc)); #endif } @@ -168,7 +168,7 @@ uint32_t IonBuilder::getPolyCallTargets(uint32_t argc, jsbytecode *pc, AutoObjectVector &targets, uint32_t maxTargets) { - types::TypeSet *calleeTypes = oracle->getCallTarget(script_, argc, pc); + types::TypeSet *calleeTypes = oracle->getCallTarget(script(), argc, pc); if (!calleeTypes) return 0; @@ -200,7 +200,7 @@ IonBuilder::canInlineTarget(JSFunction *target) return false; } - if (target->getParent() != &script_->global()) { + if (target->getParent() != &script()->global()) { IonSpew(IonSpew_Inlining, "Cannot inline due to scope mismatch"); return false; } @@ -225,7 +225,7 @@ IonBuilder::canInlineTarget(JSFunction *target) bool canInline = oracle->canEnterInlinedFunction(target); if (!canInline) { - IonSpew(IonSpew_Inlining, "Cannot inline due to oracle veto %d", script_->lineno); + IonSpew(IonSpew_Inlining, "Cannot inline due to oracle veto %d", script()->lineno); return false; } @@ -274,10 +274,10 @@ IonBuilder::build() return false; IonSpew(IonSpew_Scripts, "Analyzing script %s:%d (%p) (usecount=%d) (maxloopcount=%d)", - script_->filename, script_->lineno, (void *)script_, (int)script_->getUseCount(), - (int)script_->getMaxLoopCount()); + script()->filename, script()->lineno, (void *)script(), (int)script()->getUseCount(), + (int)script()->getMaxLoopCount()); - if (!graph().addScript(script_)) + if (!graph().addScript(script())) return false; if (!initParameters()) @@ -303,7 +303,7 @@ IonBuilder::build() // Emit the start instruction, so we can begin real instructions. current->makeStart(MStart::New(MStart::StartType_Default)); if (instrumentedProfiling()) - current->add(MFunctionBoundary::New(script_, MFunctionBoundary::Enter)); + current->add(MFunctionBoundary::New(script(), MFunctionBoundary::Enter)); // Parameters have been checked to correspond to the typeset, now we unbox // what we can in an infallible manner. @@ -346,7 +346,7 @@ IonBuilder::build() // Recompile to inline calls if this function is hot. insertRecompileCheck(); - if (script_->argumentsHasVarBinding()) { + if (script()->argumentsHasVarBinding()) { lazyArguments_ = MConstant::New(MagicValue(JS_OPTIMIZED_ARGUMENTS)); current->add(lazyArguments_); } @@ -400,9 +400,9 @@ IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoi MDefinition *thisDefn, MDefinitionVector &argv) { IonSpew(IonSpew_Scripts, "Inlining script %s:%d (%p)", - script_->filename, script_->lineno, (void *)script_); + script()->filename, script()->lineno, (void *)script()); - if (!graph().addScript(script_)) + if (!graph().addScript(script())) return false; callerBuilder_ = callerBuilder; @@ -430,7 +430,7 @@ IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoi // keep track of the inlining depth because all scripts inlined on the same // level contiguously have only one Inline_Exit node. if (instrumentedProfiling()) - predecessor->add(MFunctionBoundary::New(script_, + predecessor->add(MFunctionBoundary::New(script(), MFunctionBoundary::Inline_Enter, inliningDepth)); @@ -454,7 +454,7 @@ IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoi } // The Oracle ensures that the inlined script does not use the scope chain. - JS_ASSERT(!script_->analysis()->usesScopeChain()); + JS_ASSERT(!script()->analysis()->usesScopeChain()); MInstruction *scope = MConstant::New(UndefinedValue()); current->add(scope); current->initSlot(info().scopeChainSlot(), scope); @@ -506,9 +506,9 @@ IonBuilder::rewriteParameters() // will be adding constraints to it. types::StackTypeSet *types; if (param->index() == MParameter::THIS_SLOT) - types = oracle->thisTypeSet(script_); + types = oracle->thisTypeSet(script()); else - types = oracle->parameterTypeSet(script_, param->index()); + types = oracle->parameterTypeSet(script(), param->index()); if (!types) continue; @@ -554,12 +554,12 @@ IonBuilder::initParameters() return true; MParameter *param = MParameter::New(MParameter::THIS_SLOT, - cloneTypeSet(oracle->thisTypeSet(script_))); + cloneTypeSet(oracle->thisTypeSet(script()))); current->add(param); current->initSlot(info().thisSlot(), param); for (uint32_t i = 0; i < info().nargs(); i++) { - param = MParameter::New(i, cloneTypeSet(oracle->parameterTypeSet(script_, i))); + param = MParameter::New(i, cloneTypeSet(oracle->parameterTypeSet(script(), i))); current->add(param); current->initSlot(info().argSlot(i), param); } @@ -574,14 +574,14 @@ IonBuilder::initScopeChain() // If the script doesn't use the scopechain, then it's already initialized // from earlier. - if (!script_->analysis()->usesScopeChain()) + if (!script()->analysis()->usesScopeChain()) return true; // The scope chain is only tracked in scripts that have NAME opcodes which // will try to access the scope. For other scripts, the scope instructions // will be held live by resume points and code will still be generated for // them, so just use a constant undefined value. - if (!script_->compileAndGo) + if (!script()->compileAndGo) return abort("non-CNG global scripts are not supported"); if (JSFunction *fun = info().fun()) { @@ -604,7 +604,7 @@ IonBuilder::initScopeChain() return false; } } else { - scope = MConstant::New(ObjectValue(script_->global())); + scope = MConstant::New(ObjectValue(script()->global())); current->add(scope); } @@ -957,7 +957,7 @@ IonBuilder::inspectOpcode(JSOp op) } case JSOP_BINDGNAME: - return pushConstant(ObjectValue(script_->global())); + return pushConstant(ObjectValue(script()->global())); case JSOP_SETGNAME: { @@ -2301,7 +2301,7 @@ IonBuilder::lookupSwitch(JSOp op, jssrcnote *sn) bool prevShared = false; jsbytecode *prevpc = NULL; for (unsigned int i = 0; i < ncases; i++) { - Value rval = script_->getConst(GET_UINT32_INDEX(pc2)); + Value rval = script()->getConst(GET_UINT32_INDEX(pc2)); pc2 += UINT32_INDEX_LEN; jsbytecode *casepc = pc + GET_JUMP_OFFSET(pc2); pc2 += JUMP_OFFSET_LEN; @@ -2867,7 +2867,7 @@ IonBuilder::processReturn(JSOp op) } if (instrumentedProfiling()) - current->add(MFunctionBoundary::New(script_, MFunctionBoundary::Exit)); + current->add(MFunctionBoundary::New(script(), MFunctionBoundary::Exit)); MReturn *ret = MReturn::New(def); current->end(ret); @@ -2911,7 +2911,7 @@ IonBuilder::jsop_bitnot() MBitNot *ins = MBitNot::New(input); current->add(ins); - ins->infer(oracle->unaryTypes(script_, pc)); + ins->infer(oracle->unaryTypes(script(), pc)); current->push(ins); if (ins->isEffectful() && !resumeAfter(ins)) @@ -2957,7 +2957,7 @@ IonBuilder::jsop_bitop(JSOp op) } current->add(ins); - ins->infer(oracle->binaryTypes(script_, pc)); + ins->infer(oracle->binaryTypes(script(), pc)); current->push(ins); if (ins->isEffectful() && !resumeAfter(ins)) @@ -2969,7 +2969,7 @@ IonBuilder::jsop_bitop(JSOp op) bool IonBuilder::jsop_binary(JSOp op, MDefinition *left, MDefinition *right) { - TypeOracle::Binary b = oracle->binaryOp(script_, pc); + TypeOracle::Binary b = oracle->binaryOp(script(), pc); if (op == JSOP_ADD && b.rval == MIRType_String && (b.lhs == MIRType_String || b.lhs == MIRType_Int32) && @@ -3008,7 +3008,7 @@ IonBuilder::jsop_binary(JSOp op, MDefinition *left, MDefinition *right) return false; } - TypeOracle::BinaryTypes types = oracle->binaryTypes(script_, pc); + TypeOracle::BinaryTypes types = oracle->binaryTypes(script(), pc); current->add(ins); ins->infer(cx, types); current->push(ins); @@ -3030,7 +3030,7 @@ IonBuilder::jsop_binary(JSOp op) bool IonBuilder::jsop_pos() { - TypeOracle::Unary types = oracle->unaryOp(script_, pc); + TypeOracle::Unary types = oracle->unaryOp(script(), pc); if (IsNumberType(types.ival)) { // Already int32 or double. JS_ASSERT(IsNumberType(types.rval)); @@ -3204,31 +3204,31 @@ IonBuilder::makeInliningDecision(AutoObjectVector &targets, uint32_t argc) // 3. Do not inline functions which are not called as frequently as their // callers. - uint32_t callerUses = script_->getUseCount(); + uint32_t callerUses = script()->getUseCount(); uint32_t totalSize = 0; uint32_t checkUses = js_IonOptions.usesBeforeInlining; bool allFunctionsAreSmall = true; RootedFunction target(cx); - RootedScript script(cx); + RootedScript targetScript(cx); for (size_t i = 0; i < targets.length(); i++) { target = targets[i]->toFunction(); if (!target->isInterpreted()) return false; - script = target->nonLazyScript(); - uint32_t calleeUses = script->getUseCount(); + targetScript = target->nonLazyScript(); + uint32_t calleeUses = targetScript->getUseCount(); if (target->nargs < argc) { IonSpew(IonSpew_Inlining, "Not inlining, overflow of arguments."); return false; } - totalSize += script->length; + totalSize += targetScript->length; if (totalSize > js_IonOptions.inlineMaxTotalBytecodeLength) return false; - if (script->length > js_IonOptions.smallFunctionMaxBytecodeLength) + if (targetScript->length > js_IonOptions.smallFunctionMaxBytecodeLength) allFunctionsAreSmall = false; if (calleeUses * js_IonOptions.inlineUseCountRatio < callerUses) { @@ -3239,12 +3239,13 @@ IonBuilder::makeInliningDecision(AutoObjectVector &targets, uint32_t argc) if (allFunctionsAreSmall) checkUses = js_IonOptions.smallFunctionUsesBeforeInlining; - if (script_->getUseCount() < checkUses) { + if (script()->getUseCount() < checkUses) { IonSpew(IonSpew_Inlining, "Not inlining, caller is not hot"); return false; } - if (!oracle->canInlineCall(script_, pc)) { + RootedScript scriptRoot(cx, script()); + if (!oracle->canInlineCall(scriptRoot, pc)) { IonSpew(IonSpew_Inlining, "Cannot inline due to uninlineable call site"); return false; } @@ -3778,7 +3779,8 @@ IonBuilder::createCallObject(MDefinition *callee, MDefinition *scope) // Create a template CallObject that we'll use to generate inline object // creation. - RootedObject templateObj(cx, CallObject::createTemplateObject(cx, script_)); + RootedScript scriptRoot(cx, script()); + RootedObject templateObj(cx, CallObject::createTemplateObject(cx, scriptRoot)); if (!templateObj) return NULL; @@ -3804,7 +3806,7 @@ IonBuilder::createCallObject(MDefinition *callee, MDefinition *scope) current->add(MStoreFixedSlot::New(callObj, CallObject::calleeSlot(), callee)); // Initialize argument slots. - for (AliasedFormalIter i(script_); i; i++) { + for (AliasedFormalIter i(script()); i; i++) { unsigned slot = i.scopeSlot(); unsigned formal = i.frameIndex(); MDefinition *param = current->getSlot(info().argSlot(formal)); @@ -3853,7 +3855,7 @@ IonBuilder::createThisScripted(MDefinition *callee) current->add(getProto); // Create this from prototype - MCreateThis *createThis = MCreateThis::New(callee, getProto, NULL); + MCreateThis *createThis = MCreateThis::New(callee, getProto); current->add(createThis); return createThis; @@ -3894,10 +3896,7 @@ IonBuilder::createThisScriptedSingleton(HandleFunction target, HandleObject prot if (templateObject->type()->newScript) types::HeapTypeSet::WatchObjectStateChange(cx, templateObject->type()); - MConstant *protoDef = MConstant::New(ObjectValue(*proto)); - current->add(protoDef); - - MCreateThis *createThis = MCreateThis::New(callee, protoDef, templateObject); + MCreateThisWithTemplate *createThis = MCreateThisWithTemplate::New(templateObject); current->add(createThis); return createThis; @@ -3906,22 +3905,35 @@ IonBuilder::createThisScriptedSingleton(HandleFunction target, HandleObject prot MDefinition * IonBuilder::createThis(HandleFunction target, MDefinition *callee) { + // Create this for unknown target + if (!target) + return createThisScripted(callee); + + // Create this for native function if (target->isNative()) { if (!target->isNativeConstructor()) return NULL; return createThisNative(); } - MDefinition *createThis = NULL; + // Create this with known prototype. RootedObject proto(cx, getSingletonPrototype(target)); // Try baking in the prototype. - if (proto) - createThis = createThisScriptedSingleton(target, proto, callee); + if (proto) { + MDefinition *createThis = createThisScriptedSingleton(target, proto, callee); + if (createThis) + return createThis; + } - // If the prototype could not be hardcoded, emit a GETPROP. + MDefinition *createThis = createThisScripted(callee); if (!createThis) - createThis = createThisScripted(callee); + return NULL; + + // The native function case is already handled upfront. + // Here we can safely remove the native check for MCreateThis. + JS_ASSERT(createThis->isCreateThis()); + createThis->toCreateThis()->removeNativeCheck(); return createThis; } @@ -3942,7 +3954,7 @@ IonBuilder::jsop_funcall(uint32_t argc) return makeCall(native, argc, false); // Extract call target. - types::StackTypeSet *funTypes = oracle->getCallArg(script_, argc, 0, pc); + types::StackTypeSet *funTypes = oracle->getCallArg(script(), argc, 0, pc); RootedObject funobj(cx, (funTypes) ? funTypes->getSingleton() : NULL); RootedFunction target(cx, (funobj && funobj->isFunction()) ? funobj->toFunction() : NULL); @@ -3984,7 +3996,7 @@ IonBuilder::jsop_funapply(uint32_t argc) // Disable compilation if the second argument to |apply| cannot be guaranteed // to be either definitely |arguments| or definitely not |arguments|. - types::StackTypeSet *argObjTypes = oracle->getCallArg(script_, argc, 2, pc); + types::StackTypeSet *argObjTypes = oracle->getCallArg(script(), argc, 2, pc); LazyArgumentsType isArgObj = oracle->isArgumentObject(argObjTypes); if (isArgObj == MaybeArguments) return abort("fun.apply with MaybeArguments"); @@ -4007,7 +4019,7 @@ IonBuilder::jsop_funapply(uint32_t argc) // argc+2: The native 'apply' function. // Extract call target. - types::StackTypeSet *funTypes = oracle->getCallArg(script_, argc, 0, pc); + types::StackTypeSet *funTypes = oracle->getCallArg(script(), argc, 0, pc); RootedObject funobj(cx, (funTypes) ? funTypes->getSingleton() : NULL); RootedFunction target(cx, (funobj && funobj->isFunction()) ? funobj->toFunction() : NULL); @@ -4041,7 +4053,7 @@ IonBuilder::jsop_funapply(uint32_t argc) return false; types::StackTypeSet *barrier; - types::StackTypeSet *types = oracle->returnTypeSet(script_, pc, &barrier); + types::StackTypeSet *types = oracle->returnTypeSet(script(), pc, &barrier); return pushTypeBarrier(apply, types, barrier); } @@ -4054,7 +4066,7 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing) AutoObjectVector targets(cx); uint32_t numTargets = getPolyCallTargets(argc, pc, targets, 4); types::StackTypeSet *barrier; - types::StackTypeSet *types = oracle->returnTypeSet(script_, pc, &barrier); + types::StackTypeSet *types = oracle->returnTypeSet(script(), pc, &barrier); // Attempt to inline native and scripted functions. if (inliningEnabled()) { @@ -4124,8 +4136,8 @@ IonBuilder::makeCallHelper(HandleFunction target, uint32_t argc, bool constructi MPassArg *thisArg = current->pop()->toPassArg(); - // If the target is known, inline the constructor on the caller-side. - if (constructing && target) { + // Inline the constructor on the caller-side. + if (constructing) { MDefinition *callee = current->peek(-1); MDefinition *create = createThis(target, callee); if (!create) { @@ -4173,7 +4185,7 @@ bool IonBuilder::makeCall(HandleFunction target, uint32_t argc, bool constructing) { types::StackTypeSet *barrier; - types::StackTypeSet *types = oracle->returnTypeSet(script_, pc, &barrier); + types::StackTypeSet *types = oracle->returnTypeSet(script(), pc, &barrier); return makeCallBarrier(target, argc, constructing, types, barrier); } @@ -4187,7 +4199,8 @@ IonBuilder::jsop_compare(JSOp op) current->add(ins); current->push(ins); - ins->infer(cx, oracle->binaryTypes(script_, pc)); + TypeOracle::BinaryTypes b = oracle->binaryTypes(script(), pc); + ins->infer(cx, b); if (ins->isEffectful() && !resumeAfter(ins)) return false; @@ -4201,12 +4214,12 @@ IonBuilder::getNewArrayTemplateObject(uint32_t count) if (!templateObject) return NULL; - RootedScript script(cx, script_); - if (types::UseNewTypeForInitializer(cx, script, pc, JSProto_Array)) { + RootedScript scriptRoot(cx, script()); + if (types::UseNewTypeForInitializer(cx, scriptRoot, pc, JSProto_Array)) { if (!JSObject::setSingletonType(cx, templateObject)) return NULL; } else { - types::TypeObject *type = types::TypeScript::InitObject(cx, script, pc, JSProto_Array); + types::TypeObject *type = types::TypeScript::InitObject(cx, scriptRoot, pc, JSProto_Array); if (!type) return NULL; templateObject->setType(type); @@ -4218,7 +4231,7 @@ IonBuilder::getNewArrayTemplateObject(uint32_t count) bool IonBuilder::jsop_newarray(uint32_t count) { - JS_ASSERT(script_->compileAndGo); + JS_ASSERT(script()->compileAndGo); JSObject *templateObject = getNewArrayTemplateObject(count); if (!templateObject) @@ -4236,7 +4249,7 @@ bool IonBuilder::jsop_newobject(HandleObject baseObj) { // Don't bake in the TypeObject for non-CNG scripts. - JS_ASSERT(script_->compileAndGo); + JS_ASSERT(script()->compileAndGo); RootedObject templateObject(cx); @@ -4250,12 +4263,12 @@ IonBuilder::jsop_newobject(HandleObject baseObj) if (!templateObject) return false; - RootedScript script(cx, script_); - if (types::UseNewTypeForInitializer(cx, script, pc, JSProto_Object)) { + RootedScript scriptRoot(cx, script()); + if (types::UseNewTypeForInitializer(cx, scriptRoot, pc, JSProto_Object)) { if (!JSObject::setSingletonType(cx, templateObject)) return false; } else { - types::TypeObject *type = types::TypeScript::InitObject(cx, script, pc, JSProto_Object); + types::TypeObject *type = types::TypeScript::InitObject(cx, scriptRoot, pc, JSProto_Object); if (!type) return false; templateObject->setType(type); @@ -4272,8 +4285,8 @@ IonBuilder::jsop_newobject(HandleObject baseObj) bool IonBuilder::jsop_initelem() { - if (oracle->propertyWriteCanSpecialize(script_, pc)) { - if (oracle->elementWriteIsDenseArray(script_, pc)) + if (oracle->propertyWriteCanSpecialize(script(), pc)) { + if (oracle->elementWriteIsDenseArray(script(), pc)) return jsop_initelem_dense(); } @@ -4326,7 +4339,7 @@ IonBuilder::jsop_initprop(HandlePropertyName name) RootedObject templateObject(cx, obj->toNewObject()->templateObject()); - if (!oracle->propertyWriteCanSpecialize(script_, pc)) { + if (!oracle->propertyWriteCanSpecialize(script(), pc)) { // This should only happen for a few names like __proto__. return abort("INITPROP Monitored initprop"); } @@ -4350,7 +4363,7 @@ IonBuilder::jsop_initprop(HandlePropertyName name) } bool needsBarrier = true; - TypeOracle::BinaryTypes b = oracle->binaryTypes(script_, pc); + TypeOracle::BinaryTypes b = oracle->binaryTypes(script(), pc); if (b.lhsTypes && ((jsid)id == types::MakeTypeId(cx, id)) && !b.lhsTypes->propertyNeedsBarrier(cx, id)) @@ -4674,7 +4687,7 @@ IonBuilder::insertRecompileCheck() return; // Don't recompile if we are already inlining. - if (script_->getUseCount() >= js_IonOptions.usesBeforeInlining) + if (script()->getUseCount() >= js_IonOptions.usesBeforeInlining) return; // Don't recompile if the oracle cannot provide inlining information @@ -4682,7 +4695,7 @@ IonBuilder::insertRecompileCheck() if (!oracle->canInlineCalls()) return; - uint32_t minUses = UsesBeforeIonRecompile(script_, pc); + uint32_t minUses = UsesBeforeIonRecompile(script(), pc); MRecompileCheck *check = MRecompileCheck::New(minUses); current->add(check); } @@ -4950,7 +4963,7 @@ IonBuilder::jsop_getgname(HandlePropertyName name) if (name == cx->names().Infinity) return pushConstant(cx->runtime->positiveInfinityValue); - RootedObject globalObj(cx, &script_->global()); + RootedObject globalObj(cx, &script()->global()); JS_ASSERT(globalObj->isNative()); RootedId id(cx, NameToId(name)); @@ -4961,7 +4974,7 @@ IonBuilder::jsop_getgname(HandlePropertyName name) if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) return jsop_getname(name); - types::HeapTypeSet *propertyTypes = oracle->globalPropertyTypeSet(script_, pc, id); + types::HeapTypeSet *propertyTypes = oracle->globalPropertyTypeSet(script(), pc, id); if (propertyTypes && propertyTypes->isOwnProperty(cx, globalObj->getType(cx), true)) { // The property has been reconfigured as non-configurable, non-enumerable // or non-writable. @@ -4971,8 +4984,9 @@ IonBuilder::jsop_getgname(HandlePropertyName name) // If the property is permanent, a shape guard isn't necessary. JSValueType knownType = JSVAL_TYPE_UNKNOWN; - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); if (types) { JSObject *singleton = types->getSingleton(); @@ -5019,13 +5033,13 @@ IonBuilder::jsop_getgname(HandlePropertyName name) bool IonBuilder::jsop_setgname(HandlePropertyName name) { - RootedObject globalObj(cx, &script_->global()); + RootedObject globalObj(cx, &script()->global()); RootedId id(cx, NameToId(name)); JS_ASSERT(globalObj->isNative()); bool canSpecialize; - types::HeapTypeSet *propertyTypes = oracle->globalPropertyWrite(script_, pc, id, &canSpecialize); + types::HeapTypeSet *propertyTypes = oracle->globalPropertyWrite(script(), pc, id, &canSpecialize); // This should only happen for a few names like __proto__. if (!canSpecialize || globalObj->watched()) @@ -5090,7 +5104,7 @@ IonBuilder::jsop_getname(HandlePropertyName name) { MDefinition *object; if (js_CodeSpec[*pc].format & JOF_GNAME) { - MInstruction *global = MConstant::New(ObjectValue(script_->global())); + MInstruction *global = MConstant::New(ObjectValue(script()->global())); current->add(global); object = global; } else { @@ -5110,8 +5124,9 @@ IonBuilder::jsop_getname(HandlePropertyName name) if (!resumeAfter(ins)) return false; - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); monitorResult(ins, barrier, types); return pushTypeBarrier(ins, types, barrier); @@ -5120,7 +5135,7 @@ IonBuilder::jsop_getname(HandlePropertyName name) bool IonBuilder::jsop_intrinsicname(HandlePropertyName name) { - types::StackTypeSet *types = oracle->propertyRead(script_, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); JSValueType type = types->getKnownTypeTag(); // If we haven't executed this opcode yet, we need to get the intrinsic @@ -5134,7 +5149,8 @@ IonBuilder::jsop_intrinsicname(HandlePropertyName name) if (!resumeAfter(ins)) return false; - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); monitorResult(ins, barrier, types); return pushTypeBarrier(ins, types, barrier); } @@ -5156,10 +5172,10 @@ IonBuilder::jsop_intrinsicname(HandlePropertyName name) bool IonBuilder::jsop_bindname(PropertyName *name) { - JS_ASSERT(script_->analysis()->usesScopeChain()); + JS_ASSERT(script()->analysis()->usesScopeChain()); MDefinition *scopeChain = current->scopeChain(); - MBindNameCache *ins = MBindNameCache::New(scopeChain, name, script_, pc); + MBindNameCache *ins = MBindNameCache::New(scopeChain, name, script(), pc); current->add(ins); current->push(ins); @@ -5170,17 +5186,17 @@ IonBuilder::jsop_bindname(PropertyName *name) bool IonBuilder::jsop_getelem() { - if (oracle->elementReadIsDenseArray(script_, pc)) + if (oracle->elementReadIsDenseArray(script(), pc)) return jsop_getelem_dense(); int arrayType = TypedArray::TYPE_MAX; - if (oracle->elementReadIsTypedArray(script_, pc, &arrayType)) + if (oracle->elementReadIsTypedArray(script(), pc, &arrayType)) return jsop_getelem_typed(arrayType); - if (oracle->elementReadIsString(script_, pc)) + if (oracle->elementReadIsString(script(), pc)) return jsop_getelem_string(); - LazyArgumentsType isArguments = oracle->elementReadMagicArguments(script_, pc); + LazyArgumentsType isArguments = oracle->elementReadMagicArguments(script(), pc); if (isArguments == MaybeArguments) return abort("Type is not definitely lazy arguments."); if (isArguments == DefinitelyArguments) @@ -5198,7 +5214,7 @@ IonBuilder::jsop_getelem() bool mustMonitorResult = false; bool cacheable = false; - oracle->elementReadGeneric(script_, pc, &cacheable, &mustMonitorResult); + oracle->elementReadGeneric(script(), pc, &cacheable, &mustMonitorResult); if (cacheable) ins = MGetElementCache::New(lhs, rhs, mustMonitorResult); @@ -5211,8 +5227,9 @@ IonBuilder::jsop_getelem() if (!resumeAfter(ins)) return false; - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); if (mustMonitorResult) monitorResult(ins, barrier, types); @@ -5225,9 +5242,10 @@ IonBuilder::jsop_getelem_dense() if (oracle->arrayPrototypeHasIndexedProperty()) return abort("GETELEM Array proto has indexed properties"); - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); - bool needsHoleCheck = !oracle->elementReadIsPacked(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); + bool needsHoleCheck = !oracle->elementReadIsPacked(script(), pc); bool maybeUndefined = types->hasType(types::Type::UndefinedType()); MDefinition *id = current->pop(); @@ -5321,8 +5339,9 @@ GetTypedArrayElements(MDefinition *obj) bool IonBuilder::jsop_getelem_typed(int arrayType) { - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); MDefinition *id = current->pop(); MDefinition *obj = current->pop(); @@ -5419,7 +5438,7 @@ IonBuilder::jsop_getelem_string() // This will cause an invalidation of this script once the 'undefined' type // is monitored by the interpreter. - JS_ASSERT(oracle->propertyRead(script_, pc)->getKnownTypeTag() == JSVAL_TYPE_STRING); + JS_ASSERT(oracle->propertyRead(script(), pc)->getKnownTypeTag() == JSVAL_TYPE_STRING); id = addBoundsCheck(id, length); MCharCodeAt *charCode = MCharCodeAt::New(str, id); @@ -5434,16 +5453,16 @@ IonBuilder::jsop_getelem_string() bool IonBuilder::jsop_setelem() { - if (oracle->propertyWriteCanSpecialize(script_, pc)) { - if (oracle->elementWriteIsDenseArray(script_, pc)) + if (oracle->propertyWriteCanSpecialize(script(), pc)) { + if (oracle->elementWriteIsDenseArray(script(), pc)) return jsop_setelem_dense(); int arrayType = TypedArray::TYPE_MAX; - if (oracle->elementWriteIsTypedArray(script_, pc, &arrayType)) + if (oracle->elementWriteIsTypedArray(script(), pc, &arrayType)) return jsop_setelem_typed(arrayType); } - LazyArgumentsType isArguments = oracle->elementWriteMagicArguments(script_, pc); + LazyArgumentsType isArguments = oracle->elementWriteMagicArguments(script(), pc); if (isArguments == MaybeArguments) return abort("Type is not definitely lazy arguments."); if (isArguments == DefinitelyArguments) @@ -5466,8 +5485,8 @@ IonBuilder::jsop_setelem_dense() if (oracle->arrayPrototypeHasIndexedProperty()) return abort("SETELEM Array proto has indexed properties"); - MIRType elementType = oracle->elementWrite(script_, pc); - bool packed = oracle->elementWriteIsPacked(script_, pc); + MIRType elementType = oracle->elementWrite(script(), pc); + bool packed = oracle->elementWriteIsPacked(script(), pc); MDefinition *value = current->pop(); MDefinition *id = current->pop(); @@ -5486,7 +5505,7 @@ IonBuilder::jsop_setelem_dense() // indexes in the past. Otherwise, use MStoreElement so that we can hoist // the initialized length and bounds check. MStoreElementCommon *store; - if (oracle->setElementHasWrittenHoles(script_, pc)) { + if (oracle->setElementHasWrittenHoles(script(), pc)) { MStoreElementHole *ins = MStoreElementHole::New(obj, elements, id, value); store = ins; @@ -5512,7 +5531,7 @@ IonBuilder::jsop_setelem_dense() } // Determine whether a write barrier is required. - if (oracle->elementWriteNeedsBarrier(script_, pc)) + if (oracle->elementWriteNeedsBarrier(script(), pc)) store->setNeedsBarrier(); if (elementType != MIRType_None && packed) @@ -5572,7 +5591,7 @@ IonBuilder::jsop_length() bool IonBuilder::jsop_length_fastPath() { - TypeOracle::UnaryTypes sig = oracle->unaryTypes(script_, pc); + TypeOracle::UnaryTypes sig = oracle->unaryTypes(script(), pc); if (!sig.inTypes || !sig.outTypes) return false; @@ -5643,8 +5662,9 @@ IonBuilder::jsop_arguments_length() bool IonBuilder::jsop_arguments_getelem() { - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); MDefinition *idx = current->pop(); @@ -6129,8 +6149,9 @@ IonBuilder::loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType) JS_ASSERT(shape->hasDefaultGetter()); JS_ASSERT(shape->hasSlot()); - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); if (shape->slot() < shape->numFixedSlots()) { MLoadFixedSlot *load = MLoadFixedSlot::New(obj, shape->slot()); @@ -6184,10 +6205,11 @@ IonBuilder::jsop_getprop(HandlePropertyName name) { RootedId id(cx, NameToId(name)); - types::StackTypeSet *barrier = oracle->propertyReadBarrier(script_, pc); - types::StackTypeSet *types = oracle->propertyRead(script_, pc); - TypeOracle::Unary unary = oracle->unaryOp(script_, pc); - TypeOracle::UnaryTypes uTypes = oracle->unaryTypes(script_, pc); + RootedScript scriptRoot(cx, script()); + types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc); + types::StackTypeSet *types = oracle->propertyRead(script(), pc); + TypeOracle::Unary unary = oracle->unaryOp(script(), pc); + TypeOracle::UnaryTypes uTypes = oracle->unaryTypes(script(), pc); bool emitted = false; @@ -6231,7 +6253,7 @@ bool IonBuilder::getPropTryArgumentsLength(bool *emitted) { JS_ASSERT(*emitted == false); - LazyArgumentsType isArguments = oracle->propertyReadMagicArguments(script_, pc); + LazyArgumentsType isArguments = oracle->propertyReadMagicArguments(script(), pc); if (isArguments == MaybeArguments) return abort("Type is not definitely lazy arguments."); @@ -6253,7 +6275,7 @@ IonBuilder::getPropTryConstant(bool *emitted, HandleId id, types::StackTypeSet * if (!singleton || barrier) return true; - RootedObject global(cx, &script_->global()); + RootedObject global(cx, &script()->global()); bool isConstant, testObject, testString; if (!TestSingletonPropertyTypes(cx, unaryTypes.inTypes, global, id, @@ -6378,12 +6400,12 @@ IonBuilder::getPropTryMonomorphic(bool *emitted, HandleId id, types::StackTypeSe TypeOracle::Unary unary, TypeOracle::UnaryTypes unaryTypes) { JS_ASSERT(*emitted == false); - bool accessGetter = oracle->propertyReadAccessGetter(script_, pc); + bool accessGetter = oracle->propertyReadAccessGetter(script(), pc); if (unary.ival != MIRType_Object) return true; - Shape *objShape = mjit::GetPICSingleShape(cx, script_, pc, info().constructing()); + Shape *objShape = mjit::GetPICSingleShape(cx, script(), pc, info().constructing()); if (!objShape || objShape->inDictionary()) { spew("GETPROP not monomorphic"); return true; @@ -6418,7 +6440,7 @@ IonBuilder::getPropTryPolymorphic(bool *emitted, HandlePropertyName name, Handle TypeOracle::Unary unary, TypeOracle::UnaryTypes unaryTypes) { JS_ASSERT(*emitted == false); - bool accessGetter = oracle->propertyReadAccessGetter(script_, pc); + bool accessGetter = oracle->propertyReadAccessGetter(script(), pc); // The input value must either be an object, or we should have strong suspicions // that it can be safely unboxed to an object. @@ -6440,7 +6462,8 @@ IonBuilder::getPropTryPolymorphic(bool *emitted, HandlePropertyName name, Handle (cx->methodJitEnabled || js_IonOptions.eagerCompilation) && !invalidatedIdempotentCache()) { - if (oracle->propertyReadIdempotent(script_, pc, id)) + RootedScript scriptRoot(cx, script()); + if (oracle->propertyReadIdempotent(scriptRoot, pc, id)) load->setIdempotent(); } @@ -6475,9 +6498,9 @@ IonBuilder::jsop_setprop(HandlePropertyName name) MDefinition *value = current->pop(); MDefinition *obj = current->pop(); - bool monitored = !oracle->propertyWriteCanSpecialize(script_, pc); + bool monitored = !oracle->propertyWriteCanSpecialize(script(), pc); - TypeOracle::BinaryTypes binaryTypes = oracle->binaryTypes(script_, pc); + TypeOracle::BinaryTypes binaryTypes = oracle->binaryTypes(script(), pc); if (!monitored) { if (types::HeapTypeSet *propTypes = GetDefiniteSlot(cx, binaryTypes.lhsTypes, name)) { @@ -6531,14 +6554,14 @@ IonBuilder::jsop_setprop(HandlePropertyName name) return resumeAfter(call); } - oracle->binaryOp(script_, pc); + oracle->binaryOp(script(), pc); MSetPropertyInstruction *ins; if (monitored) { - ins = MCallSetProperty::New(obj, value, name, script_->strictModeCode); + ins = MCallSetProperty::New(obj, value, name, script()->strictModeCode); } else { Shape *objShape; - if ((objShape = mjit::GetPICSingleShape(cx, script_, pc, info().constructing())) && + if ((objShape = mjit::GetPICSingleShape(cx, script(), pc, info().constructing())) && !objShape->inDictionary()) { // The JM IC was monomorphic, so we inline the property access as @@ -6553,14 +6576,14 @@ IonBuilder::jsop_setprop(HandlePropertyName name) spew("Inlining monomorphic SETPROP"); jsid typeId = types::MakeTypeId(cx, id); - bool needsBarrier = oracle->propertyWriteNeedsBarrier(script_, pc, typeId); + bool needsBarrier = oracle->propertyWriteNeedsBarrier(script(), pc, typeId); return storeSlot(obj, shape, value, needsBarrier); } spew("SETPROP not monomorphic"); - ins = MSetPropertyCache::New(obj, value, name, script_->strictModeCode); + ins = MSetPropertyCache::New(obj, value, name, script()->strictModeCode); if (!binaryTypes.lhsTypes || binaryTypes.lhsTypes->propertyNeedsBarrier(cx, id)) ins->setNeedsBarrier(); @@ -6588,7 +6611,7 @@ IonBuilder::jsop_delprop(HandlePropertyName name) bool IonBuilder::jsop_regexp(RegExpObject *reobj) { - JSObject *prototype = script_->global().getOrCreateRegExpPrototype(cx); + JSObject *prototype = script()->global().getOrCreateRegExpPrototype(cx); if (!prototype) return false; @@ -6612,7 +6635,7 @@ IonBuilder::jsop_object(JSObject *obj) bool IonBuilder::jsop_lambda(JSFunction *fun) { - JS_ASSERT(script_->analysis()->usesScopeChain()); + JS_ASSERT(script()->analysis()->usesScopeChain()); MLambda *ins = MLambda::New(current->scopeChain(), fun); current->add(ins); current->push(ins); @@ -6623,7 +6646,7 @@ IonBuilder::jsop_lambda(JSFunction *fun) bool IonBuilder::jsop_deflocalfun(uint32_t local, JSFunction *fun) { - JS_ASSERT(script_->analysis()->usesScopeChain()); + JS_ASSERT(script()->analysis()->usesScopeChain()); MLambda *ins = MLambda::New(current->scopeChain(), fun); current->add(ins); @@ -6640,7 +6663,7 @@ IonBuilder::jsop_defvar(uint32_t index) { JS_ASSERT(JSOp(*pc) == JSOP_DEFVAR || JSOp(*pc) == JSOP_DEFCONST); - PropertyName *name = script_->getName(index); + PropertyName *name = script()->getName(index); // Bake in attrs. unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT; @@ -6648,7 +6671,7 @@ IonBuilder::jsop_defvar(uint32_t index) attrs |= JSPROP_READONLY; // Pass the ScopeChain. - JS_ASSERT(script_->analysis()->usesScopeChain()); + JS_ASSERT(script()->analysis()->usesScopeChain()); // Bake the name pointer into the MDefVar. MDefVar *defvar = MDefVar::New(name, attrs, current->scopeChain()); @@ -6663,12 +6686,12 @@ IonBuilder::jsop_this() if (!info().fun()) return abort("JSOP_THIS outside of a JSFunction."); - if (script_->strictModeCode) { + if (script()->strictModeCode) { current->pushSlot(info().thisSlot()); return true; } - types::StackTypeSet *types = oracle->thisTypeSet(script_); + types::StackTypeSet *types = oracle->thisTypeSet(script()); if (types && types->getKnownTypeTag() == JSVAL_TYPE_OBJECT) { // This is safe, because if the entry type of |this| is an object, it // will necessarily be an object throughout the entire function. OSR @@ -6683,7 +6706,7 @@ IonBuilder::jsop_this() bool IonBuilder::jsop_typeof() { - TypeOracle::Unary unary = oracle->unaryOp(script_, pc); + TypeOracle::Unary unary = oracle->unaryOp(script(), pc); MDefinition *input = current->pop(); MTypeOf *ins = MTypeOf::New(input, unary.ival); @@ -6700,7 +6723,7 @@ bool IonBuilder::jsop_toid() { // No-op if the index is an integer. - TypeOracle::Unary unary = oracle->unaryOp(script_, pc); + TypeOracle::Unary unary = oracle->unaryOp(script(), pc); if (unary.ival == MIRType_Int32) return true; @@ -6781,11 +6804,11 @@ bool IonBuilder::jsop_getaliasedvar(ScopeCoordinate sc) { types::StackTypeSet *barrier; - types::StackTypeSet *actual = oracle->aliasedVarBarrier(script_, pc, &barrier); + types::StackTypeSet *actual = oracle->aliasedVarBarrier(script(), pc, &barrier); MDefinition *obj = walkScopeChain(sc.hops); - RootedShape shape(cx, ScopeCoordinateToStaticScope(script_, pc).scopeShape()); + RootedShape shape(cx, ScopeCoordinateToStaticScope(script(), pc).scopeShape()); MInstruction *load; if (shape->numFixedSlots() <= sc.slot) { @@ -6819,7 +6842,7 @@ IonBuilder::jsop_setaliasedvar(ScopeCoordinate sc) MDefinition *rval = current->peek(-1); MDefinition *obj = walkScopeChain(sc.hops); - RootedShape shape(cx, ScopeCoordinateToStaticScope(script_, pc).scopeShape()); + RootedShape shape(cx, ScopeCoordinateToStaticScope(script(), pc).scopeShape()); MInstruction *store; if (shape->numFixedSlots() <= sc.slot) { @@ -6838,7 +6861,7 @@ IonBuilder::jsop_setaliasedvar(ScopeCoordinate sc) bool IonBuilder::jsop_in() { - if (oracle->inObjectIsDenseArray(script_, pc)) + if (oracle->inObjectIsDenseArray(script(), pc)) return jsop_in_dense(); MDefinition *obj = current->pop(); @@ -6857,7 +6880,7 @@ IonBuilder::jsop_in_dense() if (oracle->arrayPrototypeHasIndexedProperty()) return abort("JSOP_IN Array proto has indexed properties"); - bool needsHoleCheck = !oracle->inArrayIsPacked(script_, pc); + bool needsHoleCheck = !oracle->inArrayIsPacked(script(), pc); MDefinition *obj = current->pop(); MDefinition *id = current->pop(); @@ -6889,7 +6912,7 @@ IonBuilder::jsop_instanceof() MDefinition *rhs = current->pop(); MDefinition *obj = current->pop(); - TypeOracle::BinaryTypes types = oracle->binaryTypes(script_, pc); + TypeOracle::BinaryTypes types = oracle->binaryTypes(script(), pc); // If this is an 'x instanceof function' operation and we can determine the // exact function and prototype object being tested for, use a typed path. diff --git a/js/src/ion/IonMacroAssembler.h b/js/src/ion/IonMacroAssembler.h index 9de5028e31b2..ae453ee7db65 100644 --- a/js/src/ion/IonMacroAssembler.h +++ b/js/src/ion/IonMacroAssembler.h @@ -284,6 +284,16 @@ class MacroAssembler : public MacroAssemblerSpecific uint32_t bit = JSFunction::INTERPRETED << 16; branchTest32(Assembler::Zero, address, Imm32(bit), label); } + void branchIfInterpreted(Register fun, Label *label) { + // 16-bit loads are slow and unaligned 32-bit loads may be too so + // perform an aligned 32-bit load and adjust the bitmask accordingly. + JS_STATIC_ASSERT(offsetof(JSFunction, nargs) % sizeof(uint32_t) == 0); + JS_STATIC_ASSERT(offsetof(JSFunction, flags) == offsetof(JSFunction, nargs) + 2); + JS_STATIC_ASSERT(IS_LITTLE_ENDIAN); + Address address(fun, offsetof(JSFunction, nargs)); + uint32_t bit = JSFunction::INTERPRETED << 16; + branchTest32(Assembler::NonZero, address, Imm32(bit), label); + } using MacroAssemblerSpecific::Push; diff --git a/js/src/ion/LIR-Common.h b/js/src/ion/LIR-Common.h index 7224eb574230..00f19905e491 100644 --- a/js/src/ion/LIR-Common.h +++ b/js/src/ion/LIR-Common.h @@ -420,26 +420,50 @@ class LToIdV : public LCallInstructionHelper // Allocate an object for |new| on the caller-side. // Always performs object initialization with a fast path. -class LCreateThis : public LInstructionHelper<1, 0, 0> +class LCreateThisWithTemplate : public LInstructionHelper<1, 0, 0> { public: - LIR_HEADER(CreateThis) + LIR_HEADER(CreateThisWithTemplate) - LCreateThis() + LCreateThisWithTemplate() { } + MCreateThisWithTemplate *mir() const { + return mir_->toCreateThisWithTemplate(); + } +}; + +// Allocate an object for |new| on the caller-side, when there is no templateObject. +class LCreateThisV : public LCallInstructionHelper +{ + public: + LIR_HEADER(CreateThisV) + + LCreateThisV(const LAllocation &callee, const LAllocation &prototype) + { + setOperand(0, callee); + setOperand(1, prototype); + } + + const LAllocation *getCallee() { + return getOperand(0); + } + const LAllocation *getPrototype() { + return getOperand(1); + } + MCreateThis *mir() const { return mir_->toCreateThis(); } }; // Allocate an object for |new| on the caller-side, when there is no templateObject. -class LCreateThisVM : public LCallInstructionHelper<1, 2, 0> +class LCreateThisO : public LCallInstructionHelper<1, 2, 0> { public: - LIR_HEADER(CreateThisVM) + LIR_HEADER(CreateThisO) - LCreateThisVM(const LAllocation &callee, const LAllocation &prototype) + LCreateThisO(const LAllocation &callee, const LAllocation &prototype) { setOperand(0, callee); setOperand(1, prototype); @@ -681,24 +705,6 @@ class LCallDOMNative : public LJSCallInstructionHelper } }; -// Generates a polymorphic callsite for |new|, where |this| has not been -// pre-allocated by the caller. -class LCallConstructor : public LJSCallInstructionHelper -{ - public: - LIR_HEADER(CallConstructor) - - LCallConstructor(const LAllocation &func, uint32_t argslot) - : JSCallHelper(argslot) - { - setOperand(0, func); - } - - const LAllocation *getFunction() { - return getOperand(0); - } -}; - template class LDOMPropertyInstructionHelper : public LCallInstructionHelper { diff --git a/js/src/ion/LOpcodes.h b/js/src/ion/LOpcodes.h index e4250e8dcf6d..99b38dd948a9 100644 --- a/js/src/ion/LOpcodes.h +++ b/js/src/ion/LOpcodes.h @@ -35,12 +35,12 @@ _(CallKnown) \ _(CallGeneric) \ _(CallNative) \ - _(CallConstructor) \ _(ApplyArgsGeneric) \ _(StackArgT) \ _(StackArgV) \ - _(CreateThis) \ - _(CreateThisVM) \ + _(CreateThisV) \ + _(CreateThisO) \ + _(CreateThisWithTemplate) \ _(ReturnFromCtor) \ _(BitNotI) \ _(BitNotV) \ diff --git a/js/src/ion/Lowering.cpp b/js/src/ion/Lowering.cpp index 4c0a2705136e..343a995a04ba 100644 --- a/js/src/ion/Lowering.cpp +++ b/js/src/ion/Lowering.cpp @@ -235,18 +235,25 @@ LIRGenerator::visitPassArg(MPassArg *arg) return add(stack); } +bool +LIRGenerator::visitCreateThisWithTemplate(MCreateThisWithTemplate *ins) +{ + LCreateThisWithTemplate *lir = new LCreateThisWithTemplate(); + return define(lir, ins) && assignSafepoint(lir, ins); +} + bool LIRGenerator::visitCreateThis(MCreateThis *ins) { - // Template objects permit fast initialization. - if (ins->hasTemplateObject()) { - LCreateThis *lir = new LCreateThis(); - return define(lir, ins) && assignSafepoint(lir, ins); + if (ins->needNativeCheck()) { + JS_ASSERT(ins->type() == MIRType_Value); + LCreateThisV *lir = new LCreateThisV(useRegisterAtStart(ins->getCallee()), + useRegisterOrConstantAtStart(ins->getPrototype())); + return defineReturn(lir, ins) && assignSafepoint(lir, ins); } - LCreateThisVM *lir = new LCreateThisVM(useRegisterOrConstantAtStart(ins->getCallee()), - useRegisterOrConstantAtStart(ins->getPrototype())); - + LCreateThisO *lir = new LCreateThisO(useRegisterOrConstantAtStart(ins->getCallee()), + useRegisterOrConstantAtStart(ins->getPrototype())); return defineReturn(lir, ins) && assignSafepoint(lir, ins); } @@ -297,13 +304,6 @@ LIRGenerator::visitCall(MCall *call) return (defineReturn(lir, call) && assignSafepoint(lir, call)); } - // Call unknown constructors. - if (call->isConstructing()) { - LCallConstructor *lir = new LCallConstructor(useFixed(call->getFunction(), - CallTempReg0), argslot); - return (defineReturn(lir, call) && assignSafepoint(lir, call)); - } - // Call anything, using the most generic code. LCallGeneric *lir = new LCallGeneric(useFixed(call->getFunction(), CallTempReg0), argslot, tempFixed(ArgumentsRectifierReg), tempFixed(CallTempReg2)); @@ -739,7 +739,7 @@ LIRGenerator::visitAbs(MAbs *ins) if (num->type() == MIRType_Int32) { LAbsI *lir = new LAbsI(useRegisterAtStart(num)); // needed to handle abs(INT32_MIN) - if (!ins->range()->isFinite() && !assignSnapshot(lir)) + if (ins->fallible() && !assignSnapshot(lir)) return false; return defineReuseInput(lir, ins, 0); } diff --git a/js/src/ion/Lowering.h b/js/src/ion/Lowering.h index fc077ce6e4be..922aa8452b15 100644 --- a/js/src/ion/Lowering.h +++ b/js/src/ion/Lowering.h @@ -89,6 +89,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitDefVar(MDefVar *ins); bool visitPrepareCall(MPrepareCall *ins); bool visitPassArg(MPassArg *arg); + bool visitCreateThisWithTemplate(MCreateThisWithTemplate *ins); bool visitCreateThis(MCreateThis *ins); bool visitReturnFromCtor(MReturnFromCtor *ins); bool visitCall(MCall *call); diff --git a/js/src/ion/MCallOptimize.cpp b/js/src/ion/MCallOptimize.cpp index caf413249b26..d421f997980c 100644 --- a/js/src/ion/MCallOptimize.cpp +++ b/js/src/ion/MCallOptimize.cpp @@ -113,7 +113,7 @@ types::StackTypeSet * IonBuilder::getInlineReturnTypeSet() { types::StackTypeSet *barrier; - types::StackTypeSet *returnTypes = oracle->returnTypeSet(script_, pc, &barrier); + types::StackTypeSet *returnTypes = oracle->returnTypeSet(script(), pc, &barrier); JS_ASSERT(returnTypes); return returnTypes; @@ -129,7 +129,7 @@ IonBuilder::getInlineReturnType() types::StackTypeSet * IonBuilder::getInlineArgTypeSet(uint32_t argc, uint32_t arg) { - types::StackTypeSet *argTypes = oracle->getCallArg(script_, argc, arg, pc); + types::StackTypeSet *argTypes = oracle->getCallArg(script(), argc, arg, pc); JS_ASSERT(argTypes); return argTypes; } diff --git a/js/src/ion/MIR.cpp b/js/src/ion/MIR.cpp index bec60a3f491d..c9884dc59425 100644 --- a/js/src/ion/MIR.cpp +++ b/js/src/ion/MIR.cpp @@ -718,6 +718,12 @@ MBinaryArithInstruction::foldsTo(bool useValueNumbers) return this; } +bool +MAbs::fallible() const +{ + return !range() || !range()->isFinite(); +} + MDefinition * MDiv::foldsTo(bool useValueNumbers) { diff --git a/js/src/ion/MIR.h b/js/src/ion/MIR.h index af072efeedba..429184f01a4c 100644 --- a/js/src/ion/MIR.h +++ b/js/src/ion/MIR.h @@ -1570,6 +1570,36 @@ class MGuardString } }; +// Caller-side allocation of |this| for |new|: +// Given a templateobject, construct |this| for JSOP_NEW +class MCreateThisWithTemplate + : public MNullaryInstruction +{ + // Template for |this|, provided by TI + CompilerRootObject templateObject_; + + MCreateThisWithTemplate(JSObject *templateObject) + : templateObject_(templateObject) + { + setResultType(MIRType_Object); + } + + public: + INSTRUCTION_HEADER(CreateThisWithTemplate); + static MCreateThisWithTemplate *New(JSObject *templateObject) + { + return new MCreateThisWithTemplate(templateObject); + } + JSObject *getTemplateObject() const { + return templateObject_; + } + + // Although creation of |this| modifies global state, it is safely repeatable. + AliasSet getAliasSet() const { + return AliasSet::None(); + } +}; + // Caller-side allocation of |this| for |new|: // Given a prototype operand, construct |this| for JSOP_NEW. // For native constructors, returns MagicValue(JS_IS_CONSTRUCTING). @@ -1577,22 +1607,24 @@ class MCreateThis : public MAryInstruction<2>, public MixPolicy, ObjectPolicy<1> > { - // Template for |this|, provided by TI, or NULL. - CompilerRootObject templateObject_; + bool needNativeCheck_; - MCreateThis(MDefinition *callee, MDefinition *prototype, JSObject *templateObject) - : templateObject_(templateObject) + MCreateThis(MDefinition *callee, MDefinition *prototype) + : needNativeCheck_(true) { initOperand(0, callee); initOperand(1, prototype); - setResultType(MIRType_Object); + + // Type is mostly object, except for native constructors + // therefore the need of Value type. + setResultType(MIRType_Value); } public: INSTRUCTION_HEADER(CreateThis) - static MCreateThis *New(MDefinition *callee, MDefinition *prototype, JSObject *templateObject) + static MCreateThis *New(MDefinition *callee, MDefinition *prototype) { - return new MCreateThis(callee, prototype, templateObject); + return new MCreateThis(callee, prototype); } MDefinition *getCallee() const { @@ -1601,12 +1633,12 @@ class MCreateThis MDefinition *getPrototype() const { return getOperand(1); } - bool hasTemplateObject() const { - return !!templateObject_; + void removeNativeCheck() { + needNativeCheck_ = false; + setResultType(MIRType_Object); } - JSObject *getTemplateObject() const { - JS_ASSERT(hasTemplateObject()); - return templateObject_; + bool needNativeCheck() const { + return needNativeCheck_; } // Although creation of |this| modifies global state, it is safely repeatable. @@ -2243,6 +2275,7 @@ class MAbs bool congruentTo(MDefinition *const &ins) const { return congruentIfOperandsEqual(ins); } + bool fallible() const; AliasSet getAliasSet() const { return AliasSet::None(); diff --git a/js/src/ion/MOpcodes.h b/js/src/ion/MOpcodes.h index ae948185d464..05fb87864eee 100644 --- a/js/src/ion/MOpcodes.h +++ b/js/src/ion/MOpcodes.h @@ -30,6 +30,7 @@ namespace ion { _(RecompileCheck) \ _(DefVar) \ _(CreateThis) \ + _(CreateThisWithTemplate) \ _(PrepareCall) \ _(PassArg) \ _(Call) \ diff --git a/js/src/ion/TypeOracle.cpp b/js/src/ion/TypeOracle.cpp index 7c4e753afc7e..0caabcb137b9 100644 --- a/js/src/ion/TypeOracle.cpp +++ b/js/src/ion/TypeOracle.cpp @@ -66,7 +66,7 @@ TypeInferenceOracle::getMIRType(HeapTypeSet *types) TypeOracle::UnaryTypes TypeInferenceOracle::unaryTypes(JSScript *script, jsbytecode *pc) { - JS_ASSERT(script == this->script_); + JS_ASSERT(script == this->script()); UnaryTypes res; res.inTypes = script->analysis()->poppedTypes(pc, 0); @@ -77,7 +77,7 @@ TypeInferenceOracle::unaryTypes(JSScript *script, jsbytecode *pc) TypeOracle::BinaryTypes TypeInferenceOracle::binaryTypes(JSScript *script, jsbytecode *pc) { - JS_ASSERT(script == this->script_); + JS_ASSERT(script == this->script()); JSOp op = (JSOp)*pc; @@ -97,7 +97,7 @@ TypeInferenceOracle::binaryTypes(JSScript *script, jsbytecode *pc) TypeOracle::Unary TypeInferenceOracle::unaryOp(JSScript *script, jsbytecode *pc) { - JS_ASSERT(script == this->script_); + JS_ASSERT(script == this->script()); Unary res; res.ival = getMIRType(script->analysis()->poppedTypes(pc, 0)); @@ -108,7 +108,7 @@ TypeInferenceOracle::unaryOp(JSScript *script, jsbytecode *pc) TypeOracle::Binary TypeInferenceOracle::binaryOp(JSScript *script, jsbytecode *pc) { - JS_ASSERT(script == this->script_); + JS_ASSERT(script == this->script()); JSOp op = (JSOp)*pc; @@ -128,7 +128,7 @@ TypeInferenceOracle::binaryOp(JSScript *script, jsbytecode *pc) StackTypeSet * TypeInferenceOracle::thisTypeSet(JSScript *script) { - JS_ASSERT(script == this->script_); + JS_ASSERT(script == this->script()); return TypeScript::ThisTypes(script); } @@ -136,18 +136,18 @@ bool TypeInferenceOracle::getOsrTypes(jsbytecode *osrPc, Vector &slotTypes) { JS_ASSERT(JSOp(*osrPc) == JSOP_LOOPENTRY); - JS_ASSERT(script_->code < osrPc); - JS_ASSERT(osrPc < script_->code + script_->length); + JS_ASSERT(script()->code < osrPc); + JS_ASSERT(osrPc < script()->code + script()->length); Vector slotTypeSets(cx); - if (!slotTypeSets.resize(TotalSlots(script_))) + if (!slotTypeSets.resize(TotalSlots(script()))) return false; - for (uint32_t slot = ThisSlot(); slot < TotalSlots(script_); slot++) - slotTypeSets[slot] = TypeScript::SlotTypes(script_, slot); + for (uint32_t slot = ThisSlot(); slot < TotalSlots(script()); slot++) + slotTypeSets[slot] = TypeScript::SlotTypes(script(), slot); - jsbytecode *pc = script_->code; - ScriptAnalysis *analysis = script_->analysis(); + jsbytecode *pc = script()->code; + ScriptAnalysis *analysis = script()->analysis(); // To determine the slot types at the OSR pc, we have to do a forward walk // over the bytecode to reconstruct the types. @@ -158,7 +158,7 @@ TypeInferenceOracle::getOsrTypes(jsbytecode *osrPc, Vector &slotTypes) // Update variable types for all new values at this bytecode. if (const SlotValue *newv = analysis->newValues(pc)) { while (newv->slot) { - if (newv->slot < TotalSlots(script_)) + if (newv->slot < TotalSlots(script())) slotTypeSets[newv->slot] = analysis->getValueTypes(newv->value); newv++; } @@ -166,7 +166,7 @@ TypeInferenceOracle::getOsrTypes(jsbytecode *osrPc, Vector &slotTypes) } if (BytecodeUpdatesSlot(JSOp(*pc))) { - uint32_t slot = GetBytecodeSlot(script_, pc); + uint32_t slot = GetBytecodeSlot(script(), pc); if (analysis->trackSlot(slot)) slotTypeSets[slot] = analysis->pushedTypes(pc, 0); } @@ -189,15 +189,15 @@ TypeInferenceOracle::getOsrTypes(jsbytecode *osrPc, Vector &slotTypes) uint32_t stackDepth = analysis->getCode(osrPc).stackDepth; #endif - if (script_->function()) { - JS_ASSERT(slotTypes.length() == TotalSlots(script_) + stackDepth); + if (script()->function()) { + JS_ASSERT(slotTypes.length() == TotalSlots(script()) + stackDepth); - for (size_t i = ThisSlot(); i < TotalSlots(script_); i++) + for (size_t i = ThisSlot(); i < TotalSlots(script()); i++) slotTypes[i] = getMIRType(slotTypeSets[i]); } else { - JS_ASSERT(slotTypes.length() == TotalSlots(script_) + stackDepth - 1); + JS_ASSERT(slotTypes.length() == TotalSlots(script()) + stackDepth - 1); - for (size_t i = ArgSlot(0); i < TotalSlots(script_); i++) + for (size_t i = ArgSlot(0); i < TotalSlots(script()); i++) slotTypes[i - 1] = getMIRType(slotTypeSets[i]); } @@ -207,7 +207,7 @@ TypeInferenceOracle::getOsrTypes(jsbytecode *osrPc, Vector &slotTypes) StackTypeSet * TypeInferenceOracle::parameterTypeSet(JSScript *script, size_t index) { - JS_ASSERT(script == this->script_); + JS_ASSERT(script == this->script()); return TypeScript::ArgTypes(script, index); } @@ -484,14 +484,14 @@ TypeInferenceOracle::elementWrite(JSScript *script, jsbytecode *pc) bool TypeInferenceOracle::arrayPrototypeHasIndexedProperty() { - RootedScript script(cx, script_); - return ArrayPrototypeHasIndexedProperty(cx, script); + RootedScript scriptRoot(cx, script()); + return ArrayPrototypeHasIndexedProperty(cx, scriptRoot); } bool TypeInferenceOracle::canInlineCalls() { - return script_->analysis()->hasFunctionCalls(); + return script()->analysis()->hasFunctionCalls(); } bool @@ -519,10 +519,10 @@ TypeInferenceOracle::elementWriteNeedsBarrier(JSScript *script, jsbytecode *pc) StackTypeSet * TypeInferenceOracle::getCallTarget(JSScript *caller, uint32_t argc, jsbytecode *pc) { - JS_ASSERT(caller == this->script_); + JS_ASSERT(caller == this->script()); JS_ASSERT(js_CodeSpec[*pc].format & JOF_INVOKE && JSOp(*pc) != JSOP_EVAL); - ScriptAnalysis *analysis = script_->analysis(); + ScriptAnalysis *analysis = script()->analysis(); return analysis->poppedTypes(pc, argc + 1); } diff --git a/js/src/ion/TypeOracle.h b/js/src/ion/TypeOracle.h index 9b943cabd237..0330d5570090 100644 --- a/js/src/ion/TypeOracle.h +++ b/js/src/ion/TypeOracle.h @@ -208,6 +208,8 @@ class TypeInferenceOracle : public TypeOracle bool init(JSContext *cx, JSScript *script); + UnrootedScript script() { return script_.get(); } + UnaryTypes unaryTypes(JSScript *script, jsbytecode *pc); BinaryTypes binaryTypes(JSScript *script, jsbytecode *pc); Unary unaryOp(JSScript *script, jsbytecode *pc); diff --git a/js/src/ion/VMFunctions.cpp b/js/src/ion/VMFunctions.cpp index 4651e3a9480e..26b316cb8f01 100644 --- a/js/src/ion/VMFunctions.cpp +++ b/js/src/ion/VMFunctions.cpp @@ -93,35 +93,6 @@ InvokeFunction(JSContext *cx, JSFunction *fun, uint32_t argc, Value *argv, Value return ok; } -bool -InvokeConstructor(JSContext *cx, JSObject *obj, uint32_t argc, Value *argv, Value *rval) -{ - Value fval = ObjectValue(*obj); - - // See the comment in InvokeFunction. - bool needsMonitor; - - if (obj->isFunction()) { - if (obj->toFunction()->isInterpretedLazy() && - !obj->toFunction()->getOrCreateScript(cx)) - { - return false; - } - needsMonitor = ShouldMonitorReturnType(obj->toFunction()); - } else { - needsMonitor = true; - } - - // Data in the argument vector is arranged for a JIT -> JIT call. - Value *argvWithoutThis = argv + 1; - - bool ok = js::InvokeConstructor(cx, fval, argc, argvWithoutThis, rval); - if (ok && needsMonitor) - types::TypeScript::Monitor(cx, *rval); - - return ok; -} - JSObject * NewGCThing(JSContext *cx, gc::AllocKind allocKind, size_t thingSize) { diff --git a/js/src/ion/VMFunctions.h b/js/src/ion/VMFunctions.h index 264b565bb61f..da80393eb52c 100644 --- a/js/src/ion/VMFunctions.h +++ b/js/src/ion/VMFunctions.h @@ -411,7 +411,6 @@ class AutoDetectInvalidation }; bool InvokeFunction(JSContext *cx, JSFunction *fun, uint32_t argc, Value *argv, Value *rval); -bool InvokeConstructor(JSContext *cx, JSObject *obj, uint32_t argc, Value *argv, Value *rval); JSObject *NewGCThing(JSContext *cx, gc::AllocKind allocKind, size_t thingSize); bool CheckOverRecursed(JSContext *cx); diff --git a/js/src/jit-test/tests/ion/bug816492.js b/js/src/jit-test/tests/ion/bug816492.js new file mode 100644 index 000000000000..c3c5d84fa53e --- /dev/null +++ b/js/src/jit-test/tests/ion/bug816492.js @@ -0,0 +1,8 @@ +function TestCase(n, d, e, a) {} +function reportCompare () { + var testcase = new TestCase(); +} +reportCompare(); +schedulegc(10); +this.TestCase=Number; +reportCompare(4294967295.5); diff --git a/js/src/jit-test/tests/ion/bug819794.js b/js/src/jit-test/tests/ion/bug819794.js new file mode 100644 index 000000000000..919c5c47db14 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug819794.js @@ -0,0 +1,7 @@ +x = []; +x[2] = 1; +x.unshift(0); +x.unshift(0); +x.sort(function() { + return Math.abs(4) +}) diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index 2fe46445abaf..e68f8c5242e0 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -528,7 +528,7 @@ template inline T * TryNewGCThing(JSContext *cx, js::gc::AllocKind kind, size_t thingSize) { - AssertCanGC(); + AutoAssertNoGC nogc; JS_ASSERT(thingSize == js::gc::Arena::thingSize(kind)); JS_ASSERT_IF(cx->compartment == cx->runtime->atomsCompartment, kind == js::gc::FINALIZE_STRING || kind == js::gc::FINALIZE_SHORT_STRING); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 77596c963088..182a6d5b960f 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -10,14 +10,15 @@ /* * JS script descriptor. */ -#include "jsprvtd.h" #include "jsdbgapi.h" -#include "jsclist.h" #include "jsinfer.h" #include "jsopcode.h" #include "jsscope.h" #include "gc/Barrier.h" +#include "gc/Root.h" + +ForwardDeclareJS(Script); namespace js { diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 6ef3a90427ab..f742424bdefb 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -17,6 +17,7 @@ */ #include "mozilla/Attributes.h" +#include "mozilla/CheckedInt.h" #include "mozilla/FloatingPoint.h" #include @@ -63,6 +64,8 @@ using namespace js::gc; using namespace js::types; using namespace js::unicode; +using mozilla::CheckedInt; + static JSLinearString * ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno) { @@ -2025,19 +2028,28 @@ FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t } JSString *repstr = rdata.repstr; - size_t replen = repstr->length(); + CheckedInt replen = repstr->length(); for (const jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp; dp = js_strchr_limit(dp, '$', ep)) { JSSubString sub; size_t skip; if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) { - replen += sub.length - skip; + if (sub.length > skip) + replen += sub.length - skip; + else + replen -= skip - sub.length; dp += skip; } else { dp++; } } - *sizep = replen; + + if (!replen.isValid()) { + js_ReportAllocationOverflow(cx); + return false; + } + + *sizep = replen.value(); return true; } @@ -2088,8 +2100,14 @@ ReplaceRegExpCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p) if (!FindReplaceLength(cx, res, rdata, &replen)) return false; - size_t growth = leftlen + replen; - if (!rdata.sb.reserve(rdata.sb.length() + growth)) + CheckedInt newlen(rdata.sb.length()); + newlen += leftlen; + newlen += replen; + if (!newlen.isValid()) { + js_ReportAllocationOverflow(cx); + return false; + } + if (!rdata.sb.reserve(newlen.value())) return false; JSLinearString &str = rdata.str->asLinear(); /* flattened for regexp */ diff --git a/js/xpconnect/loader/Makefile.in b/js/xpconnect/loader/Makefile.in index 0d576b6cc31d..1a1d7e5a5d96 100644 --- a/js/xpconnect/loader/Makefile.in +++ b/js/xpconnect/loader/Makefile.in @@ -13,8 +13,11 @@ MODULE = jsloader LIBRARY_NAME = jsloader_s FORCE_STATIC_LIB = 1 LIBXUL_LIBRARY = 1 -LOCAL_INCLUDES += -I$(srcdir)/../src \ - -I$(srcdir)/../wrappers +LOCAL_INCLUDES += \ + -I$(srcdir)/../src \ + -I$(srcdir)/../wrappers \ + -I$(topsrcdir)/content/base/src \ + $(NULL) CPPSRCS = mozJSComponentLoader.cpp mozJSSubScriptLoader.cpp mozJSLoaderUtils.cpp diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index 12bfc1e7e8d0..e68cab25a3d6 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -43,7 +43,7 @@ #include "nsIFileURL.h" #include "nsIJARURI.h" #include "nsNetUtil.h" -#include "nsDOMFile.h" +#include "nsDOMBlobBuilder.h" #include "jsprf.h" #include "nsJSPrincipals.h" // For reporting errors with the console service @@ -245,7 +245,7 @@ File(JSContext *cx, unsigned argc, jsval *vp) } nsCOMPtr native; - rv = nsDOMFileFile::NewFile(getter_AddRefs(native)); + rv = nsDOMMultipartFile::NewFile(getter_AddRefs(native)); if (NS_FAILED(rv)) { XPCThrower::Throw(rv, cx); return false; diff --git a/js/xpconnect/src/nsDOMQS.h b/js/xpconnect/src/nsDOMQS.h index 4ef648704d32..b82b1bff533f 100644 --- a/js/xpconnect/src/nsDOMQS.h +++ b/js/xpconnect/src/nsDOMQS.h @@ -9,6 +9,7 @@ #include "nsGenericHTMLElement.h" #include "nsHTMLCanvasElement.h" #include "nsHTMLDivElement.h" +#include "nsHTMLFormElement.h" #include "nsHTMLImageElement.h" #include "nsHTMLOptionElement.h" #include "nsHTMLOptGroupElement.h" @@ -156,6 +157,7 @@ xpc_qsUnwrapArg<_clazz>(JSContext *cx, jsval v, _clazz **ppArg, \ DEFINE_UNWRAP_CAST_HTML(canvas, nsHTMLCanvasElement) DEFINE_UNWRAP_CAST_HTML(div, nsHTMLDivElement) +DEFINE_UNWRAP_CAST_HTML(form, nsHTMLFormElement) DEFINE_UNWRAP_CAST_HTML(img, nsHTMLImageElement) DEFINE_UNWRAP_CAST_HTML(optgroup, nsHTMLOptGroupElement) DEFINE_UNWRAP_CAST_HTML(option, nsHTMLOptionElement) diff --git a/js/xpconnect/tests/unit/component-file.js b/js/xpconnect/tests/unit/component-file.js index 70321a2d0c2d..0094354ec2c8 100644 --- a/js/xpconnect/tests/unit/component-file.js +++ b/js/xpconnect/tests/unit/component-file.js @@ -35,31 +35,22 @@ FileComponent.prototype = // and with nsIFiles var f3 = File(file); var f4 = new File(file); - // and extra args are ignored - var f5 = File(file.path, "foopy"); - var f6 = new File(file, Date(123123154)); // do some tests do_check_true(f1 instanceof Ci.nsIDOMFile, "Should be a DOM File"); do_check_true(f2 instanceof Ci.nsIDOMFile, "Should be a DOM File"); do_check_true(f3 instanceof Ci.nsIDOMFile, "Should be a DOM File"); do_check_true(f4 instanceof Ci.nsIDOMFile, "Should be a DOM File"); - do_check_true(f5 instanceof Ci.nsIDOMFile, "Should be a DOM File"); - do_check_true(f6 instanceof Ci.nsIDOMFile, "Should be a DOM File"); do_check_true(f1.name == "xpcshell.ini", "Should be the right file"); do_check_true(f2.name == "xpcshell.ini", "Should be the right file"); do_check_true(f3.name == "xpcshell.ini", "Should be the right file"); do_check_true(f4.name == "xpcshell.ini", "Should be the right file"); - do_check_true(f5.name == "xpcshell.ini", "Should be the right file"); - do_check_true(f6.name == "xpcshell.ini", "Should be the right file"); do_check_true(f1.type = "text/plain", "Should be the right type"); do_check_true(f2.type = "text/plain", "Should be the right type"); do_check_true(f3.type = "text/plain", "Should be the right type"); do_check_true(f4.type = "text/plain", "Should be the right type"); - do_check_true(f5.type = "text/plain", "Should be the right type"); - do_check_true(f6.type = "text/plain", "Should be the right type"); var threw = false; try { diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 1e093edf8f66..6ba1eb1b9133 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -275,7 +275,8 @@ public: */ void ProcessDisplayItems(const nsDisplayList& aList, FrameLayerBuilder::Clip& aClip, - uint32_t aFlags); + uint32_t aFlags, + const nsIFrame* aForceActiveScrolledRoot = nullptr); /** * This finalizes all the open ThebesLayers by popping every element off * mThebesLayerDataStack, then sets the children of the container layer @@ -561,6 +562,9 @@ protected: void SetupMaskLayer(Layer *aLayer, const FrameLayerBuilder::Clip& aClip, uint32_t aRoundedRectClipCount = UINT32_MAX); + bool ChooseActiveScrolledRoot(const nsDisplayList& aList, + const nsIFrame **aActiveScrolledRoot); + nsDisplayListBuilder* mBuilder; LayerManager* mManager; FrameLayerBuilder* mLayerBuilder; @@ -2023,56 +2027,40 @@ PaintInactiveLayer(nsDisplayListBuilder* aBuilder, #endif } -/** - * Checks if aAncestor is an ancestor of aFrame - */ -static bool IsFrameAncestorOf(const nsIFrame *aAncestor, const nsIFrame *aFrame) -{ - if (!aFrame) { - return false; - } - for (const nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) { - if (f == aAncestor) { - return true; - } - } - return false; -} - /** * Chooses a single active scrolled root for the entire display list, used * when we are flattening layers. */ -static bool ChooseActiveScrolledRoot(nsDisplayListBuilder *aBuilder, - const nsDisplayList& aList, - const nsIFrame **aActiveScrolledRoot) +bool +ContainerState::ChooseActiveScrolledRoot(const nsDisplayList& aList, + const nsIFrame **aActiveScrolledRoot) { for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) { nsDisplayItem::Type type = item->GetType(); if (type == nsDisplayItem::TYPE_CLIP || type == nsDisplayItem::TYPE_CLIP_ROUNDED_RECT) { - if (!ChooseActiveScrolledRoot(aBuilder, - *item->GetSameCoordinateSystemChildren(), - aActiveScrolledRoot)) { - return false; + if (ChooseActiveScrolledRoot(*item->GetSameCoordinateSystemChildren(), + aActiveScrolledRoot)) { + return true; } continue; } - if (!*aActiveScrolledRoot) { - // Try using the actual active scrolled root of the backmost item, as that - // should result in the least invalidation when scrolling. - aBuilder->IsFixedItem(item, aActiveScrolledRoot); - } else if (!IsFrameAncestorOf(*aActiveScrolledRoot, item->GetUnderlyingFrame())) { - // If there are items that aren't descendants of the background's active scrolled - // root, then give up and just use the container's reference frame instead. - return false; + LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters); + // Don't use an item that won't be part of any ThebesLayers to pick the + // active scrolled root. + if (layerState == LAYER_ACTIVE_FORCE) { + continue; + } + + // Try using the actual active scrolled root of the backmost item, as that + // should result in the least invalidation when scrolling. + mBuilder->IsFixedItem(item, aActiveScrolledRoot); + if (*aActiveScrolledRoot) { + return true; } } - if (!*aActiveScrolledRoot) { - return false; - } - return true; + return false; } /* @@ -2092,7 +2080,8 @@ static bool ChooseActiveScrolledRoot(nsDisplayListBuilder *aBuilder, void ContainerState::ProcessDisplayItems(const nsDisplayList& aList, FrameLayerBuilder::Clip& aClip, - uint32_t aFlags) + uint32_t aFlags, + const nsIFrame* aForceActiveScrolledRoot) { SAMPLE_LABEL("ContainerState", "ProcessDisplayItems"); @@ -2103,7 +2092,9 @@ ContainerState::ProcessDisplayItems(const nsDisplayList& aList, // layer, so we need to choose which active scrolled root to use for all // items. if (aFlags & NO_COMPONENT_ALPHA) { - if (!ChooseActiveScrolledRoot(mBuilder, aList, &lastActiveScrolledRoot)) { + if (aForceActiveScrolledRoot) { + lastActiveScrolledRoot = aForceActiveScrolledRoot; + } else if (!ChooseActiveScrolledRoot(aList, &lastActiveScrolledRoot)) { lastActiveScrolledRoot = mContainerReferenceFrame; } @@ -2115,7 +2106,7 @@ ContainerState::ProcessDisplayItems(const nsDisplayList& aList, if (type == nsDisplayItem::TYPE_CLIP || type == nsDisplayItem::TYPE_CLIP_ROUNDED_RECT) { FrameLayerBuilder::Clip childClip(aClip, item); - ProcessDisplayItems(*item->GetSameCoordinateSystemChildren(), childClip, aFlags); + ProcessDisplayItems(*item->GetSameCoordinateSystemChildren(), childClip, aFlags, lastActiveScrolledRoot); continue; } @@ -3774,7 +3765,7 @@ ContainerState::SetupMaskLayer(Layer *aLayer, const FrameLayerBuilder::Clip& aCl imageTransform.Scale(mParameters.mXScale, mParameters.mYScale); nsAutoPtr newKey( - new MaskLayerImageCache::MaskLayerImageKey(aLayer->Manager()->GetBackendType())); + new MaskLayerImageCache::MaskLayerImageKey()); // copy and transform the rounded rects for (uint32_t i = 0; i < newData.mRoundedClipRects.Length(); ++i) { diff --git a/layout/base/MaskLayerImageCache.h b/layout/base/MaskLayerImageCache.h index dda9d9ae03ea..ed126fabafa0 100644 --- a/layout/base/MaskLayerImageCache.h +++ b/layout/base/MaskLayerImageCache.h @@ -130,16 +130,14 @@ public: */ struct MaskLayerImageKey { - MaskLayerImageKey(layers::LayersBackend aBackend) - : mBackend(aBackend) - , mLayerCount(0) + MaskLayerImageKey() + : mLayerCount(0) , mRoundedClipRects() { MOZ_COUNT_CTOR(MaskLayerImageKey); } MaskLayerImageKey(const MaskLayerImageKey& aKey) - : mBackend(aKey.mBackend) - , mLayerCount(aKey.mLayerCount) + : mLayerCount(aKey.mLayerCount) , mRoundedClipRects(aKey.mRoundedClipRects) { MOZ_COUNT_CTOR(MaskLayerImageKey); @@ -164,7 +162,6 @@ public: for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { hash = AddToHash(hash, mRoundedClipRects[i].Hash()); } - hash = AddToHash(hash, mBackend); return hash; } @@ -174,7 +171,6 @@ public: return mRoundedClipRects == aOther.mRoundedClipRects; } - layers::LayersBackend mBackend; mutable uint32_t mLayerCount; nsTArray mRoundedClipRects; }; diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index d299d0a2f31a..44a3df4aaf68 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -89,6 +89,8 @@ #include "nsCSSRenderingBorders.h" #include "nsRenderingContext.h" #include "nsStyleStructInlines.h" +#include "nsAnimationManager.h" +#include "nsTransitionManager.h" #ifdef MOZ_XUL #include "nsIRootBox.h" @@ -1397,6 +1399,7 @@ nsCSSFrameConstructor::nsCSSFrameConstructor(nsIDocument *aDocument, , mInStyleRefresh(false) , mHoverGeneration(0) , mRebuildAllExtraHint(nsChangeHint(0)) + , mAnimationGeneration(0) , mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE | ELEMENT_IS_POTENTIAL_RESTYLE_ROOT, this) , mPendingAnimationRestyles(ELEMENT_HAS_PENDING_ANIMATION_RESTYLE | @@ -8238,9 +8241,12 @@ nsCSSFrameConstructor::ProcessRestyledFrames(nsStyleChangeList& aChangeList, #ifdef DEBUG // reget frame from content since it may have been regenerated... if (changeData->mContent) { - nsIFrame* frame = changeData->mContent->GetPrimaryFrame(); - if (frame) { - DebugVerifyStyleTree(frame); + if (!nsAnimationManager::ContentOrAncestorHasAnimation(changeData->mContent) && + !nsTransitionManager::ContentOrAncestorHasTransition(changeData->mContent)) { + nsIFrame* frame = changeData->mContent->GetPrimaryFrame(); + if (frame) { + DebugVerifyStyleTree(frame); + } } } else { NS_WARNING("Unable to test style tree integrity -- no content node"); @@ -12075,6 +12081,17 @@ nsCSSFrameConstructor::ProcessPendingRestyles() "Nesting calls to ProcessPendingRestyles?"); presContext->SetProcessingRestyles(true); + // Before we process any restyles, we need to ensure that style + // resulting from any throttled animations (animations that we're + // running entirely on the compositor thread) is up-to-date, so that + // if any style changes we cause trigger transitions, we have the + // correct old style for starting the transition. + if (css::CommonAnimationManager::ThrottlingEnabled() && + mPendingRestyles.Count() > 0) { + ++mAnimationGeneration; + presContext->TransitionManager()->UpdateAllThrottledStyles(); + } + mPendingRestyles.ProcessRestyles(); #ifdef DEBUG diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index f8c475e1ab5f..171bd397e54c 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -232,6 +232,10 @@ public: // as a result of a change to the :hover content state. uint32_t GetHoverGeneration() const { return mHoverGeneration; } + // Get a counter that increments on every style change, that we use to + // track whether off-main-thread animations are up-to-date. + uint64_t GetAnimationGeneration() const { return mAnimationGeneration; } + // Note: It's the caller's responsibility to make sure to wrap a // ProcessRestyledFrames call in a view update batch and a script blocker. // This function does not call ProcessAttachedQueue() on the binding manager. @@ -302,6 +306,7 @@ public: { PostRestyleEventCommon(aElement, aRestyleHint, aMinChangeHint, true); } + private: /** * Notify the frame constructor that an element needs to have its @@ -1886,6 +1891,10 @@ private: nsCOMPtr mTempFrameTreeState; + // The total number of animation flushes by this frame constructor. + // Used to keep the layer and animation manager in sync. + uint64_t mAnimationGeneration; + RestyleTracker mPendingRestyles; RestyleTracker mPendingAnimationRestyles; }; diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 65534c4011ff..cde72d2ad4c3 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -247,6 +247,18 @@ static void AddTransformFunctions(nsCSSValueList* aList, aFunctions.AppendElement(TransformMatrix(matrix)); break; } + case eCSSKeyword_interpolatematrix: + { + gfx3DMatrix matrix; + nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, array, + aContext, + aPresContext, + canStoreInRuleTree, + aBounds, + aAppUnitsPerPixel); + aFunctions.AppendElement(TransformMatrix(matrix)); + break; + } case eCSSKeyword_perspective: { aFunctions.AppendElement(Perspective(array->Item(1).GetFloatValue())); @@ -407,6 +419,7 @@ AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayListBuilder* aBuilder AddAnimationsForProperty(frame, aProperty, &anim, aLayer, data); } + aLayer->SetAnimationGeneration(et->mAnimationGeneration); } if (ea) { @@ -419,6 +432,7 @@ AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayListBuilder* aBuilder AddAnimationsForProperty(frame, aProperty, anim, aLayer, data); } + aLayer->SetAnimationGeneration(ea->mAnimationGeneration); } } @@ -2384,7 +2398,13 @@ nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder, nsRect nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { *aSnap = false; - return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); + return mBounds; +} + +nsRect +nsDisplayBoxShadowOuter::GetBoundsInternal() { + return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) + + ToReferenceFrame(); } bool diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 0be68799defd..a26e57f9fb04 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -1970,6 +1970,7 @@ public: nsDisplayBoxShadowOuter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) : nsDisplayItem(aBuilder, aFrame) { MOZ_COUNT_CTOR(nsDisplayBoxShadowOuter); + mBounds = GetBoundsInternal(); } #ifdef NS_BUILD_REFCNT_LOGGING virtual ~nsDisplayBoxShadowOuter() { @@ -1999,8 +2000,11 @@ public: } } + nsRect GetBoundsInternal(); + private: nsRegion mVisibleRegion; + nsRect mBounds; }; /** diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index c87ad1ae2a2f..8925037f7bfe 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -118,10 +118,10 @@ typedef struct CapturingContentInfo { nsIContent* mContent; } CapturingContentInfo; -// 0d3bfc0e-661c-4e70-933e-98efc912a75b +// a43e26cd-9573-44c7-8fe5-859549eff814 #define NS_IPRESSHELL_IID \ -{ 0x0d3bfc0e, 0x661c, 0x4e70, \ - { 0x93, 0x3e, 0x98, 0xef, 0xc9, 0x12, 0xa7, 0x5b } } +{ 0x13b031cb, 0x738a, 0x4e97, \ + { 0xb0, 0xca, 0x8b, 0x4b, 0x6c, 0xbb, 0xea, 0xa9 } } // debug VerifyReflow flags #define VERIFY_REFLOW_ON 0x01 @@ -524,6 +524,7 @@ public: * @param aType the type of notifications to flush */ virtual NS_HIDDEN_(void) FlushPendingNotifications(mozFlushType aType) = 0; + virtual NS_HIDDEN_(void) FlushPendingNotifications(mozilla::ChangesToFlush aType) = 0; /** * Callbacks will be called even if reflow itself fails for diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 1c1123d52c69..5d8aac7d635e 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -91,8 +91,9 @@ #include "nsTransitionManager.h" using namespace mozilla; -using namespace mozilla::layers; +using namespace mozilla::css; using namespace mozilla::dom; +using namespace mozilla::layers; using namespace mozilla::layout; #define FLEXBOX_ENABLED_PREF_NAME "layout.css.flexbox.enabled" @@ -177,31 +178,38 @@ FlexboxEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) } #endif // MOZ_FLEXBOX +template +static bool +HasAnimationOrTransition(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty) +{ + AnimationsOrTransitions* animations = + static_cast(aContent->GetProperty(aAnimationProperty)); + if (animations) { + bool propertyMatches = animations->HasAnimationOfProperty(aProperty); + if (propertyMatches && + animations->CanPerformOnCompositorThread( + CommonElementAnimationData::CanAnimate_AllowPartial)) { + return true; + } + } + + return false; +} + bool nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent, nsCSSProperty aProperty) { if (!aContent->MayHaveAnimations()) return false; - ElementAnimations* animations = - static_cast(aContent->GetProperty(nsGkAtoms::animationsProperty)); - if (animations) { - bool propertyMatches = animations->HasAnimationOfProperty(aProperty); - if (propertyMatches && animations->CanPerformOnCompositorThread()) { - return true; - } + if (HasAnimationOrTransition + (aContent, nsGkAtoms::animationsProperty, aProperty)) { + return true; } - - ElementTransitions* transitions = - static_cast(aContent->GetProperty(nsGkAtoms::transitionsProperty)); - if (transitions) { - bool propertyMatches = transitions->HasTransitionOfProperty(aProperty); - if (propertyMatches && transitions->CanPerformOnCompositorThread()) { - return true; - } - } - - return false; + return HasAnimationOrTransition + (aContent, nsGkAtoms::transitionsProperty, aProperty); } bool @@ -5216,3 +5224,32 @@ nsLayoutUtils::FontSizeInflationEnabled(nsPresContext *aPresContext) return true; } + +/* static */ nsRect +nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame, + const nsSize& aFrameSize) +{ + nsCSSShadowArray* boxShadows = aFrame->GetStyleBorder()->mBoxShadow; + if (!boxShadows) { + return nsRect(); + } + + nsRect shadows; + int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel(); + for (uint32_t i = 0; i < boxShadows->Length(); ++i) { + nsRect tmpRect(nsPoint(0, 0), aFrameSize); + nsCSSShadowItem* shadow = boxShadows->ShadowAt(i); + + // inset shadows are never painted outside the frame + if (shadow->mInset) + continue; + + tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset)); + tmpRect.Inflate(shadow->mSpread); + tmpRect.Inflate( + nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D)); + shadows.UnionRect(shadows, tmpRect); + } + return shadows; +} + diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 3d6cdb192302..1000ae806158 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1766,6 +1766,14 @@ public: static bool PointIsCloserToRect(PointType aPoint, const RectType& aRect, CoordType& aClosestXDistance, CoordType& aClosestYDistance); + /** + * Computes the box shadow rect for the frame, or returns an empty rect if + * there are no shadows. + * + * @param aFrame Frame to compute shadows for. + * @param aFrameSize Size of aFrame (in case it hasn't been set yet). + */ + static nsRect GetBoxShadowRectForFrame(nsIFrame* aFrame, const nsSize& aFrameSize); #ifdef DEBUG /** diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 38f8c5555768..a395cb8d4256 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -957,6 +957,10 @@ nsPresContext::Init(nsDeviceContext* aDeviceContext) } } + // Initialise refresh tick counters for OMTA + mLastStyleUpdateForAllAnimations = + mLastUpdateThrottledStyle = mRefreshDriver->MostRecentRefresh(); + mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID); // Register callbacks so we're notified when the preferences change diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index 0947090a8f33..62d6b48228b2 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -33,6 +33,7 @@ #include "mozilla/TimeStamp.h" #include "prclist.h" #include "Layers.h" +#include "nsRefreshDriver.h" #ifdef IBMBIDI class nsBidiPresUtils; @@ -66,7 +67,6 @@ struct nsFontFaceRuleContainer; class nsObjectFrame; class nsTransitionManager; class nsAnimationManager; -class nsRefreshDriver; class imgIContainer; class nsIDOMMediaQueryList; @@ -654,6 +654,22 @@ public: { mDrawColorBackground = aCanDraw; } + + /** + * Getter and setter for OMTA time counters + */ + bool ThrottledStyleIsUpToDate() const { + return mLastUpdateThrottledStyle == mRefreshDriver->MostRecentRefresh(); + } + void TickLastUpdateThrottledStyle() { + mLastUpdateThrottledStyle = mRefreshDriver->MostRecentRefresh(); + } + bool StyleUpdateForAllAnimationsIsUpToDate() const { + return mLastStyleUpdateForAllAnimations == mRefreshDriver->MostRecentRefresh(); + } + void TickLastStyleUpdateForAllAnimations() { + mLastStyleUpdateForAllAnimations = mRefreshDriver->MostRecentRefresh(); + } #ifdef IBMBIDI /** @@ -971,6 +987,17 @@ public: mUsesViewportUnits = aValue; } + // true if there are OMTA transition updates for the current document which + // have been throttled, and therefore some style information may not be up + // to date + bool ExistThrottledUpdates() const { + return mExistThrottledUpdates; + } + + void SetExistThrottledUpdates(bool aExistThrottledUpdates) { + mExistThrottledUpdates = aExistThrottledUpdates; + } + protected: friend class nsRunnableMethod; NS_HIDDEN_(void) ThemeChangedInternal(); @@ -1182,6 +1209,8 @@ protected: ScrollbarStyles mViewportStyleOverflow; uint8_t mFocusRingWidth; + bool mExistThrottledUpdates; + uint16_t mImageAnimationMode; uint16_t mImageAnimationModePref; @@ -1193,6 +1222,11 @@ protected: mozilla::TimeStamp mReflowStartTime; + // last time animations/transition styles were flushed to their primary frames + mozilla::TimeStamp mLastUpdateThrottledStyle; + // last time we did a full style flush + mozilla::TimeStamp mLastStyleUpdateForAllAnimations; + unsigned mHasPendingInterrupt : 1; unsigned mInterruptsEnabled : 1; unsigned mUseDocumentFonts : 1; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 209820bae6a5..b8ca429954ee 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -179,6 +179,7 @@ #include "mozilla/css/ImageLoader.h" #include "Layers.h" +#include "nsTransitionManager.h" #include "LayerTreeInvalidation.h" #include "nsAsyncDOMEvent.h" @@ -186,6 +187,7 @@ (nsIPresShell::SCROLL_OVERFLOW_HIDDEN | nsIPresShell::SCROLL_NO_PARENT_FRAMES) using namespace mozilla; +using namespace mozilla::css; using namespace mozilla::dom; using namespace mozilla::layers; @@ -3749,12 +3751,21 @@ PresShell::IsSafeToFlush() const void PresShell::FlushPendingNotifications(mozFlushType aType) +{ + // by default, flush animations if aType >= Flush_Style + mozilla::ChangesToFlush flush(aType, aType >= Flush_Style); + FlushPendingNotifications(flush); +} + +void +PresShell::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) { /** * VERY IMPORTANT: If you add some sort of new flushing to this * method, make sure to add the relevant SetNeedLayoutFlush or * SetNeedStyleFlush calls on the document. */ + mozFlushType flushType = aFlush.mFlushType; #ifdef MOZ_ENABLE_PROFILER_SPS static const char flushTypeNames[][20] = { @@ -3765,11 +3776,12 @@ PresShell::FlushPendingNotifications(mozFlushType aType) "Layout", "Display" }; + // Make sure that we don't miss things added to mozFlushType! - MOZ_ASSERT(static_cast(aType) <= ArrayLength(flushTypeNames)); + MOZ_ASSERT(static_cast(flushType) <= ArrayLength(flushTypeNames)); SAMPLE_LABEL_PRINTF("layout", "Flush", "(Flush_%s)", - flushTypeNames[aType - 1]); + flushTypeNames[flushType - 1]); #endif #ifdef ACCESSIBILITY @@ -3782,7 +3794,7 @@ PresShell::FlushPendingNotifications(mozFlushType aType) #endif #endif - NS_ASSERTION(aType >= Flush_Frames, "Why did we get called?"); + NS_ASSERTION(flushType >= Flush_Frames, "Why did we get called?"); bool isSafeToFlush = IsSafeToFlush(); @@ -3815,7 +3827,7 @@ PresShell::FlushPendingNotifications(mozFlushType aType) // filter to work). We only need external resources to be flushed when the // main document is flushing >= Flush_Frames, so we flush external // resources here instead of nsDocument::FlushPendingNotifications. - mDocument->FlushExternalResources(aType); + mDocument->FlushExternalResources(flushType); // Force flushing of any pending content notifications that might have // queued up while our event was pending. That will ensure that we don't @@ -3839,6 +3851,16 @@ PresShell::FlushPendingNotifications(mozFlushType aType) mDocument->GetAnimationController()->FlushResampleRequests(); } + if (aFlush.mFlushAnimations && + (!CommonAnimationManager::ThrottlingEnabled() || + !mPresContext->StyleUpdateForAllAnimationsIsUpToDate())) { + mPresContext->AnimationManager()-> + FlushAnimations(CommonAnimationManager::Cannot_Throttle); + mPresContext->TransitionManager()-> + FlushTransitions(CommonAnimationManager::Cannot_Throttle); + mPresContext->TickLastStyleUpdateForAllAnimations(); + } + // The FlushResampleRequests() above flushed style changes. if (!mIsDestroying) { nsAutoScriptBlocker scriptBlocker; @@ -3878,11 +3900,11 @@ PresShell::FlushPendingNotifications(mozFlushType aType) // worry about them. They can't be triggered during reflow, so we should // be good. - if (aType >= (mSuppressInterruptibleReflows ? Flush_Layout : Flush_InterruptibleLayout) && + if (flushType >= (mSuppressInterruptibleReflows ? Flush_Layout : Flush_InterruptibleLayout) && !mIsDestroying) { mFrameConstructor->RecalcQuotesAndCounters(); mViewManager->FlushDelayedResize(true); - if (ProcessReflowCommands(aType < Flush_Layout) && mContentToScrollTo) { + if (ProcessReflowCommands(flushType < Flush_Layout) && mContentToScrollTo) { // We didn't get interrupted. Go ahead and scroll to our content DoScrollContentIntoView(); if (mContentToScrollTo) { @@ -3891,13 +3913,13 @@ PresShell::FlushPendingNotifications(mozFlushType aType) } } } else if (!mIsDestroying && mSuppressInterruptibleReflows && - aType == Flush_InterruptibleLayout) { + flushType == Flush_InterruptibleLayout) { // We suppressed this flush, but the document thinks it doesn't // need to flush anymore. Let it know what's really going on. mDocument->SetNeedLayoutFlush(); } - if (aType >= Flush_Layout) { + if (flushType >= Flush_Layout) { if (!mIsDestroying) { mViewManager->UpdateWidgetGeometry(); } @@ -7114,7 +7136,7 @@ PresShell::WillPaint(bool aWillSendDidPaint) // reflow being interspersed. Note that we _do_ allow this to be // interruptible; if we can't do all the reflows it's better to flicker a bit // than to freeze up. - FlushPendingNotifications(Flush_InterruptibleLayout); + FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout, false)); } void diff --git a/layout/base/nsPresShell.h b/layout/base/nsPresShell.h index 0aa76c26a1f3..1927b303e7a4 100644 --- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -102,6 +102,7 @@ public: virtual NS_HIDDEN_(void) CancelAllPendingReflows(); virtual NS_HIDDEN_(bool) IsSafeToFlush() const; virtual NS_HIDDEN_(void) FlushPendingNotifications(mozFlushType aType); + virtual NS_HIDDEN_(void) FlushPendingNotifications(mozilla::ChangesToFlush aType); /** * Recreates the frames for a node diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index cf8ee866a7f7..7591b9a432b1 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -7,11 +7,27 @@ /* * Code to notify things that animate before a refresh, at an appropriate * refresh rate. (Perhaps temporary, until replaced by compositor.) + * + * Chrome and each tab have their own RefreshDriver, which in turn + * hooks into one of a few global timer based on RefreshDriverTimer, + * defined below. There are two main global timers -- one for active + * animations, and one for inactive ones. These are implemented as + * subclasses of RefreshDriverTimer; see below for a description of + * their implementations. In the future, additional timer types may + * implement things like blocking on vsync. */ +#ifdef XP_WIN +#include +// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have +// to manually include it +#include +#endif + #include "mozilla/Util.h" #include "nsRefreshDriver.h" +#include "nsITimer.h" #include "nsPresContext.h" #include "nsComponentManagerUtils.h" #include "prlog.h" @@ -27,19 +43,412 @@ #include "sampler.h" #include "nsNPAPIPluginInstance.h" +using mozilla::TimeStamp; +using mozilla::TimeDuration; + using namespace mozilla; +#ifdef PR_LOGGING +static PRLogModuleInfo *gLog = nullptr; +#define LOG(...) PR_LOG(gLog, PR_LOG_NOTICE, (__VA_ARGS__)) +#else +#define LOG(...) do { } while(0) +#endif + #define DEFAULT_FRAME_RATE 60 #define DEFAULT_THROTTLED_FRAME_RATE 1 +// after 10 minutes, stop firing off inactive timers +#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600 -static bool sPrecisePref; +namespace mozilla { + +/* + * The base class for all global refresh driver timers. It takes care + * of managing the list of refresh drivers attached to them and + * provides interfaces for querying/setting the rate and actually + * running a timer 'Tick'. Subclasses must implement StartTimer(), + * StopTimer(), and ScheduleNextTick() -- the first two just + * start/stop whatever timer mechanism is in use, and ScheduleNextTick + * is called at the start of the Tick() implementation to set a time + * for the next tick. + */ +class RefreshDriverTimer { +public: + /* + * aRate -- the delay, in milliseconds, requested between timer firings + */ + RefreshDriverTimer(double aRate) + { + SetRate(aRate); + } + + virtual ~RefreshDriverTimer() + { + NS_ASSERTION(mRefreshDrivers.Length() == 0, "Should have removed all refresh drivers from here by now!"); + } + + virtual void AddRefreshDriver(nsRefreshDriver* aDriver) + { + LOG("[%p] AddRefreshDriver %p", this, aDriver); + + NS_ASSERTION(!mRefreshDrivers.Contains(aDriver), "AddRefreshDriver for a refresh driver that's already in the list!"); + mRefreshDrivers.AppendElement(aDriver); + + if (mRefreshDrivers.Length() == 1) { + StartTimer(); + } + } + + virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver) + { + LOG("[%p] RemoveRefreshDriver %p", this, aDriver); + + NS_ASSERTION(mRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the list!"); + mRefreshDrivers.RemoveElement(aDriver); + + if (mRefreshDrivers.Length() == 0) { + StopTimer(); + } + } + + double GetRate() const + { + return mRateMilliseconds; + } + + // will take effect at next timer tick + virtual void SetRate(double aNewRate) + { + mRateMilliseconds = aNewRate; + mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds); + } + + TimeStamp MostRecentRefresh() const { return mLastFireTime; } + int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; } + +protected: + virtual void StartTimer() = 0; + virtual void StopTimer() = 0; + virtual void ScheduleNextTick(TimeStamp aNowTime) = 0; + + /* + * Actually runs a tick, poking all the attached RefreshDrivers. + * Grabs the "now" time via JS_Now and TimeStamp::Now(). + */ + void Tick() + { + int64_t jsnow = JS_Now(); + TimeStamp now = TimeStamp::Now(); + + ScheduleNextTick(now); + + mLastFireEpoch = jsnow; + mLastFireTime = now; + + LOG("[%p] ticking drivers...", this); + nsTArray > drivers(mRefreshDrivers); + for (size_t i = 0; i < drivers.Length(); ++i) { + // don't poke this driver if it's in test mode + if (drivers[i]->IsTestControllingRefreshesEnabled()) { + continue; + } + + TickDriver(drivers[i], jsnow, now); + } + LOG("[%p] done.", this); + } + + static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now) + { + LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow); + driver->Tick(jsnow, now); + } + + double mRateMilliseconds; + TimeDuration mRateDuration; + + int64_t mLastFireEpoch; + TimeStamp mLastFireTime; + TimeStamp mTargetTime; + + nsTArray > mRefreshDrivers; + + // useful callback for nsITimer-based derived classes, here + // bacause of c++ protected shenanigans + static void TimerTick(nsITimer* aTimer, void* aClosure) + { + RefreshDriverTimer *timer = static_cast(aClosure); + timer->Tick(); + } +}; + +/* + * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that + * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to + * implement ScheduleNextTick and intelligently calculate the next time to tick, + * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain + * with its attempt at intelligent slack removal and such, so we don't do it. + */ +class SimpleTimerBasedRefreshDriverTimer : + public RefreshDriverTimer +{ +public: + SimpleTimerBasedRefreshDriverTimer(double aRate) + : RefreshDriverTimer(aRate) + { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + virtual ~SimpleTimerBasedRefreshDriverTimer() + { + StopTimer(); + } + +protected: + + virtual void StartTimer() + { + mLastFireTime = TimeStamp::Now(); + mTargetTime = mLastFireTime; + + mTimer->InitWithFuncCallback(TimerTick, this, 0, nsITimer::TYPE_ONE_SHOT); + } + + virtual void StopTimer() + { + mTimer->Cancel(); + } + + nsRefPtr mTimer; +}; + +/* + * PreciseRefreshDriverTimer schedules ticks based on the current time + * and when the next tick -should- be sent if we were hitting our + * rate. It always schedules ticks on multiples of aRate -- meaning that + * if some execution takes longer than an alloted slot, the next tick + * will be delayed instead of triggering instantly. This might not be + * desired -- there's an #if 0'd block below that we could put behind + * a pref to control this behaviour. + */ +class PreciseRefreshDriverTimer : + public SimpleTimerBasedRefreshDriverTimer +{ +public: + PreciseRefreshDriverTimer(double aRate) + : SimpleTimerBasedRefreshDriverTimer(aRate) + { + } + +protected: + virtual void ScheduleNextTick(TimeStamp aNowTime) + { + // The number of (whole) elapsed intervals between the last target + // time and the actual time. We want to truncate the double down + // to an int number of intervals. + int numElapsedIntervals = static_cast((aNowTime - mTargetTime) / mRateDuration); + + // the last "tick" that may or may not have been actually sent was + // at this time. For example, if the rate is 15ms, the target + // time is 200ms, and it's now 225ms, the last effective tick + // would have been at 215ms. The next one should then be + // scheduled for 5 ms from now. + // + // We then add another mRateDuration to find the next tick target. + TimeStamp newTarget = mTargetTime + mRateDuration * (numElapsedIntervals + 1); + + // the amount of (integer) ms until the next time we should tick + uint32_t delay = static_cast((newTarget - aNowTime).ToMilliseconds()); + + // Without this block, we'll always schedule on interval ticks; + // with it, we'll schedule immediately if we missed our tick target + // last time. +#if 0 + if (numElapsedIntervals > 0) { + // we're late, so reset + newTarget = aNowTime; + delay = 0; + } +#endif + + // log info & lateness + LOG("[%p] precise timer last tick late by %f ms, next tick in %d ms", + this, + (aNowTime - mTargetTime).ToMilliseconds(), + delay); + + // then schedule the timer + mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); + + mTargetTime = newTarget; + } +}; + +/* + * A RefreshDriverTimer for inactive documents. When a new refresh driver is + * added, the rate is reset to the base (normally 1s/1fps). Every time + * it ticks, a single refresh driver is poked. Once they have all been poked, + * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point, + * the timer is quiet and doesn't tick (until something is added to it again). + * + * When a timer is removed, there is a possibility of another timer + * being skipped for one cycle. We could avoid this by adjusting + * mNextDriverIndex in RemoveRefreshDriver, but there's little need to + * add that complexity. All we want is for inactive drivers to tick + * at some point, but we don't care too much about how often. + */ +class InactiveRefreshDriverTimer : + public RefreshDriverTimer +{ +public: + InactiveRefreshDriverTimer(double aRate) + : RefreshDriverTimer(aRate), + mNextTickDuration(aRate), + mDisableAfterMilliseconds(-1.0), + mNextDriverIndex(0) + { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds) + : RefreshDriverTimer(aRate), + mNextTickDuration(aRate), + mDisableAfterMilliseconds(aDisableAfterMilliseconds), + mNextDriverIndex(0) + { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + virtual void AddRefreshDriver(nsRefreshDriver* aDriver) + { + RefreshDriverTimer::AddRefreshDriver(aDriver); + + LOG("[%p] inactive timer got new refresh driver %p, resetting rate", + this, aDriver); + + // reset the timer, and start with the newly added one next time. + mNextTickDuration = mRateMilliseconds; + + // we don't really have to start with the newly added one, but we may as well + // not tick the old ones at the fastest rate any more than we need to. + mNextDriverIndex = mRefreshDrivers.Length() - 1; + + StopTimer(); + StartTimer(); + } + +protected: + virtual void StartTimer() + { + mLastFireTime = TimeStamp::Now(); + mTargetTime = mLastFireTime; + + mTimer->InitWithFuncCallback(TimerTickOne, this, 0, nsITimer::TYPE_ONE_SHOT); + } + + virtual void StopTimer() + { + mTimer->Cancel(); + } + + virtual void ScheduleNextTick(TimeStamp aNowTime) + { + if (mDisableAfterMilliseconds > 0.0 && + mNextTickDuration > mDisableAfterMilliseconds) + { + // We hit the time after which we should disable + // inactive window refreshes; don't schedule anything + // until we get kicked by an AddRefreshDriver call. + return; + } + + // double the next tick time if we've already gone through all of them once + if (mNextDriverIndex >= mRefreshDrivers.Length()) { + mNextTickDuration *= 2.0; + mNextDriverIndex = 0; + } + + // this doesn't need to be precise; do a simple schedule + uint32_t delay = static_cast(mNextTickDuration); + mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT); + + LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration, + mNextDriverIndex, mRefreshDrivers.Length()); + } + + /* Runs just one driver's tick. */ + void TickOne() + { + int64_t jsnow = JS_Now(); + TimeStamp now = TimeStamp::Now(); + + ScheduleNextTick(now); + + mLastFireEpoch = jsnow; + mLastFireTime = now; + + nsTArray > drivers(mRefreshDrivers); + if (mNextDriverIndex < drivers.Length() && + !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled()) + { + TickDriver(drivers[mNextDriverIndex], jsnow, now); + } + + mNextDriverIndex++; + } + + static void TimerTickOne(nsITimer* aTimer, void* aClosure) + { + InactiveRefreshDriverTimer *timer = static_cast(aClosure); + timer->TickOne(); + } + + nsRefPtr mTimer; + double mNextTickDuration; + double mDisableAfterMilliseconds; + uint32_t mNextDriverIndex; +}; + +} // namespace mozilla + +static PreciseRefreshDriverTimer *sRegularRateTimer = nullptr; +static InactiveRefreshDriverTimer *sThrottledRateTimer = nullptr; + +#ifdef XP_WIN +static int32_t sHighPrecisionTimerRequests = 0; +// a bare pointer to avoid introducing a static constructor +static nsITimer *sDisableHighPrecisionTimersTimer = nullptr; +#endif /* static */ void nsRefreshDriver::InitializeStatics() { - Preferences::AddBoolVarCache(&sPrecisePref, - "layout.frame_rate.precise", - false); +#ifdef PR_LOGGING + if (!gLog) { + gLog = PR_NewLogModule("nsRefreshDriver"); + } +#endif +} + +/* static */ void +nsRefreshDriver::Shutdown() +{ + // clean up our timers + delete sRegularRateTimer; + delete sThrottledRateTimer; + + sRegularRateTimer = nullptr; + sThrottledRateTimer = nullptr; + +#ifdef XP_WIN + if (sDisableHighPrecisionTimersTimer) { + sDisableHighPrecisionTimersTimer->Cancel(); + NS_RELEASE(sDisableHighPrecisionTimersTimer); + timeEndPeriod(1); + } else if (sHighPrecisionTimerRequests) { + timeEndPeriod(1); + } +#endif } /* static */ int32_t @@ -50,46 +459,60 @@ nsRefreshDriver::DefaultInterval() // Compute the interval to use for the refresh driver timer, in // milliseconds -int32_t -nsRefreshDriver::GetRefreshTimerInterval() const +double +nsRefreshDriver::GetRegularTimerInterval() const { - const char* prefName = - mThrottled ? "layout.throttled_frame_rate" : "layout.frame_rate"; - int32_t rate = Preferences::GetInt(prefName, -1); + int32_t rate = Preferences::GetInt("layout.frame_rate", -1); if (rate <= 0) { // TODO: get the rate from the platform - rate = mThrottled ? DEFAULT_THROTTLED_FRAME_RATE : DEFAULT_FRAME_RATE; + rate = DEFAULT_FRAME_RATE; } - NS_ASSERTION(rate > 0, "Must have positive rate here"); - int32_t interval = NSToIntRound(1000.0/rate); - if (mThrottled) { - interval = NS_MAX(interval, mLastTimerInterval * 2); - } - mLastTimerInterval = interval; - return interval; + return 1000.0 / rate; } -int32_t -nsRefreshDriver::GetRefreshTimerType() const +double +nsRefreshDriver::GetThrottledTimerInterval() const +{ + int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1); + if (rate <= 0) { + rate = DEFAULT_THROTTLED_FRAME_RATE; + } + return 1000.0 / rate; +} + +double +nsRefreshDriver::GetRefreshTimerInterval() const +{ + return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval(); +} + +RefreshDriverTimer* +nsRefreshDriver::ChooseTimer() const { if (mThrottled) { - return nsITimer::TYPE_ONE_SHOT; + if (!sThrottledRateTimer) + sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(), + DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0); + return sThrottledRateTimer; } - if (HaveFrameRequestCallbacks() || sPrecisePref) { - return nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP; - } - return nsITimer::TYPE_REPEATING_SLACK; + + if (!sRegularRateTimer) + sRegularRateTimer = new PreciseRefreshDriverTimer(GetRegularTimerInterval()); + return sRegularRateTimer; } -nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext) - : mPresContext(aPresContext), +nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext) + : mActiveTimer(nullptr), + mPresContext(aPresContext), mFrozen(false), mThrottled(false), mTestControllingRefreshes(false), - mTimerIsPrecise(false), mViewManagerFlushIsPending(false), - mLastTimerInterval(0) + mRequestedHighPrecision(false) { + mMostRecentRefreshEpochTime = JS_Now(); + mMostRecentRefresh = TimeStamp::Now(); + mRequests.Init(); } @@ -97,7 +520,7 @@ nsRefreshDriver::~nsRefreshDriver() { NS_ABORT_IF_FALSE(ObserverCount() == 0, "observers should have unregistered"); - NS_ABORT_IF_FALSE(!mTimer, "timer should be gone"); + NS_ABORT_IF_FALSE(!mActiveTimer, "timer should be gone"); for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); @@ -110,12 +533,22 @@ nsRefreshDriver::~nsRefreshDriver() void nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) { - mTestControllingRefreshes = true; + // ensure that we're removed from our driver + StopTimer(); + + if (!mTestControllingRefreshes) { + mMostRecentRefreshEpochTime = JS_Now(); + mMostRecentRefresh = TimeStamp::Now(); + + mTestControllingRefreshes = true; + } + mMostRecentRefreshEpochTime += aMilliseconds * 1000; - mMostRecentRefresh += TimeDuration::FromMilliseconds(aMilliseconds); + mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds); + nsCxPusher pusher; if (pusher.PushNull()) { - Notify(nullptr); + DoTick(); pusher.Pop(); } } @@ -124,31 +557,23 @@ void nsRefreshDriver::RestoreNormalRefresh() { mTestControllingRefreshes = false; - nsCxPusher pusher; - if (pusher.PushNull()) { - Notify(nullptr); // will call UpdateMostRecentRefresh() - pusher.Pop(); - } + EnsureTimerStarted(false); } TimeStamp nsRefreshDriver::MostRecentRefresh() const { - const_cast(this)->EnsureTimerStarted(false); - return mMostRecentRefresh; } int64_t nsRefreshDriver::MostRecentRefreshEpochTime() const { - const_cast(this)->EnsureTimerStarted(false); - return mMostRecentRefreshEpochTime; } bool -nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver, +nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -160,7 +585,7 @@ nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver, } bool -nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver, +nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -193,47 +618,108 @@ void nsRefreshDriver::ClearAllImageRequests() void nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer) { - if (mTimer || mFrozen || !mPresContext) { - // It's already been started, or we don't want to start it now or - // we've been disconnected. + if (mTestControllingRefreshes) + return; + + // will it already fire, and no other changes needed? + if (mActiveTimer && !aAdjustingTimer) + return; + + if (mFrozen || !mPresContext) { + // If we don't want to start it now, or we've been disconnected. + StopTimer(); return; } - if (!aAdjustingTimer) { - // If we didn't already have a timer and aAdjustingTimer is false, - // then we just got our first observer (or an explicit call to - // MostRecentRefresh by a caller who's likely to add an observer - // shortly). This means we should fake a most-recent-refresh time - // of now so that said observer gets a reasonable refresh time, so - // things behave as though the timer had always been running. - UpdateMostRecentRefresh(); - } - - mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - if (!mTimer) { - return; - } - - int32_t timerType = GetRefreshTimerType(); - mTimerIsPrecise = (timerType == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); - - nsresult rv = mTimer->InitWithCallback(this, - GetRefreshTimerInterval(), - timerType); - if (NS_FAILED(rv)) { - mTimer = nullptr; + // We got here because we're either adjusting the time *or* we're + // starting it for the first time. Add to the right timer, + // prehaps removing it from a previously-set one. + RefreshDriverTimer *newTimer = ChooseTimer(); + if (newTimer != mActiveTimer) { + if (mActiveTimer) + mActiveTimer->RemoveRefreshDriver(this); + mActiveTimer = newTimer; + mActiveTimer->AddRefreshDriver(this); } } void nsRefreshDriver::StopTimer() { - if (!mTimer) { + if (!mActiveTimer) return; - } - mTimer->Cancel(); - mTimer = nullptr; + mActiveTimer->RemoveRefreshDriver(this); + mActiveTimer = nullptr; + + if (mRequestedHighPrecision) { + SetHighPrecisionTimersEnabled(false); + } +} + +#ifdef XP_WIN +static void +DisableHighPrecisionTimersCallback(nsITimer *aTimer, void *aClosure) +{ + timeEndPeriod(1); + NS_RELEASE(sDisableHighPrecisionTimersTimer); +} +#endif + +void +nsRefreshDriver::ConfigureHighPrecision() +{ + bool haveFrameRequestCallbacks = mFrameRequestCallbackDocs.Length() > 0; + + // if the only change that's needed is that we need high precision, + // then just set that + if (!mThrottled && !mRequestedHighPrecision && haveFrameRequestCallbacks) { + SetHighPrecisionTimersEnabled(true); + } else if (mRequestedHighPrecision && !haveFrameRequestCallbacks) { + SetHighPrecisionTimersEnabled(false); + } +} + +void +nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable) +{ + LOG("[%p] SetHighPrecisionTimersEnabled (%s)", this, aEnable ? "true" : "false"); + + if (aEnable) { + NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!"); +#ifdef XP_WIN + if (++sHighPrecisionTimerRequests == 1) { + // If we had a timer scheduled to disable it, that means that it's already + // enabled; just cancel the timer. Otherwise, really enable it. + if (sDisableHighPrecisionTimersTimer) { + sDisableHighPrecisionTimersTimer->Cancel(); + NS_RELEASE(sDisableHighPrecisionTimersTimer); + } else { + timeBeginPeriod(1); + } + } +#endif + mRequestedHighPrecision = true; + } else { + NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!"); +#ifdef XP_WIN + if (--sHighPrecisionTimerRequests == 0) { + // Don't jerk us around between high precision and low precision + // timers; instead, only allow leaving high precision timers + // after 90 seconds. This is arbitrary, but hopefully good + // enough. + NS_ASSERTION(!sDisableHighPrecisionTimersTimer, "We shouldn't have an outstanding disable-high-precision timer !"); + + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); + timer.forget(&sDisableHighPrecisionTimersTimer); + sDisableHighPrecisionTimersTimer->InitWithFuncCallback(DisableHighPrecisionTimersCallback, + nullptr, + 90 * 1000, + nsITimer::TYPE_ONE_SHOT); + } +#endif + mRequestedHighPrecision = false; + } } uint32_t @@ -261,18 +747,6 @@ nsRefreshDriver::ImageRequestCount() const return mRequests.Count(); } -void -nsRefreshDriver::UpdateMostRecentRefresh() -{ - if (mTestControllingRefreshes) { - return; - } - - // Call JS_Now first, since that can have nonzero latency in some rare cases. - mMostRecentRefreshEpochTime = JS_Now(); - mMostRecentRefresh = TimeStamp::Now(); -} - nsRefreshDriver::ObserverArray& nsRefreshDriver::ArrayFor(mozFlushType aFlushType) { @@ -293,33 +767,50 @@ nsRefreshDriver::ArrayFor(mozFlushType aFlushType) * nsISupports implementation */ -NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsITimerCallback) +NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsISupports) /* * nsITimerCallback implementation */ -NS_IMETHODIMP -nsRefreshDriver::Notify(nsITimer *aTimer) +void +nsRefreshDriver::DoTick() { - SAMPLE_LABEL("nsRefreshDriver", "Notify"); - NS_PRECONDITION(!mFrozen, "Why are we notified while frozen?"); NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?"); NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(), "Shouldn't have a JSContext on the stack"); + + if (mTestControllingRefreshes) { + Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh); + } else { + Tick(JS_Now(), TimeStamp::Now()); + } +} + +void +nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) +{ + NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(), + "Shouldn't have a JSContext on the stack"); + if (nsNPAPIPluginInstance::InPluginCall()) { NS_ERROR("Refresh driver should not run during plugin call!"); // Try to survive this by just ignoring the refresh tick. - return NS_OK; + return; } - if (mTestControllingRefreshes && aTimer) { - // Ignore real refreshes from our timer (but honor the others). - return NS_OK; + SAMPLE_LABEL("nsRefreshDriver", "Tick"); + + // We're either frozen or we were disconnected (likely in the middle + // of a tick iteration). Just do nothing here, since our + // prescontext went away. + if (mFrozen || !mPresContext) { + return; } - UpdateMostRecentRefresh(); + mMostRecentRefresh = aNowTime; + mMostRecentRefreshEpochTime = aNowEpoch; nsCOMPtr presShell = mPresContext->GetPresShell(); if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) { @@ -331,7 +822,7 @@ nsRefreshDriver::Notify(nsITimer *aTimer) // wait until we get a Notify() call when we have no observers // before stopping the timer. StopTimer(); - return NS_OK; + return; } /* @@ -344,11 +835,11 @@ nsRefreshDriver::Notify(nsITimer *aTimer) ObserverArray::EndLimitedIterator etor(mObservers[i]); while (etor.HasMore()) { nsRefPtr obs = etor.GetNext(); - obs->WillRefresh(mMostRecentRefresh); + obs->WillRefresh(aNowTime); if (!mPresContext || !mPresContext->GetPresShell()) { StopTimer(); - return NS_OK; + return; } } @@ -363,7 +854,7 @@ nsRefreshDriver::Notify(nsITimer *aTimer) // readded as needed. mFrameRequestCallbackDocs.Clear(); - int64_t eventTime = mMostRecentRefreshEpochTime / PR_USEC_PER_MSEC; + int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC; for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) { nsAutoMicroTask mt; frameRequestCallbacks[i]->Sample(eventTime); @@ -383,7 +874,7 @@ nsRefreshDriver::Notify(nsITimer *aTimer) NS_ADDREF(shell); mStyleFlushObservers.RemoveElement(shell); shell->FrameConstructor()->mObservingRefreshDriver = false; - shell->FlushPendingNotifications(Flush_Style); + shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false)); NS_RELEASE(shell); } } @@ -403,7 +894,8 @@ nsRefreshDriver::Notify(nsITimer *aTimer) mLayoutFlushObservers.RemoveElement(shell); shell->mReflowScheduled = false; shell->mSuppressInterruptibleReflows = false; - shell->FlushPendingNotifications(Flush_InterruptibleLayout); + shell->FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout, + false)); NS_RELEASE(shell); } } @@ -415,10 +907,9 @@ nsRefreshDriver::Notify(nsITimer *aTimer) * for refresh events. */ - ImageRequestParameters parms = {mMostRecentRefresh}; + ImageRequestParameters parms = {aNowTime}; if (mRequests.Count()) { mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms); - EnsureTimerStarted(false); } for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { @@ -437,25 +928,6 @@ nsRefreshDriver::Notify(nsITimer *aTimer) printf("Ending ProcessPendingUpdates\n"); #endif } - - if (mThrottled || - (mTimerIsPrecise != - (GetRefreshTimerType() == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP))) { - // Stop the timer now and restart it here. Stopping is in the mThrottled - // case ok because either it's already one-shot, and it just fired, and all - // we need to do is null it out, or it's repeating and we need to reset it - // to be one-shot. Stopping and restarting in the case when we need to - // switch from precise to slack timers or vice versa is unfortunately - // required. - - // Note that the EnsureTimerStarted() call here is ok because - // EnsureTimerStarted makes sure to not start the timer if it shouldn't be - // started. - StopTimer(); - EnsureTimerStarted(true); - } - - return NS_OK; } PLDHashOperator @@ -503,10 +975,9 @@ nsRefreshDriver::SetThrottled(bool aThrottled) { if (aThrottled != mThrottled) { mThrottled = aThrottled; - if (mTimer) { + if (mActiveTimer) { // We want to switch our timer type here, so just stop and // restart the timer. - StopTimer(); EnsureTimerStarted(true); } } @@ -516,14 +987,14 @@ void nsRefreshDriver::DoRefresh() { // Don't do a refresh unless we're in a state where we should be refreshing. - if (!mFrozen && mPresContext && mTimer) { - Notify(nullptr); + if (!mFrozen && mPresContext && mActiveTimer) { + DoTick(); } } #ifdef DEBUG bool -nsRefreshDriver::IsRefreshObserver(nsARefreshObserver *aObserver, +nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -547,8 +1018,9 @@ nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument) mFrameRequestCallbackDocs.NoIndex, "Don't schedule the same document multiple times"); mFrameRequestCallbackDocs.AppendElement(aDocument); - // No need to worry about restarting our timer in precise mode if it's - // already running; that will happen automatically when it fires. + + // make sure that the timer is running + ConfigureHighPrecision(); EnsureTimerStarted(false); } @@ -556,6 +1028,7 @@ void nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument) { mFrameRequestCallbackDocs.RemoveElement(aDocument); + ConfigureHighPrecision(); // No need to worry about restarting our timer in slack mode if it's already // running; that will happen automatically when it fires. } diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index 0347253bd869..e7d7998d7627 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -1,3 +1,4 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ /* 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 @@ -13,7 +14,6 @@ #include "mozilla/TimeStamp.h" #include "mozFlushType.h" -#include "nsITimer.h" #include "nsCOMPtr.h" #include "nsTObserverArray.h" #include "nsTArray.h" @@ -32,6 +32,10 @@ class imgIRequest; * notified at refresh times. When nothing needs to be painted, callers * may not be notified. */ +namespace mozilla { + class RefreshDriverTimer; +} + class nsARefreshObserver { public: // AddRef and Release signatures that match nsISupports. Implementors @@ -46,25 +50,28 @@ public: virtual void WillRefresh(mozilla::TimeStamp aTime) = 0; }; -class nsRefreshDriver MOZ_FINAL : public nsITimerCallback { +class nsRefreshDriver MOZ_FINAL : public nsISupports { public: nsRefreshDriver(nsPresContext *aPresContext); ~nsRefreshDriver(); static void InitializeStatics(); + static void Shutdown(); // nsISupports implementation NS_DECL_ISUPPORTS - // nsITimerCallback implementation - NS_DECL_NSITIMERCALLBACK - /** * Methods for testing, exposed via nsIDOMWindowUtils. See * nsIDOMWindowUtils.advanceTimeAndRefresh for description. */ void AdvanceTimeAndRefresh(int64_t aMilliseconds); void RestoreNormalRefresh(); + void DoTick(); + bool IsTestControllingRefreshesEnabled() const + { + return mTestControllingRefreshes; + } /** * Return the time of the most recent refresh. This is intended to be @@ -226,6 +233,8 @@ private: typedef nsTObserverArray ObserverArray; typedef nsTHashtable RequestTable; + void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime); + void EnsureTimerStarted(bool aAdjustingTimer); void StopTimer(); @@ -233,22 +242,20 @@ private: uint32_t ImageRequestCount() const; static PLDHashOperator ImageRequestEnumerator(nsISupportsHashKey* aEntry, void* aUserArg); - void UpdateMostRecentRefresh(); ObserverArray& ArrayFor(mozFlushType aFlushType); // Trigger a refresh immediately, if haven't been disconnected or frozen. void DoRefresh(); - int32_t GetRefreshTimerInterval() const; - int32_t GetRefreshTimerType() const; + double GetRefreshTimerInterval() const; + double GetRegularTimerInterval() const; + double GetThrottledTimerInterval() const; bool HaveFrameRequestCallbacks() const { return mFrameRequestCallbackDocs.Length() != 0; } - nsCOMPtr mTimer; - mozilla::TimeStamp mMostRecentRefresh; // only valid when mTimer non-null - int64_t mMostRecentRefreshEpochTime; // same thing as mMostRecentRefresh, - // but in microseconds since the epoch. + mozilla::RefreshDriverTimer* ChooseTimer() const; + mozilla::RefreshDriverTimer *mActiveTimer; nsPresContext *mPresContext; // weak; pres context passed in constructor // and unset in Disconnect @@ -256,11 +263,11 @@ private: bool mFrozen; bool mThrottled; bool mTestControllingRefreshes; - /* If mTimer is non-null, this boolean indicates whether the timer is - a precise timer. If mTimer is null, this boolean's value can be - anything. */ - bool mTimerIsPrecise; bool mViewManagerFlushIsPending; + bool mRequestedHighPrecision; + + int64_t mMostRecentRefreshEpochTime; + mozilla::TimeStamp mMostRecentRefresh; // separate arrays for each flush type we support ObserverArray mObservers[3]; @@ -272,14 +279,16 @@ private: // nsTArray on purpose, because we want to be able to swap. nsTArray mFrameRequestCallbackDocs; - // This is the last interval we used for our timer. May be 0 if we - // haven't computed a timer interval yet. - mutable int32_t mLastTimerInterval; - // Helper struct for processing image requests struct ImageRequestParameters { mozilla::TimeStamp ts; }; + + friend class mozilla::RefreshDriverTimer; + + // turn on or turn off high precision based on various factors + void ConfigureHighPrecision(); + void SetHighPrecisionTimersEnabled(bool aEnable); }; #endif /* !defined(nsRefreshDriver_h_) */ diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index 876c78b459a7..6265fcad0a9c 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -366,4 +366,6 @@ nsLayoutStatics::Shutdown() AudioChannelService::Shutdown(); ContentParent::ShutDown(); + + nsRefreshDriver::Shutdown(); } diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index df90e611accc..771d89780e2d 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -5110,27 +5110,7 @@ ComputeOutlineAndEffectsRect(nsIFrame* aFrame, } // box-shadow - nsCSSShadowArray* boxShadows = aFrame->GetStyleBorder()->mBoxShadow; - if (boxShadows) { - nsRect shadows; - int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel(); - for (uint32_t i = 0; i < boxShadows->Length(); ++i) { - nsRect tmpRect(nsPoint(0, 0), aNewSize); - nsCSSShadowItem* shadow = boxShadows->ShadowAt(i); - - // inset shadows are never painted outside the frame - if (shadow->mInset) - continue; - - tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset)); - tmpRect.Inflate(shadow->mSpread, shadow->mSpread); - tmpRect.Inflate( - nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D)); - - shadows.UnionRect(shadows, tmpRect); - } - r.UnionRect(r, shadows); - } + r.UnionRect(r, nsLayoutUtils::GetBoxShadowRectForFrame(aFrame, aNewSize)); const nsStyleOutline* outline = aFrame->GetStyleOutline(); uint8_t outlineStyle = outline->GetOutlineStyle(); diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h index 3d739f55e543..492e25a96c04 100644 --- a/layout/generic/nsGfxScrollFrame.h +++ b/layout/generic/nsGfxScrollFrame.h @@ -470,6 +470,9 @@ public: virtual nsPoint GetScrollPosition() const MOZ_OVERRIDE { return mInner.GetScrollPosition(); } + virtual nsPoint GetLogicalScrollPosition() const MOZ_OVERRIDE { + return mInner.GetLogicalScrollPosition(); + } virtual nsRect GetScrollRange() const MOZ_OVERRIDE { return mInner.GetScrollRange(); } @@ -720,6 +723,9 @@ public: virtual nsPoint GetScrollPosition() const MOZ_OVERRIDE { return mInner.GetScrollPosition(); } + virtual nsPoint GetLogicalScrollPosition() const MOZ_OVERRIDE { + return mInner.GetLogicalScrollPosition(); + } virtual nsRect GetScrollRange() const MOZ_OVERRIDE { return mInner.GetScrollRange(); } diff --git a/layout/generic/nsIScrollableFrame.h b/layout/generic/nsIScrollableFrame.h index 0e334d224cd0..912d4cafda7e 100644 --- a/layout/generic/nsIScrollableFrame.h +++ b/layout/generic/nsIScrollableFrame.h @@ -93,6 +93,10 @@ public: * This will always be a multiple of device pixels. */ virtual nsPoint GetScrollPosition() const = 0; + /** + * As GetScrollPosition(), but uses the top-right as origin for RTL frames. + */ + virtual nsPoint GetLogicalScrollPosition() const = 0; /** * Get the area that must contain the scroll position. Typically * (but not always, e.g. for RTL content) x and y will be 0, and diff --git a/layout/style/AnimationCommon.cpp b/layout/style/AnimationCommon.cpp index bb1c56e41331..e4728ee52640 100644 --- a/layout/style/AnimationCommon.cpp +++ b/layout/style/AnimationCommon.cpp @@ -6,11 +6,19 @@ #include "gfxPlatform.h" #include "AnimationCommon.h" #include "nsRuleData.h" +#include "nsCSSFrameConstructor.h" #include "nsCSSValue.h" #include "nsStyleContext.h" #include "nsIFrame.h" #include "nsAnimationManager.h" #include "nsLayoutUtils.h" +#include "mozilla/LookAndFeel.h" +#include "Layers.h" +#include "FrameLayerBuilder.h" +#include "nsDisplayList.h" +#include "mozilla/Preferences.h" + +using namespace mozilla::layers; namespace mozilla { namespace css { @@ -151,6 +159,22 @@ CommonAnimationManager::ExtractComputedValueForTransition( return result; } +/* static */ bool +CommonAnimationManager::ThrottlingEnabled() +{ + static bool sThrottlePref = false; + static bool sThrottlePrefCached = false; + + if (!sThrottlePrefCached) { + Preferences::AddBoolVarCache(&sThrottlePref, + "layers.offmainthreadcomposition.throttle-animations", false); + sThrottlePrefCached = true; + } + + return sThrottlePref; +} + + NS_IMPL_ISUPPORTS1(AnimValuesStyleRule, nsIStyleRule) /* virtual */ void @@ -243,7 +267,7 @@ ComputedTimingFunction::GetValue(double aPortion) const bool CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *aElement, nsCSSProperty aProperty, - bool aHasGeometricProperties) + CanAnimateFlags aFlags) { bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled(); if (shouldLog && !gfxPlatform::OffMainThreadCompositingEnabled()) { @@ -258,8 +282,8 @@ CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *a if (shouldLog) { nsCString message; message.AppendLiteral("Performance warning: Async animation of geometric property '"); - message.Append(aProperty); - message.AppendLiteral(" is disabled"); + message.Append(nsCSSProps::GetStringValue(aProperty)); + message.AppendLiteral("' is disabled"); LogAsyncAnimationFailure(message, aElement); } return false; @@ -291,7 +315,7 @@ CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *a } return false; } - if (aHasGeometricProperties) { + if (aFlags & CanAnimate_HasGeometricProperty) { if (shouldLog) { nsCString message; message.AppendLiteral("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties"); @@ -307,7 +331,7 @@ CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *a } return enabled; } - return true; + return aFlags & CanAnimate_AllowPartial; } /* static */ void @@ -326,8 +350,85 @@ CommonElementAnimationData::LogAsyncAnimationFailure(nsCString& aMessage, } aMessage.AppendLiteral("]"); } + aMessage.AppendLiteral("\n"); printf_stderr(aMessage.get()); } +bool +CommonElementAnimationData::CanThrottleTransformChanges(TimeStamp aTime) +{ + if (!CommonAnimationManager::ThrottlingEnabled()) { + return false; + } + + // If we know that the animation cannot cause overflow, + // we can just disable flushes for this animation. + + // If we don't show scrollbars, we don't care about overflow. + if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) { + return true; + } + + // If this animation can cause overflow, we can throttle some of the ticks. + if ((aTime - mStyleRuleRefreshTime) < TimeDuration::FromMilliseconds(200)) { + return true; + } + + // If the nearest scrollable ancestor has overflow:hidden, + // we don't care about overflow. + nsIScrollableFrame* scrollable = + nsLayoutUtils::GetNearestScrollableFrame(mElement->GetPrimaryFrame()); + if (!scrollable) { + return true; + } + + nsPresContext::ScrollbarStyles ss = scrollable->GetScrollbarStyles(); + if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN && + ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && + scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) { + return true; + } + + return false; +} + +bool +CommonElementAnimationData::CanThrottleAnimation(TimeStamp aTime) +{ + nsIFrame* frame = mElement->GetPrimaryFrame(); + if (!frame) { + return false; + } + + bool hasTransform = HasAnimationOfProperty(eCSSProperty_transform); + bool hasOpacity = HasAnimationOfProperty(eCSSProperty_opacity); + if (hasOpacity) { + Layer* layer = FrameLayerBuilder::GetDedicatedLayer( + frame, nsDisplayItem::TYPE_OPACITY); + if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) { + return false; + } + } + + if (!hasTransform) { + return true; + } + + Layer* layer = FrameLayerBuilder::GetDedicatedLayer( + frame, nsDisplayItem::TYPE_TRANSFORM); + if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) { + return false; + } + + return CanThrottleTransformChanges(aTime); +} + +void +CommonElementAnimationData::UpdateAnimationGeneration(nsPresContext* aPresContext) +{ + mAnimationGeneration = + aPresContext->PresShell()->FrameConstructor()->GetAnimationGeneration(); +} + } } diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h index f75271fef08a..d2d02dc1064c 100644 --- a/layout/style/AnimationCommon.h +++ b/layout/style/AnimationCommon.h @@ -52,10 +52,16 @@ public: */ void Disconnect(); + enum FlushFlags { + Can_Throttle, + Cannot_Throttle + }; + static bool ExtractComputedValueForTransition( nsCSSProperty aProperty, nsStyleContext* aStyleContext, nsStyleAnimation::Value& aComputedValue); + static bool ThrottlingEnabled(); protected: friend struct CommonElementAnimationData; // for ElementDataRemoved @@ -151,10 +157,28 @@ struct CommonElementAnimationData : public PRCList mElement->DeleteProperty(mElementProperty); } + bool CanThrottleTransformChanges(mozilla::TimeStamp aTime); + + bool CanThrottleAnimation(mozilla::TimeStamp aTime); + + enum CanAnimateFlags { + // Testing for width, height, top, right, bottom, or left. + CanAnimate_HasGeometricProperty = 1, + // Allow the case where OMTA is allowed in general, but not for the + // specified property. + CanAnimate_AllowPartial = 2 + }; + static bool CanAnimatePropertyOnCompositor(const dom::Element *aElement, nsCSSProperty aProperty, - bool aHasGeometricProperties); + CanAnimateFlags aFlags); + + // True if this animation can be performed on the compositor thread. + // Do not pass CanAnimate_AllowPartial to make sure that all properties of this + // animation are supported by the compositor. + virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const = 0; + virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const = 0; static void LogAsyncAnimationFailure(nsCString& aMessage, const nsIContent* aContent = nullptr); @@ -175,6 +199,16 @@ struct CommonElementAnimationData : public PRCList // NOTE: If we don't need to apply any styles, mStyleRule will be // null, but mStyleRuleRefreshTime will still be valid. nsRefPtr mStyleRule; + + // nsCSSFrameConstructor keeps track of the number of animation 'mini-flushes' + // (see nsTransitionManager::UpdateAllThrottledStyles()). mFlushCount is + // the last flush where a transition/animation changed. We keep a similar + // count on the corresponding layer so we can check that the layer is up to + // date with the animation manager. + uint64_t mAnimationGeneration; + // Update mFlushCount to nsCSSFrameConstructor's count + void UpdateAnimationGeneration(nsPresContext* aPresContext); + // The refresh time associated with mStyleRule. TimeStamp mStyleRuleRefreshTime; diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp index a9110587ecb0..df14a88d5728 100644 --- a/layout/style/nsAnimationManager.cpp +++ b/layout/style/nsAnimationManager.cpp @@ -11,7 +11,6 @@ #include "nsStyleAnimation.h" #include "nsSMILKeySpline.h" #include "nsEventDispatcher.h" -#include "nsDisplayList.h" #include "nsCSSFrameConstructor.h" using namespace mozilla; @@ -60,9 +59,6 @@ ElementAnimations::GetPositionInIteration(TimeStamp aStartTime, TimeStamp aCurre aAnimation->mLastNotification != ElementAnimation::LAST_NOTIFICATION_END) { aAnimation->mLastNotification = ElementAnimation::LAST_NOTIFICATION_END; - // XXXdz: if this animation was done on the compositor, we should - // invalidate the frame and update style once we start throttling style - // updates. AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END, currentTimeDuration); aEventsToDispatch->AppendElement(ei); @@ -140,7 +136,7 @@ ElementAnimations::GetPositionInIteration(TimeStamp aStartTime, TimeStamp aCurre uint32_t message = aAnimation->mLastNotification == ElementAnimation::LAST_NOTIFICATION_NONE ? NS_ANIMATION_START : NS_ANIMATION_ITERATION; - // XXXdz: If this is a start, invalidate the frame here once we throttle animations. + aAnimation->mLastNotification = whichIteration; AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message, currentTimeDuration); @@ -152,10 +148,13 @@ ElementAnimations::GetPositionInIteration(TimeStamp aStartTime, TimeStamp aCurre void ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime, - EventArray& aEventsToDispatch) + EventArray& aEventsToDispatch, + bool aIsThrottled) { - if (!mNeedsRefreshes) { - // All of our animations are paused or completed. + if (!mNeedsRefreshes || + aIsThrottled) { + // All of our animations are paused or completed or this animation is being + // handled on the compositor thread, so we shouldn't interpolate here. mStyleRuleRefreshTime = aRefreshTime; return; } @@ -269,7 +268,7 @@ ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime, bool ElementAnimation::IsRunningAt(TimeStamp aTime) const { - return !IsPaused() && aTime > mStartTime && + return !IsPaused() && aTime >= mStartTime && (aTime - mStartTime) / mIterationDuration < mIterationCount; } @@ -300,7 +299,7 @@ ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const } bool -ElementAnimations::CanPerformOnCompositorThread() const +ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const { nsIFrame* frame = mElement->GetPrimaryFrame(); if (!frame) { @@ -310,7 +309,9 @@ ElementAnimations::CanPerformOnCompositorThread() const if (mElementProperty != nsGkAtoms::animationsProperty) { if (nsLayoutUtils::IsAnimationLoggingEnabled()) { nsCString message; - message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367"); + message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 ("); + message.Append(nsAtomCString(mElementProperty)); + message.AppendLiteral(")"); LogAsyncAnimationFailure(message, mElement); } return false; @@ -318,14 +319,13 @@ ElementAnimations::CanPerformOnCompositorThread() const TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh(); - bool hasGeometricProperty = false; for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { const ElementAnimation& anim = mAnimations[animIdx]; for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length(); propIdx != propEnd; ++propIdx) { if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) && anim.IsRunningAt(now)) { - hasGeometricProperty = true; + aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty); break; } } @@ -345,7 +345,7 @@ ElementAnimations::CanPerformOnCompositorThread() const const AnimationProperty& prop = anim.mProperties[propIdx]; if (!CanAnimatePropertyOnCompositor(mElement, prop.mProperty, - hasGeometricProperty)) { + aFlags)) { return false; } if (prop.mProperty == eCSSProperty_opacity) { @@ -411,6 +411,15 @@ nsAnimationManager::GetElementAnimations(dom::Element *aElement, return ea; } + +void +nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET) +{ + aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(), + mPendingEvents, + false); +} + /* virtual */ void nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData) { @@ -505,10 +514,9 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); if (ea) { - // XXXdz: Invalidate the frame since the animation changed. - // The cached style rule is invalid. ea->mStyleRule = nullptr; ea->mStyleRuleRefreshTime = TimeStamp(); + ea->UpdateAnimationGeneration(mPresContext); // Copy over the start times and (if still paused) pause starts // for each animation (matching on name only) that was also in the @@ -567,7 +575,7 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, ea->mAnimations.SwapElements(newAnimations); ea->mNeedsRefreshes = true; - ea->EnsureStyleRuleFor(refreshTime, mPendingEvents); + ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false); // We don't actually dispatch the mPendingEvents now. We'll either // dispatch them the next time we get a refresh driver notification // or the next time somebody calls @@ -893,10 +901,6 @@ nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, return nullptr; } - NS_WARN_IF_FALSE(ea->mStyleRuleRefreshTime == - mPresContext->RefreshDriver()->MostRecentRefresh(), - "should already have refreshed style rule"); - if (mPresContext->IsProcessingRestyles() && !mPresContext->IsProcessingAnimationStyleChange()) { // During the non-animation part of processing restyles, we don't @@ -909,6 +913,10 @@ nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, return nullptr; } + NS_WARN_IF_FALSE(ea->mStyleRuleRefreshTime == + mPresContext->RefreshDriver()->MostRecentRefresh(), + "should already have refreshed style rule"); + return ea->mStyleRule; } @@ -927,20 +935,38 @@ nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime) return; } + FlushAnimations(Can_Throttle); +} + +void +nsAnimationManager::FlushAnimations(FlushFlags aFlags) +{ // FIXME: check that there's at least one style rule that's not // in its "done" state, and if there isn't, remove ourselves from // the refresh driver (but leave the animations!). + TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); + bool didThrottle = false; for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; l = PR_NEXT_LINK(l)) { ElementAnimations *ea = static_cast(l); + bool canThrottleTick = aFlags == Can_Throttle && + ea->CanPerformOnCompositorThread( + CommonElementAnimationData::CanAnimateFlags(0)) && + ea->CanThrottleAnimation(now); + nsRefPtr oldStyleRule = ea->mStyleRule; - ea->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(), - mPendingEvents); + ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick); if (oldStyleRule != ea->mStyleRule) { ea->PostRestyleForAnimation(mPresContext); + } else { + didThrottle = true; } } + if (didThrottle) { + mPresContext->Document()->SetNeedStyleFlush(); + } + DispatchEvents(); // may destroy us } diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h index 330689d86dd6..8353a240dfd9 100644 --- a/layout/style/nsAnimationManager.h +++ b/layout/style/nsAnimationManager.h @@ -90,7 +90,7 @@ struct ElementAnimation return mPlayState == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED; } - bool HasAnimationOfProperty(nsCSSProperty aProperty) const; + virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const; bool IsRunningAt(mozilla::TimeStamp aTime) const; mozilla::TimeStamp mStartTime; // with delay taken into account @@ -111,7 +111,8 @@ struct ElementAnimation /** * Data about all of the animations running on an element. */ -struct ElementAnimations : public mozilla::css::CommonElementAnimationData +struct ElementAnimations MOZ_FINAL + : public mozilla::css::CommonElementAnimationData { typedef mozilla::TimeStamp TimeStamp; typedef mozilla::TimeDuration TimeDuration; @@ -136,13 +137,14 @@ struct ElementAnimations : public mozilla::css::CommonElementAnimationData TimeDuration aDuration, double aIterationCount, uint32_t aDirection, - bool IsForElement = true, + bool aIsForElement = true, ElementAnimation* aAnimation = nullptr, ElementAnimations* aEa = nullptr, EventArray* aEventsToDispatch = nullptr); void EnsureStyleRuleFor(TimeStamp aRefreshTime, - EventArray &aEventsToDispatch); + EventArray &aEventsToDispatch, + bool aIsThrottled); bool IsForElement() const { // rather than for a pseudo-element return mElementProperty == nsGkAtoms::animationsProperty; @@ -154,8 +156,8 @@ struct ElementAnimations : public mozilla::css::CommonElementAnimationData } // True if this animation can be performed on the compositor thread. - bool CanPerformOnCompositorThread() const; - bool HasAnimationOfProperty(nsCSSProperty aProperty) const; + virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const MOZ_OVERRIDE; + virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE; // False when we know that our current style rule is valid // indefinitely into the future (because all of our animations are @@ -185,10 +187,26 @@ public: if (!animations) return nullptr; bool propertyMatches = animations->HasAnimationOfProperty(aProperty); - return (propertyMatches && animations->CanPerformOnCompositorThread()) ? - animations : nullptr; + return (propertyMatches && + animations->CanPerformOnCompositorThread( + mozilla::css::CommonElementAnimationData::CanAnimate_AllowPartial)) + ? animations + : nullptr; } + // Returns true if aContent or any of its ancestors has an animation. + static bool ContentOrAncestorHasAnimation(nsIContent* aContent) { + do { + if (aContent->GetProperty(nsGkAtoms::animationsProperty)) { + return true; + } + } while ((aContent = aContent->GetParent())); + + return false; + } + + void EnsureStyleRuleFor(ElementAnimations* aET); + // nsIStyleRuleProcessor (parts) virtual void RulesMatching(ElementRuleProcessorData* aData) MOZ_OVERRIDE; virtual void RulesMatching(PseudoElementRuleProcessorData* aData) MOZ_OVERRIDE; @@ -204,6 +222,8 @@ public: // nsARefreshObserver virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE; + void FlushAnimations(FlushFlags aFlags); + /** * Return the style rule that RulesMatching should add for * aStyleContext. This might be different from what RulesMatching @@ -236,10 +256,11 @@ public: } } -private: ElementAnimations* GetElementAnimations(mozilla::dom::Element *aElement, nsCSSPseudoElements::Type aPseudoType, bool aCreateIfNeeded); + +private: void BuildAnimations(nsStyleContext* aStyleContext, InfallibleTArray& aAnimations); bool BuildSegment(InfallibleTArray& aSegments, diff --git a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp index d13890f16518..6ab33e4f3552 100644 --- a/layout/style/nsStyleSet.cpp +++ b/layout/style/nsStyleSet.cpp @@ -972,6 +972,23 @@ nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext, false, nullptr); } +already_AddRefed +nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext, + nsStyleContext* aOldStyle, + const nsTArray& aRules) +{ + nsRuleWalker ruleWalker(mRuleTree); + for (int32_t i = aRules.Length() - 1; i >= 0; --i) { + ruleWalker.SetLevel(aRules[i].mLevel, false, false); + ruleWalker.ForwardOnPossiblyCSSRule(aRules[i].mRule); + } + + return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr, + aOldStyle->IsLinkContext(), aOldStyle->RelevantLinkVisited(), + nullptr, nsCSSPseudoElements::ePseudo_NotPseudoElement, + false, nullptr); +} + already_AddRefed nsStyleSet::ResolveStyleByAddingRules(nsStyleContext* aBaseContext, const nsCOMArray &aRules) diff --git a/layout/style/nsStyleSet.h b/layout/style/nsStyleSet.h index 75d9eb2edba0..5bdb71f3aff9 100644 --- a/layout/style/nsStyleSet.h +++ b/layout/style/nsStyleSet.h @@ -89,6 +89,21 @@ class nsStyleSet ResolveStyleForRules(nsStyleContext* aParentContext, const nsTArray< nsCOMPtr > &aRules); + // used in ResolveStyleForRules below + struct RuleAndLevel + { + nsIStyleRule* mRule; + uint8_t mLevel; + }; + + // Get a new style context for aElement for the rules in aRules + // aRules is an array of rules and their levels in reverse order, + // that is from the leaf-most to the root-most rule in the rule tree. + already_AddRefed + ResolveStyleForRules(nsStyleContext* aParentContext, + nsStyleContext* aOldStyle, + const nsTArray& aRules); + // Get a style context that represents aBaseContext, but as though // it additionally matched the rules in the aRules array (in that // order, as more specific than any other rules). diff --git a/layout/style/nsStyleTransformMatrix.cpp b/layout/style/nsStyleTransformMatrix.cpp index e6a3f657ee95..f2f3c8c9b818 100644 --- a/layout/style/nsStyleTransformMatrix.cpp +++ b/layout/style/nsStyleTransformMatrix.cpp @@ -160,7 +160,7 @@ ProcessMatrix3D(gfx3DMatrix& aMatrix, } /* Helper function to process two matrices that we need to interpolate between */ -static void +void ProcessInterpolateMatrix(gfx3DMatrix& aMatrix, const nsCSSValue::Array* aData, nsStyleContext* aContext, diff --git a/layout/style/nsStyleTransformMatrix.h b/layout/style/nsStyleTransformMatrix.h index c18fccadb183..e8d7cea199e5 100644 --- a/layout/style/nsStyleTransformMatrix.h +++ b/layout/style/nsStyleTransformMatrix.h @@ -37,6 +37,14 @@ namespace nsStyleTransformMatrix { nscoord aSize, float aAppUnitsPerMatrixUnit); + void + ProcessInterpolateMatrix(gfx3DMatrix& aMatrix, + const nsCSSValue::Array* aData, + nsStyleContext* aContext, + nsPresContext* aPresContext, + bool& aCanStoreInRuleTree, + nsRect& aBounds, float aAppUnitsPerMatrixUnit); + /** * Given an nsCSSValueList containing -moz-transform functions, * returns a matrix containing the value of those functions. diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp index 2db9d7a2e502..eddf3529b7c4 100644 --- a/layout/style/nsTransitionManager.cpp +++ b/layout/style/nsTransitionManager.cpp @@ -7,6 +7,7 @@ /* Code to start and animate CSS transitions. */ #include "nsTransitionManager.h" +#include "nsAnimationManager.h" #include "nsIContent.h" #include "nsStyleContext.h" #include "nsCSSProps.h" @@ -24,17 +25,23 @@ #include "mozilla/dom/Element.h" #include "nsIFrame.h" #include "nsCSSFrameConstructor.h" +#include "Layers.h" +#include "FrameLayerBuilder.h" +#include "nsDisplayList.h" using mozilla::TimeStamp; using mozilla::TimeDuration; -namespace dom = mozilla::dom; -namespace css = mozilla::css; +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::css; -ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement, nsIAtom *aElementProperty, - nsTransitionManager *aTransitionManager) - : CommonElementAnimationData(aElement, aElementProperty, - aTransitionManager) +ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement, + nsIAtom *aElementProperty, + nsTransitionManager *aTransitionManager, + TimeStamp aNow) + : CommonElementAnimationData(aElement, aElementProperty, aTransitionManager) + , mFlushGeneration(aNow) { } @@ -47,7 +54,11 @@ ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const double duration = mDuration.ToSeconds(); NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden"); double timePortion; - if (duration == 0.0) { + if (IsRemovedSentinel()) { + // The transition is being removed, but we still want an update so that any + // new transitions start in the right place. + timePortion = 1.0; + } else if (duration == 0.0) { // When duration is zero, we can still have a transition when delay // is nonzero. mStartTime already incorporates delay. if (aRefreshTime >= mStartTime) { @@ -110,11 +121,13 @@ ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime) bool ElementPropertyTransition::IsRunningAt(TimeStamp aTime) const { - return !IsRemovedSentinel() && mStartTime < aTime && aTime < mStartTime + mDuration; + return !IsRemovedSentinel() && + mStartTime <= aTime && + aTime < mStartTime + mDuration; } bool -ElementTransitions::HasTransitionOfProperty(nsCSSProperty aProperty) const +ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const { for (uint32_t tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) { if (aProperty == mPropertyTransitions[tranIdx].mProperty) { @@ -125,7 +138,7 @@ ElementTransitions::HasTransitionOfProperty(nsCSSProperty aProperty) const } bool -ElementTransitions::CanPerformOnCompositorThread() const +ElementTransitions::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const { nsIFrame* frame = mElement->GetPrimaryFrame(); if (!frame) { @@ -143,25 +156,28 @@ ElementTransitions::CanPerformOnCompositorThread() const TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh(); - bool hasGeometricProperty = false; for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) { const ElementPropertyTransition& pt = mPropertyTransitions[i]; if (css::IsGeometricProperty(pt.mProperty) && pt.IsRunningAt(now)) { - hasGeometricProperty = true; + aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty); break; } } bool hasOpacity = false; bool hasTransform = false; + bool existsProperty = false; for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) { const ElementPropertyTransition& pt = mPropertyTransitions[i]; if (pt.IsRemovedSentinel()) { continue; } + + existsProperty = true; + if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(mElement, pt.mProperty, - hasGeometricProperty)) { + aFlags)) { return false; } if (pt.mProperty == eCSSProperty_opacity) { @@ -170,6 +186,12 @@ ElementTransitions::CanPerformOnCompositorThread() const hasTransform = true; } } + + // No properties to animate + if (!existsProperty) { + return false; + } + // This transition can be done on the compositor. Mark the frame as active, in // case we are able to throttle this transition. if (hasOpacity) { @@ -185,6 +207,211 @@ ElementTransitions::CanPerformOnCompositorThread() const * nsTransitionManager * *****************************************************************************/ +// reparent :before and :after pseudo elements of aElement +static void ReparentBeforeAndAfter(dom::Element* aElement, + nsIFrame* aPrimaryFrame, + nsStyleContext* aNewStyle, + nsStyleSet* aStyleSet) +{ + if (nsIFrame* before = nsLayoutUtils::GetBeforeFrame(aPrimaryFrame)) { + nsRefPtr beforeStyle = + aStyleSet->ReparentStyleContext(before->GetStyleContext(), + aNewStyle, aElement); + before->SetStyleContextWithoutNotification(beforeStyle); + } + if (nsIFrame* after = nsLayoutUtils::GetBeforeFrame(aPrimaryFrame)) { + nsRefPtr afterStyle = + aStyleSet->ReparentStyleContext(after->GetStyleContext(), + aNewStyle, aElement); + after->SetStyleContextWithoutNotification(afterStyle); + } +} + +// Ensure that the next repaint rebuilds the layer tree for aFrame. That +// means that changes to animations on aFrame's layer are propagated to +// the compositor, which is needed for correct behaviour of new +// transitions. +static void +ForceLayerRerendering(nsIFrame* aFrame, CommonElementAnimationData* aData) +{ + if (aData->HasAnimationOfProperty(eCSSProperty_opacity)) { + if (Layer* layer = FrameLayerBuilder::GetDedicatedLayer( + aFrame, nsDisplayItem::TYPE_OPACITY)) { + layer->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey()); + } + } + + if (aData->HasAnimationOfProperty(eCSSProperty_transform)) { + if (Layer* layer = FrameLayerBuilder::GetDedicatedLayer( + aFrame, nsDisplayItem::TYPE_TRANSFORM)) { + layer->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey()); + } + } +} + +nsStyleContext* +nsTransitionManager::UpdateThrottledStyle(dom::Element* aElement, + nsStyleContext* aParentStyle) +{ + NS_ASSERTION(GetElementTransitions(aElement, + nsCSSPseudoElements::ePseudo_NotPseudoElement, + false), "element not transitioning"); + + nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); + if (!primaryFrame) { + return nullptr; + } + + nsStyleContext* oldStyle = primaryFrame->GetStyleContext(); + nsRuleNode* ruleNode = oldStyle->GetRuleNode(); + nsTArray rules; + do { + if (ruleNode->IsRoot()) { + break; + } + + nsStyleSet::RuleAndLevel* curRule = rules.AppendElement(); + curRule->mLevel = ruleNode->GetLevel(); + + if (curRule->mLevel == nsStyleSet::eAnimationSheet) { + ElementAnimations* ea = + mPresContext->AnimationManager()->GetElementAnimations(aElement, + oldStyle->GetPseudoType(), + false); + NS_ASSERTION(ea, "Rule has level eAnimationSheet without animation on manager"); + + mPresContext->AnimationManager()->EnsureStyleRuleFor(ea); + curRule->mRule = ea->mStyleRule; + + ForceLayerRerendering(primaryFrame, ea); + } else if (curRule->mLevel == nsStyleSet::eTransitionSheet) { + ElementTransitions *et = + GetElementTransitions(aElement, oldStyle->GetPseudoType(), false); + NS_ASSERTION(et, "Rule has level eTransitionSheet without transition on manager"); + + et->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh()); + curRule->mRule = et->mStyleRule; + + ForceLayerRerendering(primaryFrame, et); + } else { + curRule->mRule = ruleNode->GetRule(); + } + } while (ruleNode = ruleNode->GetParent()); + + nsRefPtr newStyle = mPresContext->PresShell()->StyleSet()-> + ResolveStyleForRules(aParentStyle, oldStyle, rules); + primaryFrame->SetStyleContextWithoutNotification(newStyle); + + ReparentBeforeAndAfter(aElement, primaryFrame, newStyle, mPresContext->PresShell()->StyleSet()); + + return newStyle; +} + +void +nsTransitionManager::UpdateThrottledStylesForSubtree(nsIContent* aContent, + nsStyleContext* aParentStyle) +{ + dom::Element* element; + if (aContent->IsElement()) { + element = aContent->AsElement(); + } else { + element = nullptr; + } + + nsRefPtr newStyle; + + ElementTransitions* et; + if (element && + (et = GetElementTransitions(element, + nsCSSPseudoElements::ePseudo_NotPseudoElement, + false))) { + // re-resolve our style + newStyle = UpdateThrottledStyle(element, aParentStyle); + // remove the current transition from the working set + et->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh(); +; + } else { + // reparent the element's style + nsStyleSet* styleSet = mPresContext->PresShell()->StyleSet(); + nsIFrame* primaryFrame = aContent->GetPrimaryFrame(); + if (!primaryFrame) { + return; + } + + newStyle = styleSet->ReparentStyleContext(primaryFrame->GetStyleContext(), + aParentStyle, element); + primaryFrame->SetStyleContextWithoutNotification(newStyle); + ReparentBeforeAndAfter(element, primaryFrame, newStyle, styleSet); + } + + // walk the children + if (newStyle) { + nsIContent* child = aContent->GetFirstChild(); + for (nsIContent *child = aContent->GetFirstChild(); child; + child = child->GetNextSibling()) { + UpdateThrottledStylesForSubtree(child, newStyle); + } + } +} + +void +nsTransitionManager::UpdateAllThrottledStyles() +{ + if (PR_CLIST_IS_EMPTY(&mElementData)) { + // no throttled transitions, leave early + mPresContext->TickLastUpdateThrottledStyle(); + return; + } + + if (mPresContext->ThrottledStyleIsUpToDate()) { + // throttled transitions are up to date, leave early + return; + } + + mPresContext->TickLastUpdateThrottledStyle(); + TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); + + // update each transitioning element by finding its root-most ancestor with a + // transition, and flushing the style on that ancestor and all its descendants + PRCList *next = PR_LIST_HEAD(&mElementData); + while (next != &mElementData) { + ElementTransitions* et = static_cast(next); + next = PR_NEXT_LINK(next); + + if (et->mFlushGeneration == now) { + // this element has been ticked already + continue; + } + + // element is initialised to the starting element (i.e., one we know has + // a transition) and ends up with the root-most transitioning ancestor, + // that is, the element where we begin updates. + dom::Element* element = et->mElement; + // make a list of ancestors + nsTArray ancestors; + do { + ancestors.AppendElement(element); + } while (element = element->GetElementParent()); + + // walk down the ancestors until we find one with a throttled transition + for (int32_t i = ancestors.Length() - 1; i >= 0; --i) { + if (GetElementTransitions(ancestors[i], + nsCSSPseudoElements::ePseudo_NotPseudoElement, + false)) { + element = ancestors[i]; + break; + } + } + + nsIFrame* primaryFrame; + if (element && + (primaryFrame = element->GetPrimaryFrame())) { + UpdateThrottledStylesForSubtree(element, + primaryFrame->GetStyleContext()->GetParent()); + } + } +} + already_AddRefed nsTransitionManager::StyleContextChanged(dom::Element *aElement, nsStyleContext *aOldStyleContext, @@ -248,6 +475,10 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement, return nullptr; } + NS_WARN_IF_FALSE(!CommonAnimationManager::ThrottlingEnabled() || + mPresContext->ThrottledStyleIsUpToDate(), + "throttled animations not up to date"); + // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html // I'll consider only the transitions from the number of items in // 'transition-property' on down, and later ones will override earlier @@ -340,6 +571,7 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement, currentValue != pt.mEndValue) { // stop the transition pts.RemoveElementAt(i); + et->UpdateAnimationGeneration(mPresContext); } } while (i != 0); @@ -386,17 +618,19 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement, void nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty, - const nsTransition& aTransition, - dom::Element *aElement, - ElementTransitions *&aElementTransitions, - nsStyleContext *aOldStyleContext, - nsStyleContext *aNewStyleContext, - bool *aStartedAny, - nsCSSPropertySet *aWhichStarted) + const nsTransition& aTransition, + dom::Element* aElement, + ElementTransitions*& aElementTransitions, + nsStyleContext* aOldStyleContext, + nsStyleContext* aNewStyleContext, + bool* aStartedAny, + nsCSSPropertySet* aWhichStarted) { // IsShorthand itself will assert if aProperty is not a property. NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty), "property out of range"); + NS_ASSERTION(!aElementTransitions || + aElementTransitions->mElement == aElement, "Element mismatch"); if (aWhichStarted->HasProperty(aProperty)) { // A later item in transition-property already started a @@ -414,22 +648,15 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty, nsStyleAnimation::Value dummyValue; bool haveValues = ExtractComputedValueForTransition(aProperty, aOldStyleContext, - pt.mStartValue) && + pt.mStartValue) && ExtractComputedValueForTransition(aProperty, aNewStyleContext, - pt.mEndValue); + pt.mEndValue); bool haveChange = pt.mStartValue != pt.mEndValue; - bool haveOMTA = false; - if (!aNewStyleContext->GetPseudoType()) { - ElementTransitions* et = nsTransitionManager::GetTransitions(aElement); - if (et) { - haveOMTA = et->CanPerformOnCompositorThread(); - } - } - + bool shouldAnimate = haveValues && - (haveChange || haveOMTA) && + haveChange && // Check that we can interpolate between these values // (If this is ever a performance problem, we could add a // CanInterpolate method, but it seems fine for now.) @@ -465,6 +692,8 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty, // is almost done (and whose current value rounds to its end // value) just because we got an unrelated style change. pts.RemoveElementAt(currentIndex); + aElementTransitions->UpdateAnimationGeneration(mPresContext); + if (pts.IsEmpty()) { aElementTransitions->Destroy(); // |aElementTransitions| is now a dangling pointer! @@ -569,13 +798,13 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty, return; } } + aElementTransitions->UpdateAnimationGeneration(mPresContext); nsRestyleHint hint = aNewStyleContext->GetPseudoType() == nsCSSPseudoElements::ePseudo_NotPseudoElement ? eRestyle_Self : eRestyle_Subtree; presContext->PresShell()->RestyleForAnimation(aElement, hint); - // XXXdz: invalidate the frame here, once animations are throttled. *aStartedAny = true; aWhichStarted->AddProperty(aProperty); @@ -608,7 +837,8 @@ nsTransitionManager::GetElementTransitions(dom::Element *aElement, aElement->GetProperty(propName)); if (!et && aCreateIfNeeded) { // FIXME: Consider arena-allocating? - et = new ElementTransitions(aElement, propName, this); + et = new ElementTransitions(aElement, propName, this, + mPresContext->RefreshDriver()->MostRecentRefresh()); nsresult rv = aElement->SetProperty(propName, et, ElementTransitionsPropertyDtor, false); if (NS_FAILED(rv)) { @@ -747,8 +977,20 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) return; } - nsTArray events; + FlushTransitions(Can_Throttle); +} +void +nsTransitionManager::FlushTransitions(FlushFlags aFlags) +{ + if (PR_CLIST_IS_EMPTY(&mElementData)) { + // no transitions, leave early + return; + } + + nsTArray events; + TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); + bool didThrottle = false; // Trim transitions that have completed, and post restyle events for // frames that are still transitioning. { @@ -757,6 +999,11 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) ElementTransitions *et = static_cast(next); next = PR_NEXT_LINK(next); + bool canThrottleTick = aFlags == Can_Throttle && + et->CanPerformOnCompositorThread( + CommonElementAnimationData::CanAnimateFlags(0)) && + et->CanThrottleAnimation(now); + NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() == mPresContext->Document(), "Element::UnbindFromTree should have " @@ -764,14 +1011,20 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) uint32_t i = et->mPropertyTransitions.Length(); NS_ABORT_IF_FALSE(i != 0, "empty transitions list?"); + bool transitionEnded = false; do { --i; ElementPropertyTransition &pt = et->mPropertyTransitions[i]; if (pt.IsRemovedSentinel()) { - // Actually remove transitions one cycle after their - // completion. See comment below. - et->mPropertyTransitions.RemoveElementAt(i); - } else if (pt.mStartTime + pt.mDuration <= aTime) { + // Actually remove transitions one throttle-able cycle after their + // completion. We only clear on a throttle-able cycle because that + // means it is a regular restyle tick and thus it is safe to discard + // the transition. If the flush is not throttle-able, we might still + // have new transitions left to process. See comment below. + if (aFlags == Can_Throttle) { + et->mPropertyTransitions.RemoveElementAt(i); + } + } else if (pt.mStartTime + pt.mDuration <= now) { // Fire transitionend events only for transitions on elements // and not those on pseudo-elements, since we can't target an // event at pseudo-elements. @@ -793,6 +1046,8 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) // to know not to start a new transition for the transition // from the almost-completed value to the final value. pt.SetRemovedSentinel(); + et->UpdateAnimationGeneration(mPresContext); + transitionEnded = true; } } while (i != 0); @@ -802,12 +1057,13 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty || et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty, "Unexpected element property; might restyle too much"); - nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ? - eRestyle_Self : eRestyle_Subtree; - mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint); - // XXXdz: if we have started a transition since the last tick and are - // performing the transition off the main thread, we need to invalidate - // the frame once we start throttling animation ticks. + if (!canThrottleTick || transitionEnded) { + nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ? + eRestyle_Self : eRestyle_Subtree; + mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint); + } else { + didThrottle = true; + } if (et->mPropertyTransitions.IsEmpty()) { et->Destroy(); @@ -820,6 +1076,10 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) // We might have removed transitions above. ElementDataRemoved(); + if (didThrottle) { + mPresContext->Document()->SetNeedStyleFlush(); + } + for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) { TransitionEventInfo &info = events[i]; nsEventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent); diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h index 161f75c30891..9ec9636157f4 100644 --- a/layout/style/nsTransitionManager.h +++ b/layout/style/nsTransitionManager.h @@ -70,19 +70,25 @@ struct ElementPropertyTransition bool IsRunningAt(mozilla::TimeStamp aTime) const; }; -struct ElementTransitions : public mozilla::css::CommonElementAnimationData +struct ElementTransitions MOZ_FINAL + : public mozilla::css::CommonElementAnimationData { ElementTransitions(mozilla::dom::Element *aElement, nsIAtom *aElementProperty, - nsTransitionManager *aTransitionManager); + nsTransitionManager *aTransitionManager, + mozilla::TimeStamp aNow); void EnsureStyleRuleFor(mozilla::TimeStamp aRefreshTime); + virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE; + virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const MOZ_OVERRIDE; - bool HasTransitionOfProperty(nsCSSProperty aProperty) const; - // True if this animation can be performed on the compositor thread. - bool CanPerformOnCompositorThread() const; // Either zero or one for each CSS property: nsTArray mPropertyTransitions; + + // Generation counter for flushes of throttled transitions. + // Used to prevent updating the styles twice for a given element during + // UpdateAllThrottledStyles. + mozilla::TimeStamp mFlushGeneration; }; @@ -100,16 +106,31 @@ public: (aContent->GetProperty(nsGkAtoms::transitionsProperty)); } + // Returns true if aContent or any of its ancestors has a transition. + static bool ContentOrAncestorHasTransition(nsIContent* aContent) { + do { + if (GetTransitions(aContent)) { + return true; + } + } while ((aContent = aContent->GetParent())); + + return false; + } + + typedef mozilla::css::CommonElementAnimationData CommonElementAnimationData; + static ElementTransitions* GetTransitionsForCompositor(nsIContent* aContent, nsCSSProperty aProperty) { - if (!aContent->MayHaveAnimations()) + if (!aContent->MayHaveAnimations()) { return nullptr; + } ElementTransitions* transitions = GetTransitions(aContent); if (!transitions || - !transitions->HasTransitionOfProperty(aProperty) || - !transitions->CanPerformOnCompositorThread()) { + !transitions->HasAnimationOfProperty(aProperty) || + !transitions->CanPerformOnCompositorThread( + CommonElementAnimationData::CanAnimate_AllowPartial)) { return nullptr; } return transitions; @@ -151,6 +172,29 @@ public: // nsARefreshObserver virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE; + void FlushTransitions(FlushFlags aFlags); + + // Performs a 'mini-flush' to make styles from throttled transitions + // up-to-date prior to processing an unrelated style change, so that + // any transitions triggered by that style change produce correct + // results. + // + // In more detail: when we're able to run animations on the + // compositor, we sometimes "throttle" these animations by skipping + // updating style data on the main thread. However, whenever we + // process a normal (non-animation) style change, any changes in + // computed style on elements that have transition-* properties set + // may need to trigger new transitions; this process requires knowing + // both the old and new values of the property. To do this correctly, + // we need to have an up-to-date *old* value of the property on the + // primary frame. So the purpose of the mini-flush is to update the + // style for all throttled transitions and animations to the current + // animation state without making any other updates, so that when we + // process the queued style updates we'll have correct old data to + // compare against. When we do this, we don't bother touching frames + // other than primary frames. + void UpdateAllThrottledStyles(); + private: void ConsiderStartingTransition(nsCSSProperty aProperty, const nsTransition& aTransition, @@ -165,6 +209,17 @@ private: bool aCreateIfNeeded); void WalkTransitionRule(ElementDependentRuleProcessorData* aData, nsCSSPseudoElements::Type aPseudoType); + + // Update the animated styles of an element and its descendants. + // If the element has a transition, it is flushed back to its primary frame. + // If the element does not have a transition, then its style is reparented. + void UpdateThrottledStylesForSubtree(nsIContent* aContent, + nsStyleContext* aParentStyle); + // Update the style on aElement from the transition stored in this manager and + // the new parent style - aParentStyle. aElement must be transitioning or + // animated. Returns the updated style. + nsStyleContext* UpdateThrottledStyle(mozilla::dom::Element* aElement, + nsStyleContext* aParentStyle); }; #endif /* !defined(nsTransitionManager_h_) */ diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp index 6af829de6b27..15bbbc0f569c 100644 --- a/layout/svg/nsSVGUtils.cpp +++ b/layout/svg/nsSVGUtils.cpp @@ -1864,3 +1864,41 @@ nsSVGUtils::SetupCairoStroke(nsIFrame* aFrame, gfxContext* aContext, return SetupCairoStrokePaint(aFrame, aContext, aObjectPaint); } + +bool +nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext, + gfxFont::DrawMode aDrawMode, + gfxTextObjectPaint* aObjectPaint) +{ + nsIFrame* frame = aElement->GetPrimaryFrame(); + nsISVGChildFrame* svgFrame = do_QueryFrame(frame); + MOZ_ASSERT(!frame || svgFrame, "Non SVG frame for SVG glyph"); + if (svgFrame) { + nsRenderingContext context; + context.Init(frame->PresContext()->DeviceContext(), aContext); + context.AddUserData(&gfxTextObjectPaint::sUserDataKey, aObjectPaint, nullptr); + nsresult rv = svgFrame->PaintSVG(&context, nullptr); + if (NS_SUCCEEDED(rv)) { + return true; + } + } + return false; +} + +bool +nsSVGUtils::GetSVGGlyphExtents(Element* aElement, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult) +{ + nsIFrame* frame = aElement->GetPrimaryFrame(); + nsISVGChildFrame* svgFrame = do_QueryFrame(frame); + MOZ_ASSERT(!frame || svgFrame, "Non SVG frame for SVG glyph"); + if (svgFrame) { + *aResult = svgFrame->GetBBoxContribution(aSVGToAppSpace, + nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry | + nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry | + nsSVGUtils::eBBoxIncludeMarkers); + return true; + } + return false; +} diff --git a/layout/svg/nsSVGUtils.h b/layout/svg/nsSVGUtils.h index cbdc25e6f629..374e398b5826 100644 --- a/layout/svg/nsSVGUtils.h +++ b/layout/svg/nsSVGUtils.h @@ -10,6 +10,7 @@ #define _USE_MATH_DEFINES #include +#include "gfxFont.h" #include "gfxMatrix.h" #include "gfxPoint.h" #include "gfxRect.h" @@ -203,6 +204,7 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsISVGFilterProperty, NS_ISVGFILTERPROPERTY_IID) class nsSVGUtils { public: + typedef mozilla::dom::Element Element; static void Init(); @@ -631,6 +633,28 @@ public: * property on the element. */ static uint16_t GetGeometryHitTestFlags(nsIFrame* aFrame); + + /** + * Render a SVG glyph. + * @param aElement the SVG glyph element to render + * @param aContext the thebes aContext to draw to + * @param aDrawMode fill or stroke or both (see gfxFont::DrawMode) + * @return true if rendering succeeded + */ + static bool PaintSVGGlyph(Element* aElement, gfxContext* aContext, + gfxFont::DrawMode aDrawMode, + gfxTextObjectPaint* aObjectPaint); + /** + * Get the extents of a SVG glyph. + * @param aElement the SVG glyph element + * @param aSVGToAppSpace the matrix mapping the SVG glyph space to the + * target context space + * @param aResult the result (valid when true is returned) + * @return true if calculating the extents succeeded + */ + static bool GetSVGGlyphExtents(Element* aElement, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult); }; #endif diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index d276118fe0e5..16e2b7dbb569 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -7297,8 +7297,9 @@ nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame, // XXXbz this doesn't handle outlines, does it? aFrame->InvalidateFrame(); parent->InvalidateFrameWithRect(aOrigVisualOverflow + aOrigRect.TopLeft()); - } else { - aFrame->InvalidateFrameWithRect(aOrigVisualOverflow);; + } else if (aOrigRect.Size() != aFrame->GetSize() || + aOrigVisualOverflow.Size() != visualOverflow.Size()){ + aFrame->InvalidateFrameWithRect(aOrigVisualOverflow); aFrame->InvalidateFrame(); parent->InvalidateFrameWithRect(aOrigRect);; parent->InvalidateFrame(); diff --git a/media/mtransport/nricemediastream.cpp b/media/mtransport/nricemediastream.cpp index ce872c902f87..afdd1865fa30 100644 --- a/media/mtransport/nricemediastream.cpp +++ b/media/mtransport/nricemediastream.cpp @@ -107,7 +107,7 @@ nsresult NrIceMediaStream::ParseAttributes(std::vector& for (size_t i=0; i(attributes[i].c_str())); } - + // Still need to call nr_ice_ctx_parse_stream_attributes. int r = nr_ice_peer_ctx_parse_stream_attributes(ctx_->peer(), stream_, @@ -127,7 +127,8 @@ nsresult NrIceMediaStream::ParseAttributes(std::vector& nsresult NrIceMediaStream::ParseTrickleCandidate(const std::string& candidate) { int r; - MOZ_MTLOG(PR_LOG_DEBUG, "NrIceCtx(" << ctx_->name() << "): parsing trickle candidate " << candidate); + MOZ_MTLOG(PR_LOG_DEBUG, "NrIceCtx(" << ctx_->name() << ")/STREAM(" << + name() << ") : parsing trickle candidate " << candidate); r = nr_ice_peer_ctx_parse_trickle_candidate(ctx_->peer(), stream_, diff --git a/media/mtransport/runnable_utils.py b/media/mtransport/runnable_utils.py index b5d28b9cdb1e..2934445bfd92 100644 --- a/media/mtransport/runnable_utils.py +++ b/media/mtransport/runnable_utils.py @@ -1,7 +1,7 @@ # 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/. -MAX_ARGS = 10 +MAX_ARGS = 15 boilerplate = "/* This Source Code Form is subject to the terms of the Mozilla Public\n\ * License, v. 2.0. If a copy of the MPL was not distributed with this\n\ diff --git a/media/mtransport/runnable_utils_generated.h b/media/mtransport/runnable_utils_generated.h index 6d4aaa4fc351..195cf16c45b2 100644 --- a/media/mtransport/runnable_utils_generated.h +++ b/media/mtransport/runnable_utils_generated.h @@ -903,6 +903,606 @@ template class runnable_args_nm_10 : public runnable_args_base { + public: + runnable_args_nm_10(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) : + m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {} + + NS_IMETHOD Run() { + m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_); + return NS_OK; + } + + private: + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; +}; + + + +// 10 arguments -- +template class runnable_args_nm_10_ret : public runnable_args_base { + public: + runnable_args_nm_10_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R *r) : + m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {} + + NS_IMETHOD Run() { + *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_); + return NS_OK; + } + + private: + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; +}; + + + +// 10 arguments -- +template class runnable_args_m_10 : public runnable_args_base { + public: + runnable_args_m_10(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) : + o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {} + + NS_IMETHOD Run() { + ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_); + return NS_OK; + } + + private: + C o_; + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; +}; + + + +// 10 arguments -- +template class runnable_args_m_10_ret : public runnable_args_base { + public: + runnable_args_m_10_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R *r) : + o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {} + + NS_IMETHOD Run() { + *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_); + return NS_OK; + } + + private: + C o_; + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; +}; + + + +// 11 arguments -- +template class runnable_args_nm_11 : public runnable_args_base { + public: + runnable_args_nm_11(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) : + m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {} + + NS_IMETHOD Run() { + m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_); + return NS_OK; + } + + private: + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; +}; + + + +// 11 arguments -- +template class runnable_args_nm_11_ret : public runnable_args_base { + public: + runnable_args_nm_11_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R *r) : + m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {} + + NS_IMETHOD Run() { + *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_); + return NS_OK; + } + + private: + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; +}; + + + +// 11 arguments -- +template class runnable_args_m_11 : public runnable_args_base { + public: + runnable_args_m_11(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) : + o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {} + + NS_IMETHOD Run() { + ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_); + return NS_OK; + } + + private: + C o_; + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; +}; + + + +// 11 arguments -- +template class runnable_args_m_11_ret : public runnable_args_base { + public: + runnable_args_m_11_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R *r) : + o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {} + + NS_IMETHOD Run() { + *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_); + return NS_OK; + } + + private: + C o_; + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; +}; + + + +// 12 arguments -- +template class runnable_args_nm_12 : public runnable_args_base { + public: + runnable_args_nm_12(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) : + m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {} + + NS_IMETHOD Run() { + m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_); + return NS_OK; + } + + private: + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; +}; + + + +// 12 arguments -- +template class runnable_args_nm_12_ret : public runnable_args_base { + public: + runnable_args_nm_12_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R *r) : + m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {} + + NS_IMETHOD Run() { + *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_); + return NS_OK; + } + + private: + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; +}; + + + +// 12 arguments -- +template class runnable_args_m_12 : public runnable_args_base { + public: + runnable_args_m_12(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) : + o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {} + + NS_IMETHOD Run() { + ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_); + return NS_OK; + } + + private: + C o_; + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; +}; + + + +// 12 arguments -- +template class runnable_args_m_12_ret : public runnable_args_base { + public: + runnable_args_m_12_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R *r) : + o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {} + + NS_IMETHOD Run() { + *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_); + return NS_OK; + } + + private: + C o_; + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; +}; + + + +// 13 arguments -- +template class runnable_args_nm_13 : public runnable_args_base { + public: + runnable_args_nm_13(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) : + m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {} + + NS_IMETHOD Run() { + m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_); + return NS_OK; + } + + private: + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; +}; + + + +// 13 arguments -- +template class runnable_args_nm_13_ret : public runnable_args_base { + public: + runnable_args_nm_13_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R *r) : + m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {} + + NS_IMETHOD Run() { + *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_); + return NS_OK; + } + + private: + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; +}; + + + +// 13 arguments -- +template class runnable_args_m_13 : public runnable_args_base { + public: + runnable_args_m_13(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) : + o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {} + + NS_IMETHOD Run() { + ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_); + return NS_OK; + } + + private: + C o_; + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; +}; + + + +// 13 arguments -- +template class runnable_args_m_13_ret : public runnable_args_base { + public: + runnable_args_m_13_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R *r) : + o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {} + + NS_IMETHOD Run() { + *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_); + return NS_OK; + } + + private: + C o_; + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; +}; + + + +// 14 arguments -- +template class runnable_args_nm_14 : public runnable_args_base { + public: + runnable_args_nm_14(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) : + m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {} + + NS_IMETHOD Run() { + m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_); + return NS_OK; + } + + private: + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; + A13 a13_; +}; + + + +// 14 arguments -- +template class runnable_args_nm_14_ret : public runnable_args_base { + public: + runnable_args_nm_14_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R *r) : + m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {} + + NS_IMETHOD Run() { + *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_); + return NS_OK; + } + + private: + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; + A13 a13_; +}; + + + +// 14 arguments -- +template class runnable_args_m_14 : public runnable_args_base { + public: + runnable_args_m_14(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) : + o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {} + + NS_IMETHOD Run() { + ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_); + return NS_OK; + } + + private: + C o_; + M m_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; + A13 a13_; +}; + + + +// 14 arguments -- +template class runnable_args_m_14_ret : public runnable_args_base { + public: + runnable_args_m_14_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R *r) : + o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {} + + NS_IMETHOD Run() { + *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_); + return NS_OK; + } + + private: + C o_; + M m_; + R* r_; + A0 a0_; + A1 a1_; + A2 a2_; + A3 a3_; + A4 a4_; + A5 a5_; + A6 a6_; + A7 a7_; + A8 a8_; + A9 a9_; + A10 a10_; + A11 a11_; + A12 a12_; + A13 a13_; +}; + + + @@ -1186,3 +1786,143 @@ runnable_args_m_9_ret* WrapRunnable (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, r); } +// 10 arguments -- +template +runnable_args_nm_10* WrapRunnableNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) { + return new runnable_args_nm_10 + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); +} + +// 10 arguments -- +template +runnable_args_nm_10_ret* WrapRunnableNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R* r) { + return new runnable_args_nm_10_ret + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, r); +} + +// 10 arguments -- +template +runnable_args_m_10* WrapRunnable(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) { + return new runnable_args_m_10 + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); +} + +// 10 arguments -- +template +runnable_args_m_10_ret* WrapRunnableRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R* r) { + return new runnable_args_m_10_ret + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, r); +} + +// 11 arguments -- +template +runnable_args_nm_11* WrapRunnableNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) { + return new runnable_args_nm_11 + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); +} + +// 11 arguments -- +template +runnable_args_nm_11_ret* WrapRunnableNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R* r) { + return new runnable_args_nm_11_ret + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, r); +} + +// 11 arguments -- +template +runnable_args_m_11* WrapRunnable(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) { + return new runnable_args_m_11 + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); +} + +// 11 arguments -- +template +runnable_args_m_11_ret* WrapRunnableRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R* r) { + return new runnable_args_m_11_ret + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, r); +} + +// 12 arguments -- +template +runnable_args_nm_12* WrapRunnableNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) { + return new runnable_args_nm_12 + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); +} + +// 12 arguments -- +template +runnable_args_nm_12_ret* WrapRunnableNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R* r) { + return new runnable_args_nm_12_ret + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, r); +} + +// 12 arguments -- +template +runnable_args_m_12* WrapRunnable(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) { + return new runnable_args_m_12 + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); +} + +// 12 arguments -- +template +runnable_args_m_12_ret* WrapRunnableRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R* r) { + return new runnable_args_m_12_ret + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, r); +} + +// 13 arguments -- +template +runnable_args_nm_13* WrapRunnableNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) { + return new runnable_args_nm_13 + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); +} + +// 13 arguments -- +template +runnable_args_nm_13_ret* WrapRunnableNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R* r) { + return new runnable_args_nm_13_ret + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, r); +} + +// 13 arguments -- +template +runnable_args_m_13* WrapRunnable(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) { + return new runnable_args_m_13 + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); +} + +// 13 arguments -- +template +runnable_args_m_13_ret* WrapRunnableRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R* r) { + return new runnable_args_m_13_ret + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, r); +} + +// 14 arguments -- +template +runnable_args_nm_14* WrapRunnableNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) { + return new runnable_args_nm_14 + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); +} + +// 14 arguments -- +template +runnable_args_nm_14_ret* WrapRunnableNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R* r) { + return new runnable_args_nm_14_ret + (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, r); +} + +// 14 arguments -- +template +runnable_args_m_14* WrapRunnable(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) { + return new runnable_args_m_14 + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); +} + +// 14 arguments -- +template +runnable_args_m_14_ret* WrapRunnableRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R* r) { + return new runnable_args_m_14_ret + (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, r); +} + diff --git a/media/mtransport/third_party/nICEr/nicer.gyp b/media/mtransport/third_party/nICEr/nicer.gyp index 0e1f8b4f82e5..fed0bb8d35de 100644 --- a/media/mtransport/third_party/nICEr/nicer.gyp +++ b/media/mtransport/third_party/nICEr/nicer.gyp @@ -177,11 +177,9 @@ '../nrappkit/src/port/win32/include' ], }], - - - ## Linux - [ 'OS == "linux"', { - 'cflags': [ + ## Linux/Android + [ '(OS == "linux") or (OS=="android")', { + 'cflags_mozilla': [ '-Wall', '-Wno-parentheses', '-Wno-strict-prototypes', diff --git a/media/mtransport/third_party/nICEr/src/stun/addrs.c b/media/mtransport/third_party/nICEr/src/stun/addrs.c index b0b66b200c5e..47e456e9ad94 100644 --- a/media/mtransport/third_party/nICEr/src/stun/addrs.c +++ b/media/mtransport/third_party/nICEr/src/stun/addrs.c @@ -33,7 +33,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. static char *RCSSTRING __UNUSED__="$Id: addrs.c,v 1.2 2008/04/28 18:21:30 ekr Exp $"; - #include #include #include @@ -43,10 +42,17 @@ static char *RCSSTRING __UNUSED__="$Id: addrs.c,v 1.2 2008/04/28 18:21:30 ekr Ex #include #include #else /* UNIX */ -#include #include #include +#ifndef ANDROID +#include #include +#else +#include +/* Work around an Android NDK < r8c bug */ +#undef __unused +#include +#endif #include #ifndef LINUX #include diff --git a/media/webrtc/signaling/signaling.gyp b/media/webrtc/signaling/signaling.gyp index 34c335a1bd67..e7b4b1d19496 100644 --- a/media/webrtc/signaling/signaling.gyp +++ b/media/webrtc/signaling/signaling.gyp @@ -187,7 +187,7 @@ 'USE_FAKE_MEDIA_STREAMS' ], }], - ['OS=="linux"', { + ['(OS=="linux") or (OS=="android")', { 'include_dirs': [ ], @@ -590,12 +590,66 @@ # # OS SPECIFIC - # + # 'conditions': [ - ['OS=="linux"', { + ['(OS=="android") or (OS=="linux")', { 'include_dirs': [ ], + 'defines' : [ + 'SIP_OS_LINUX', + '_GNU_SOURCE', + 'CPR_MEMORY_LITTLE_ENDIAN', + 'NO_SOCKET_POLLING', + 'USE_TIMER_SELECT_BASED', + 'FULL_BUILD', + 'STUBBED_OUT', + 'USE_PRINTF' + 'LINUX', + ], + + 'cflags_mozilla': [ + ], + }], + ['OS=="android"', { + 'sources': [ + # SIPSTACK + './src/sipcc/core/sipstack/sip_platform_task.c', + + # PLAT + './src/sipcc/plat/common/dns_utils.c', + + # CPR + './src/sipcc/cpr/android/cpr_android_errno.c', + './src/sipcc/cpr/android/cpr_android_init.c', + './src/sipcc/cpr/android/cpr_android_ipc.c', + './src/sipcc/cpr/android/cpr_android_locks.c', + './src/sipcc/cpr/android/cpr_android_socket.c', + './src/sipcc/cpr/android/cpr_android_stdio.c', + './src/sipcc/cpr/android/cpr_android_string.c', + './src/sipcc/cpr/android/cpr_android_threads.c', + './src/sipcc/cpr/android/cpr_android_timers_using_select.c', + + './src/sipcc/cpr/android/cpr_assert.h', + './src/sipcc/cpr/android/cpr_android_align.h', + './src/sipcc/cpr/android/cpr_android_assert.h', + './src/sipcc/cpr/android/cpr_android_errno.h', + './src/sipcc/cpr/android/cpr_android_in.h', + './src/sipcc/cpr/android/cpr_darwin_ipc.h', + './src/sipcc/cpr/android/cpr_android_locks.h', + './src/sipcc/cpr/android/cpr_android_private.h', + './src/sipcc/cpr/android/cpr_android_rand.h', + './src/sipcc/cpr/android/cpr_android_socket.h', + './src/sipcc/cpr/android/cpr_android_stdio.h', + './src/sipcc/cpr/android/cpr_android_string.h', + './src/sipcc/cpr/android/cpr_android_strings.h', + './src/sipcc/cpr/android/cpr_android_time.h', + './src/sipcc/cpr/android/cpr_android_timers.h', + './src/sipcc/cpr/android/cpr_android_tst.h', + './src/sipcc/cpr/android/cpr_android_types.h', + ], + }], + ['OS=="linux"', { 'sources': [ # SIPSTACK './src/sipcc/core/sipstack/sip_platform_task.c', @@ -612,8 +666,8 @@ './src/sipcc/cpr/linux/cpr_linux_stdio.c', './src/sipcc/cpr/linux/cpr_linux_string.c', './src/sipcc/cpr/linux/cpr_linux_threads.c', - './src/sipcc/cpr/linux/cpr_linux_timers_using_select.c', - + './src/sipcc/cpr/linux/cpr_linux_timers_using_select.c', + './src/sipcc/cpr/linux/cpr_assert.h', './src/sipcc/cpr/linux/cpr_linux_align.h', './src/sipcc/cpr/linux/cpr_linux_assert.h', @@ -631,22 +685,7 @@ './src/sipcc/cpr/linux/cpr_linux_timers.h', './src/sipcc/cpr/linux/cpr_linux_tst.h', './src/sipcc/cpr/linux/cpr_linux_types.h', - - ], - 'defines' : [ - 'SIP_OS_LINUX', - '_GNU_SOURCE', - 'CPR_MEMORY_LITTLE_ENDIAN', - 'NO_SOCKET_POLLING', - 'USE_TIMER_SELECT_BASED', - 'FULL_BUILD', - 'STUBBED_OUT', - 'USE_PRINTF' - 'LINUX', - ], - - 'cflags_mozilla': [ ], }], ['OS=="win"', { diff --git a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp index 7d0e9ee0abd5..566fcac031ed 100644 --- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp +++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp @@ -60,9 +60,10 @@ typedef enum { using namespace CSF; -VcmSIPCCBinding * VcmSIPCCBinding::_pSelf = NULL; -int VcmSIPCCBinding::mAudioCodecMask = 0; -int VcmSIPCCBinding::mVideoCodecMask = 0; +VcmSIPCCBinding * VcmSIPCCBinding::gSelf = NULL; +int VcmSIPCCBinding::gAudioCodecMask = 0; +int VcmSIPCCBinding::gVideoCodecMask = 0; +nsIThread *VcmSIPCCBinding::gMainThread = NULL; static mozilla::RefPtr vcmCreateTransportFlow(sipcc::PeerConnectionImpl *pc, int level, bool rtcp, @@ -70,17 +71,27 @@ static mozilla::RefPtr vcmCreateTransportFlow(sipcc::PeerConnecti const char *fingerprint ); +// Convenience macro to acquire PC + +#define ENSURE_PC(pc, errval) \ + do { \ + if (!pc.impl()) { \ + CSFLogDebug(logTag, "%s: couldn't acquire peerconnection %s", __FUNCTION__, peerconnection); \ + return errval; \ + } \ + } while(0) + VcmSIPCCBinding::VcmSIPCCBinding () : streamObserver(NULL) { - delete _pSelf;//delete is NULL safe, so I don't need to check if it's NULL - _pSelf = this; + delete gSelf;//delete is NULL safe, so I don't need to check if it's NULL + gSelf = this; } VcmSIPCCBinding::~VcmSIPCCBinding () { - assert(_pSelf != NULL); - _pSelf = NULL; + assert(gSelf); + gSelf = NULL; } void VcmSIPCCBinding::setStreamObserver(StreamObserver* obs) @@ -91,8 +102,8 @@ void VcmSIPCCBinding::setStreamObserver(StreamObserver* obs) /* static */ StreamObserver * VcmSIPCCBinding::getStreamObserver() { - if (_pSelf != NULL) - return _pSelf->streamObserver; + if (gSelf != NULL) + return gSelf->streamObserver; return NULL; } @@ -105,8 +116,8 @@ void VcmSIPCCBinding::setMediaProviderObserver(MediaProviderObserver* obs) MediaProviderObserver * VcmSIPCCBinding::getMediaProviderObserver() { - if (_pSelf != NULL) - return _pSelf->mediaProviderObserver; + if (gSelf != NULL) + return gSelf->mediaProviderObserver; return NULL; } @@ -114,25 +125,34 @@ MediaProviderObserver * VcmSIPCCBinding::getMediaProviderObserver() void VcmSIPCCBinding::setAudioCodecs(int codecMask) { CSFLogDebugS(logTag, "SETTING AUDIO: " << codecMask); - VcmSIPCCBinding::mAudioCodecMask = codecMask; + VcmSIPCCBinding::gAudioCodecMask = codecMask; } void VcmSIPCCBinding::setVideoCodecs(int codecMask) { CSFLogDebugS(logTag, "SETTING VIDEO: " << codecMask); - VcmSIPCCBinding::mVideoCodecMask = codecMask; + VcmSIPCCBinding::gVideoCodecMask = codecMask; } int VcmSIPCCBinding::getAudioCodecs() { - return VcmSIPCCBinding::mAudioCodecMask; + return VcmSIPCCBinding::gAudioCodecMask; } int VcmSIPCCBinding::getVideoCodecs() { - return VcmSIPCCBinding::mVideoCodecMask; + return VcmSIPCCBinding::gVideoCodecMask; } +void VcmSIPCCBinding::setMainThread(nsIThread *thread) +{ + gMainThread = thread; +} + +nsIThread* VcmSIPCCBinding::getMainThread() +{ + return gMainThread; +} /* static */ AudioTermination * VcmSIPCCBinding::getAudioTermination() @@ -393,16 +413,16 @@ void vcmRxAllocPort(cc_mcapid_t mcap_id, * @return void * */ -void vcmRxAllocICE(cc_mcapid_t mcap_id, - cc_groupid_t group_id, - cc_streamid_t stream_id, - cc_call_handle_t call_handle, - const char *peerconnection, - uint16_t level, - char **default_addrp, /* Out */ - int *default_portp, /* Out */ - char ***candidatesp, /* Out */ - int *candidate_ctp /* Out */ +static void vcmRxAllocICE_m(cc_mcapid_t mcap_id, + cc_groupid_t group_id, + cc_streamid_t stream_id, + cc_call_handle_t call_handle, + const char *peerconnection, + uint16_t level, + char **default_addrp, /* Out */ + int *default_portp, /* Out */ + char ***candidatesp, /* Out */ + int *candidate_ctp /* Out */ ) { *default_portp = -1; @@ -413,17 +433,11 @@ void vcmRxAllocICE(cc_mcapid_t mcap_id, // Note: we don't acquire any media resources here, and we assume that the // ICE streams already exist, so we're just acquiring them. Any logic // to make them on demand is elsewhere. - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - CSFLogError(logTag, "%s: AcquireInstance returned NULL", __FUNCTION__); - return; - } + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, /**/); CSFLogDebug( logTag, "%s: Getting stream %d", __FUNCTION__, level); - mozilla::RefPtr stream = pc->impl()->media()-> + mozilla::RefPtr stream = pc.impl()->media()-> ice_media_stream(level-1); MOZ_ASSERT(stream); if (!stream) { @@ -465,6 +479,50 @@ void vcmRxAllocICE(cc_mcapid_t mcap_id, } +/** + * Gets the ICE parameters for a stream. Called "alloc" for style consistency + * + * This is a thunk to vcmRxAllocICE_m + * + * @param[in] group_id - group identifier to which stream belongs. + * @param[in] stream_id - stream identifier + * @param[in] call_handle - call identifier + * @param[in] peerconnection - the peerconnection in use + * @param[out] default_addrp - the ICE default addr + * @param[out] port_allocatedp - the ICE default port + * @param[out] candidatesp - the ICE candidate array + * @param[out] candidate_ctp length of the array + * + * @return void + * + */ +void vcmRxAllocICE(cc_mcapid_t mcap_id, + cc_groupid_t group_id, + cc_streamid_t stream_id, + cc_call_handle_t call_handle, + const char *peerconnection, + uint16_t level, + char **default_addrp, /* Out */ + int *default_portp, /* Out */ + char ***candidatesp, /* Out */ + int *candidate_ctp /* Out */ + ) +{ + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNM(&vcmRxAllocICE_m, + mcap_id, + group_id, + stream_id, + call_handle, + peerconnection, + level, + default_addrp, + default_portp, + candidatesp, + candidate_ctp), + NS_DISPATCH_SYNC); +} + /* Get ICE global parameters (ufrag and pwd) * @param[in] peerconnection - the peerconnection in use * @param[out] ufragp - where to put the ufrag @@ -472,7 +530,9 @@ void vcmRxAllocICE(cc_mcapid_t mcap_id, * * @return void */ -void vcmGetIceParams(const char *peerconnection, char **ufragp, char **pwdp) +static void vcmGetIceParams_m(const char *peerconnection, + char **ufragp, + char **pwdp) { CSFLogDebug( logTag, "%s: PC = %s", __FUNCTION__, peerconnection); @@ -481,15 +541,10 @@ void vcmGetIceParams(const char *peerconnection, char **ufragp, char **pwdp) // Note: we don't acquire any media resources here, and we assume that the // ICE streams already exist, so we're just acquiring them. Any logic // to make them on demand is elsewhere. - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - return; - } + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, /**/); - std::vector attrs = pc->impl()->media()-> + std::vector attrs = pc.impl()->media()-> ice_ctx()->GetGlobalAttributes(); // Now fish through these looking for a ufrag and passwd @@ -530,6 +585,27 @@ void vcmGetIceParams(const char *peerconnection, char **ufragp, char **pwdp) return; } +/* Get ICE global parameters (ufrag and pwd) + * + * This is a thunk to vcmGetIceParams_m. + * + * @param[in] peerconnection - the peerconnection in use + * @param[out] ufragp - where to put the ufrag + * @param[out] pwdp - where to put the pwd + * + * @return void + */ +void vcmGetIceParams(const char *peerconnection, + char **ufragp, + char **pwdp) +{ + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNM(&vcmGetIceParams_m, + peerconnection, + ufragp, + pwdp), + NS_DISPATCH_SYNC); +} /* Set remote ICE global parameters. @@ -540,17 +616,14 @@ void vcmGetIceParams(const char *peerconnection, char **ufragp, char **pwdp) * * @return 0 success, error failure */ -short vcmSetIceSessionParams(const char *peerconnection, char *ufrag, char *pwd) +static short vcmSetIceSessionParams_m(const char *peerconnection, + char *ufrag, + char *pwd) { CSFLogDebug( logTag, "%s: PC = %s", __FUNCTION__, peerconnection); - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); std::vector attributes; @@ -559,7 +632,7 @@ short vcmSetIceSessionParams(const char *peerconnection, char *ufrag, char *pwd) if (pwd) attributes.push_back(pwd); - nsresult res = pc->impl()->media()->ice_ctx()-> + nsresult res = pc.impl()->media()->ice_ctx()-> ParseGlobalAttributes(attributes); if (!NS_SUCCEEDED(res)) { @@ -570,6 +643,33 @@ short vcmSetIceSessionParams(const char *peerconnection, char *ufrag, char *pwd) return 0; } +/* Set remote ICE global parameters. + * + * This is a thunk to vcmSetIceSessionParams_m. + * + * @param[in] peerconnection - the peerconnection in use + * @param[in] ufrag - the ufrag + * @param[in] pwd - the pwd + * + * @return 0 success, error failure + */ +short vcmSetIceSessionParams(const char *peerconnection, + char *ufrag, + char *pwd) +{ + short ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmSetIceSessionParams_m, + peerconnection, + ufrag, + pwd, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + /* Set ice candidate for trickle ICE. * * @param[in] peerconnection - the peerconnection in use @@ -578,26 +678,23 @@ short vcmSetIceSessionParams(const char *peerconnection, char *ufrag, char *pwd) * * @return 0 success, error failure */ -short vcmSetIceCandidate(const char *peerconnection, const char *icecandidate, uint16_t level) +static short vcmSetIceCandidate_m(const char *peerconnection, + const char *icecandidate, + uint16_t level) { CSFLogDebug( logTag, "%s: PC = %s", __FUNCTION__, peerconnection); - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); CSFLogDebug( logTag, "%s(): Getting stream %d", __FUNCTION__, level); - mozilla::RefPtr stream = pc->impl()->media()-> + mozilla::RefPtr stream = pc.impl()->media()-> ice_media_stream(level-1); if (!stream) return VCM_ERROR; nsresult res; - nsresult rv = pc->impl()->media()->ice_ctx()->thread()->Dispatch( + nsresult rv = pc.impl()->media()->ice_ctx()->thread()->Dispatch( WrapRunnableRet(stream, &NrIceMediaStream::ParseTrickleCandidate, icecandidate, &res), NS_DISPATCH_SYNC); @@ -614,25 +711,48 @@ short vcmSetIceCandidate(const char *peerconnection, const char *icecandidate, u return 0; } +/* Set ice candidate for trickle ICE. + * + * This is a thunk to vcmSetIceCandidate_m + * + * @param[in] peerconnection - the peerconnection in use + * @param[in] icecandidate - the icecandidate + * @param[in] level - the m line level + * + * @return 0 success, error failure + */ +short vcmSetIceCandidate(const char *peerconnection, + const char *icecandidate, + uint16_t level) +{ + short ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmSetIceCandidate_m, + peerconnection, + icecandidate, + level, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + + /* Start ICE checks * @param[in] peerconnection - the peerconnection in use * @return 0 success, error failure */ -short vcmStartIceChecks(const char *peerconnection) +static short vcmStartIceChecks_m(const char *peerconnection) { CSFLogDebug( logTag, "%s: PC = %s", __FUNCTION__, peerconnection); - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); nsresult res; - nsresult rv = pc->impl()->media()->ice_ctx()->thread()->Dispatch( - WrapRunnableRet(pc->impl()->media()->ice_ctx(), &NrIceCtx::StartChecks, &res), + nsresult rv = pc.impl()->media()->ice_ctx()->thread()->Dispatch( + WrapRunnableRet(pc.impl()->media()->ice_ctx(), &NrIceCtx::StartChecks, &res), NS_DISPATCH_SYNC); if (!NS_SUCCEEDED(rv)) { @@ -649,6 +769,26 @@ short vcmStartIceChecks(const char *peerconnection) } +/* Start ICE checks + * + * This is a thunk to vcmStartIceChecks_m + * + * @param[in] peerconnection - the peerconnection in use + * @return 0 success, error failure + */ +short vcmStartIceChecks(const char *peerconnection) +{ + short ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmStartIceChecks_m, + peerconnection, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + /* Set remote ICE media-level parameters. * * @param[in] peerconnection - the peerconnection in use @@ -659,21 +799,20 @@ short vcmStartIceChecks(const char *peerconnection) * @param[i] candidate_ct - the number of candidates * @return 0 success, error failure */ -short vcmSetIceMediaParams(const char *peerconnection, int level, char *ufrag, char *pwd, - char **candidates, int candidate_ct) +static short vcmSetIceMediaParams_m(const char *peerconnection, + int level, + char *ufrag, + char *pwd, + char **candidates, + int candidate_ct) { CSFLogDebug( logTag, "%s: PC = %s", __FUNCTION__, peerconnection); - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); CSFLogDebug( logTag, "%s(): Getting stream %d", __FUNCTION__, level); - mozilla::RefPtr stream = pc->impl()->media()-> + mozilla::RefPtr stream = pc.impl()->media()-> ice_media_stream(level-1); if (!stream) return VCM_ERROR; @@ -699,6 +838,42 @@ short vcmSetIceMediaParams(const char *peerconnection, int level, char *ufrag, c return 0; } +/* Set remote ICE media-level parameters. + * + * This is a thunk to vcmSetIceMediaParams_w + * + * @param[in] peerconnection - the peerconnection in use + * @param[in] level - the m-line + * @param[in] ufrag - the ufrag + * @param[in] pwd - the pwd + * @param[in] candidates - the candidates + * @param[i] candidate_ct - the number of candidates + * @return 0 success, error failure + */ +short vcmSetIceMediaParams(const char *peerconnection, + int level, + char *ufrag, + char *pwd, + char **candidates, + int candidate_ct) +{ + short ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmSetIceMediaParams_m, + peerconnection, + level, + ufrag, + pwd, + candidates, + candidate_ct, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + + /* * Create a remote stream * @@ -711,7 +886,7 @@ short vcmSetIceMediaParams(const char *peerconnection, int level, char *ufrag, c * * Returns: zero(0) for success; otherwise, ERROR for failure */ -short vcmCreateRemoteStream( +static short vcmCreateRemoteStream_m( cc_mcapid_t mcap_id, const char *peerconnection, int *pc_stream_id) { @@ -719,13 +894,8 @@ short vcmCreateRemoteStream( nsresult res; CSFLogDebug( logTag, "%s", __FUNCTION__); - - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); if (CC_IS_AUDIO(mcap_id)) { hints |= nsDOMMediaStream::HINT_CONTENTS_AUDIO; @@ -735,12 +905,12 @@ short vcmCreateRemoteStream( } sipcc::RemoteSourceStreamInfo* info; - res = pc->impl()->CreateRemoteSourceStreamInfo(hints, &info); + res = pc.impl()->CreateRemoteSourceStreamInfo(hints, &info); if (NS_FAILED(res)) { return VCM_ERROR; } - res = pc->impl()->media()->AddRemoteStream(info, pc_stream_id); + res = pc.impl()->media()->AddRemoteStream(info, pc_stream_id); if (NS_FAILED(res)) { return VCM_ERROR; } @@ -765,6 +935,37 @@ short vcmCreateRemoteStream( return 0; } +/* + * Create a remote stream + * + * This is a thunk to vcmCreateRemoteStream_m + * + * @param[in] mcap_id - group identifier to which stream belongs. + * @param[in] peerconnection - the peerconnection in use + * @param[out] pc_stream_id - the id of the allocated stream + * + * TODO(ekr@rtfm.com): Revise along with everything else for the + * new stream model. + * + * Returns: zero(0) for success; otherwise, ERROR for failure + */ +short vcmCreateRemoteStream(cc_mcapid_t mcap_id, + const char *peerconnection, + int *pc_stream_id) +{ + short ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmCreateRemoteStream_m, + mcap_id, + peerconnection, + pc_stream_id, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + /* * Get DTLS key data @@ -777,23 +978,18 @@ short vcmCreateRemoteStream( * * Returns: zero(0) for success; otherwise, ERROR for failure */ -short vcmGetDtlsIdentity(const char *peerconnection, - char *digest_algp, - size_t max_digest_alg_len, - char *digestp, - size_t max_digest_len) { - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - MOZ_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } +static short vcmGetDtlsIdentity_m(const char *peerconnection, + char *digest_algp, + size_t max_digest_alg_len, + char *digestp, + size_t max_digest_len) { + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); unsigned char digest[TransportLayerDtls::kMaxDigestLength]; size_t digest_len; - nsresult res = pc->impl()->GetIdentity()->ComputeFingerprint("sha-256", digest, + nsresult res = pc.impl()->GetIdentity()->ComputeFingerprint("sha-256", digest, sizeof(digest), &digest_len); if (!NS_SUCCEEDED(res)) { @@ -816,6 +1012,39 @@ short vcmGetDtlsIdentity(const char *peerconnection, return 0; } +/* + * Get DTLS key data + * + * This is a thunk to vcmGetDtlsIdentity_m + * + * @param[in] peerconnection - the peerconnection in use + * @param[out] digest_algp - the digest algorithm e.g. 'SHA-1' + * @param[in] max_digest_alg_len - length of string + * @param[out] digestp - the digest string + * @param[in] max_digest_len - length of string + * + * Returns: zero(0) for success; otherwise, ERROR for failure + */ +short vcmGetDtlsIdentity(const char *peerconnection, + char *digest_algp, + size_t max_digest_alg_len, + char *digestp, + size_t max_digest_len) { + short ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmGetDtlsIdentity_m, + peerconnection, + digest_algp, + max_digest_alg_len, + digestp, + max_digest_len, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + /* Set negotiated DataChannel parameters. * * @param[in] peerconnection - the peerconnection in use @@ -829,15 +1058,6 @@ short vcmSetDataChannelParameters(const char *peerconnection, cc_uint16_t stream { CSFLogDebug( logTag, "%s: PC = %s", __FUNCTION__, peerconnection); - CSFLogDebug( logTag, "%s: acquiring peerconnection %s", __FUNCTION__, peerconnection); - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - PR_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } - - return 0; } @@ -1030,7 +1250,7 @@ int vcmRxStart(cc_mcapid_t mcap_id, * Returns: zero(0) for success; otherwise, ERROR for failure */ -int vcmRxStartICE(cc_mcapid_t mcap_id, +static int vcmRxStartICE_m(cc_mcapid_t mcap_id, cc_groupid_t group_id, cc_streamid_t stream_id, int level, @@ -1046,13 +1266,9 @@ int vcmRxStartICE(cc_mcapid_t mcap_id, { CSFLogDebug( logTag, "%s(%s)", __FUNCTION__, peerconnection); - // Find the PC and get the stream - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - PR_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } + // Find the PC. + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); if(!payloads) { CSFLogError( logTag, "Unitialized payload list"); @@ -1061,7 +1277,7 @@ int vcmRxStartICE(cc_mcapid_t mcap_id, // Find the stream we need nsRefPtr stream = - pc->impl()->media()->GetRemoteStream(pc_stream_id); + pc.impl()->media()->GetRemoteStream(pc_stream_id); if (!stream) { // This should never happen PR_ASSERT(PR_FALSE); @@ -1069,14 +1285,14 @@ int vcmRxStartICE(cc_mcapid_t mcap_id, } // Create the transport flows mozilla::RefPtr rtp_flow = - vcmCreateTransportFlow(pc->impl(), level, false, + vcmCreateTransportFlow(pc.impl(), level, false, fingerprint_alg, fingerprint); if (!rtp_flow) { CSFLogError( logTag, "Could not create RTP flow"); return VCM_ERROR; } mozilla::RefPtr rtcp_flow = - vcmCreateTransportFlow(pc->impl(), level, true, + vcmCreateTransportFlow(pc.impl(), level, true, fingerprint_alg, fingerprint); if (!rtcp_flow) { CSFLogError( logTag, "Could not create RTCP flow"); @@ -1111,8 +1327,8 @@ int vcmRxStartICE(cc_mcapid_t mcap_id, // Now we have all the pieces, create the pipeline stream->StorePipeline(pc_track_id, new mozilla::MediaPipelineReceiveAudio( - pc->impl()->GetMainThread().get(), - pc->impl()->GetSTSThread(), + pc.impl()->GetMainThread().get(), + pc.impl()->GetSTSThread(), stream->GetMediaStream(), conduit, rtp_flow, rtcp_flow)); @@ -1143,8 +1359,8 @@ int vcmRxStartICE(cc_mcapid_t mcap_id, // Now we have all the pieces, create the pipeline stream->StorePipeline(pc_track_id, new mozilla::MediaPipelineReceiveVideo( - pc->impl()->GetMainThread().get(), - pc->impl()->GetSTSThread(), + pc.impl()->GetMainThread().get(), + pc.impl()->GetSTSThread(), stream->GetMediaStream(), conduit, rtp_flow, rtcp_flow)); @@ -1158,6 +1374,67 @@ int vcmRxStartICE(cc_mcapid_t mcap_id, } +/** + * start rx stream + * Same concept as vcmRxStart but for ICE/PeerConnection-based flows + * + * This is a thunk to vcmRxStartICE_m + * + * @param[in] mcap_id - media cap id + * @param[in] group_id - group identifier to which the stream belongs + * @param[in] stream_id - stream id of the given media type. + * @param[in] level - the m-line index + * @param[in] pc_stream_id - the media stream index (from PC.addStream()) + * @param[i]n pc_track_id - the track within the media stream + * @param[in] call_handle - call handle + * @param[in] peerconnection - the peerconnection in use + * @param[in] num_payloads - number of negotiated payloads + * @param[in] payloads - negotiated codec details list + * @param[in] fingerprint_alg - the DTLS fingerprint algorithm + * @param[in] fingerprint - the DTLS fingerprint + * @param[in] attrs - media attributes + * + * Returns: zero(0) for success; otherwise, ERROR for failure + */ + +int vcmRxStartICE(cc_mcapid_t mcap_id, + cc_groupid_t group_id, + cc_streamid_t stream_id, + int level, + int pc_stream_id, + int pc_track_id, + cc_call_handle_t call_handle, + const char *peerconnection, + int num_payloads, + const vcm_payload_info_t* payloads, + const char *fingerprint_alg, + const char *fingerprint, + vcm_mediaAttrs_t *attrs) +{ + int ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmRxStartICE_m, + mcap_id, + group_id, + stream_id, + level, + pc_stream_id, + pc_track_id, + call_handle, + peerconnection, + num_payloads, + payloads, + fingerprint_alg, + fingerprint, + attrs, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + + /** * Close the receive stream. * @@ -1596,7 +1873,7 @@ int vcmTxStart(cc_mcapid_t mcap_id, */ #define EXTRACT_DYNAMIC_PAYLOAD_TYPE(PTYPE) ((PTYPE)>>16) -int vcmTxStartICE(cc_mcapid_t mcap_id, +static int vcmTxStartICE_m(cc_mcapid_t mcap_id, cc_groupid_t group_id, cc_streamid_t stream_id, int level, @@ -1613,25 +1890,21 @@ int vcmTxStartICE(cc_mcapid_t mcap_id, CSFLogDebug( logTag, "%s(%s)", __FUNCTION__, peerconnection); // Find the PC and get the stream - mozilla::ScopedDeletePtr pc( - sipcc::PeerConnectionImpl::AcquireInstance(peerconnection)); - PR_ASSERT(pc); - if (!pc) { - return VCM_ERROR; - } - nsRefPtr stream = pc->impl()->media()-> + sipcc::PeerConnectionWrapper pc(peerconnection); + ENSURE_PC(pc, VCM_ERROR); + nsRefPtr stream = pc.impl()->media()-> GetLocalStream(pc_stream_id); // Create the transport flows mozilla::RefPtr rtp_flow = - vcmCreateTransportFlow(pc->impl(), level, false, + vcmCreateTransportFlow(pc.impl(), level, false, fingerprint_alg, fingerprint); if (!rtp_flow) { CSFLogError( logTag, "Could not create RTP flow"); return VCM_ERROR; } mozilla::RefPtr rtcp_flow = - vcmCreateTransportFlow(pc->impl(), level, true, + vcmCreateTransportFlow(pc.impl(), level, true, fingerprint_alg, fingerprint); if (!rtcp_flow) { CSFLogError( logTag, "Could not create RTCP flow"); @@ -1660,8 +1933,8 @@ int vcmTxStartICE(cc_mcapid_t mcap_id, mozilla::RefPtr pipeline = new mozilla::MediaPipelineTransmit( - pc->impl()->GetMainThread().get(), - pc->impl()->GetSTSThread(), + pc.impl()->GetMainThread().get(), + pc.impl()->GetSTSThread(), stream->GetMediaStream(), conduit, rtp_flow, rtcp_flow); @@ -1693,8 +1966,8 @@ int vcmTxStartICE(cc_mcapid_t mcap_id, // Create the pipeline mozilla::RefPtr pipeline = new mozilla::MediaPipelineTransmit( - pc->impl()->GetMainThread().get(), - pc->impl()->GetSTSThread(), + pc.impl()->GetMainThread().get(), + pc.impl()->GetSTSThread(), stream->GetMediaStream(), conduit, rtp_flow, rtcp_flow); @@ -1712,6 +1985,68 @@ int vcmTxStartICE(cc_mcapid_t mcap_id, return 0; } +/** + * start tx stream + * Same concept as vcmTxStart but for ICE/PeerConnection-based flows + * + * This is a thunk to vcmTxStartICE_m + * + * @param[in] mcap_id - media cap id + * @param[in] group_id - group identifier to which the stream belongs + * @param[in] stream_id - stream id of the given media type. + * @param[in] level - the m-line index + * @param[in] pc_stream_id - the media stream index (from PC.addStream()) + * @param[i]n pc_track_id - the track within the media stream + * @param[in] call_handle - call handle + * @param[in] peerconnection - the peerconnection in use + * @param[in] payload - payload type + * @param[in] tos - bit marking + * @param[in] fingerprint_alg - the DTLS fingerprint algorithm + * @param[in] fingerprint - the DTLS fingerprint + * @param[in] attrs - media attributes + * + * Returns: zero(0) for success; otherwise, ERROR for failure + * + */ +#define EXTRACT_DYNAMIC_PAYLOAD_TYPE(PTYPE) ((PTYPE)>>16) + +int vcmTxStartICE(cc_mcapid_t mcap_id, + cc_groupid_t group_id, + cc_streamid_t stream_id, + int level, + int pc_stream_id, + int pc_track_id, + cc_call_handle_t call_handle, + const char *peerconnection, + const vcm_payload_info_t *payload, + short tos, + const char *fingerprint_alg, + const char *fingerprint, + vcm_mediaAttrs_t *attrs) +{ + int ret; + + VcmSIPCCBinding::getMainThread()->Dispatch( + WrapRunnableNMRet(&vcmTxStartICE_m, + mcap_id, + group_id, + stream_id, + level, + pc_stream_id, + pc_track_id, + call_handle, + peerconnection, + payload, + tos, + fingerprint_alg, + fingerprint, + attrs, + &ret), + NS_DISPATCH_SYNC); + + return ret; +} + /** * Close the transmit stream diff --git a/media/webrtc/signaling/src/media/VcmSIPCCBinding.h b/media/webrtc/signaling/src/media/VcmSIPCCBinding.h index 678413ccd65a..4167e0227307 100644 --- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.h +++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.h @@ -10,6 +10,8 @@ extern "C" #include "ccapi_types.h" } +class nsIThread; + namespace CSF { class AudioTermination; @@ -53,12 +55,16 @@ namespace CSF static int getAudioCodecs(); static int getVideoCodecs(); + static void setMainThread(nsIThread *thread); + static nsIThread *getMainThread(); + private: - static VcmSIPCCBinding * _pSelf; + static VcmSIPCCBinding * gSelf; StreamObserver* streamObserver; MediaProviderObserver *mediaProviderObserver; - static int mAudioCodecMask; - static int mVideoCodecMask; + static int gAudioCodecMask; + static int gVideoCodecMask; + static nsIThread *gMainThread; }; } diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp index f6396715256f..b8c90101bf8f 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp @@ -442,10 +442,13 @@ nsresult MediaPipelineTransmit::Init() { "audio" : "video") << " hints=" << stream_->GetHintContents()); - return RUN_ON_THREAD(main_thread_, WrapRunnable(stream_->GetStream(), + // Force this to be a refptr so that we are holding a strong reference + // to the media stream. + nsRefPtr stream (stream_->GetStream()); + return RUN_ON_THREAD(main_thread_, WrapRunnable(stream, &MediaStream::AddListener, listener_), - NS_DISPATCH_SYNC); + NS_DISPATCH_NORMAL); } nsresult MediaPipeline::PipelineTransport::SendRtpPacket( @@ -665,10 +668,14 @@ void MediaPipelineTransmit::ProcessVideoChunk(VideoSessionConduit *conduit, nsresult MediaPipelineReceiveAudio::Init() { MOZ_MTLOG(PR_LOG_DEBUG, __FUNCTION__); - return RUN_ON_THREAD(main_thread_, WrapRunnable(stream_->GetStream(), - &MediaStream::AddListener, - listener_), - NS_DISPATCH_SYNC); + + // Force this to be a refptr so that we are holding a strong reference + // to the media stream. + nsRefPtr stream (stream_->GetStream()); + return RUN_ON_THREAD(main_thread_, WrapRunnable(stream, + &MediaStream::AddListener, + listener_), + NS_DISPATCH_NORMAL); } void MediaPipelineReceiveAudio::PipelineListener:: diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp index 4e2ad32d00cf..974905d845b2 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp @@ -13,39 +13,64 @@ #include "CC_SIPCCDeviceInfo.h" #include "CSFLog.h" #include "vcm.h" +#include "VcmSIPCCBinding.h" #include "PeerConnectionImpl.h" #include "PeerConnectionCtx.h" +#include "runnable_utils.h" #include "cpr_socket.h" static const char* logTag = "PeerConnectionCtx"; namespace sipcc { -PeerConnectionCtx* PeerConnectionCtx::instance; +PeerConnectionCtx* PeerConnectionCtx::gInstance; +nsIThread* PeerConnectionCtx::gMainThread; + +nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread) { + if (!gMainThread) { + gMainThread = mainThread; + CSF::VcmSIPCCBinding::setMainThread(gMainThread); + } else { + MOZ_ASSERT(gMainThread == mainThread); + } + bool on; + + nsresult res; + +#ifdef MOZILLA_INTERNAL_API + // This check fails on the unit tests because they do not + // have the right thread behavior. + res = gMainThread->IsOnCurrentThread(&on); + NS_ENSURE_SUCCESS(res, res); + MOZ_ASSERT(on); +#endif + + if (!gInstance) { + CSFLogDebug(logTag, "Creating PeerConnectionCtx"); + PeerConnectionCtx *ctx = new PeerConnectionCtx(); + + res = ctx->Initialize(); + PR_ASSERT(NS_SUCCEEDED(res)); + if (!NS_SUCCEEDED(res)) + return res; + + gInstance = ctx; + } + + return NS_OK; +} PeerConnectionCtx* PeerConnectionCtx::GetInstance() { - if (instance) - return instance; - - CSFLogDebug(logTag, "Creating PeerConnectionCtx"); - PeerConnectionCtx *ctx = new PeerConnectionCtx(); - - nsresult res = ctx->Initialize(); - PR_ASSERT(NS_SUCCEEDED(res)); - if (!NS_SUCCEEDED(res)) - return NULL; - - instance = ctx; - - return instance; + MOZ_ASSERT(gInstance); + return gInstance; } void PeerConnectionCtx::Destroy() { CSFLogDebug(logTag, "%s", __FUNCTION__); - instance->Cleanup(); - delete instance; - instance = NULL; + gInstance->Cleanup(); + delete gInstance; + gInstance = NULL; } nsresult PeerConnectionCtx::Initialize() { @@ -118,20 +143,34 @@ void PeerConnectionCtx::onDeviceEvent(ccapi_device_event_e aDeviceEvent, } } -// Demux the call event to the right PeerConnection void PeerConnectionCtx::onCallEvent(ccapi_call_event_e aCallEvent, - CSF::CC_CallPtr aCall, - CSF::CC_CallInfoPtr aInfo) { - CSFLogDebug(logTag, "onCallEvent()"); - mozilla::ScopedDeletePtr pc( - PeerConnectionImpl::AcquireInstance( - aCall->getPeerConnection())); + CSF::CC_CallPtr aCall, + CSF::CC_CallInfoPtr aInfo) { + // This is called on a SIPCC thread. + // WARNING: Do not make this NS_DISPATCH_NORMAL. + // CC_*Ptr is not thread-safe so we must not manipulate + // the ref count on multiple threads at once. + // NS_DISPATCH_SYNC enforces this and because this is + // not a real nsThread, we don't have to worry about + // reentrancy. + RUN_ON_THREAD(gMainThread, + WrapRunnable(this, + &PeerConnectionCtx::onCallEvent_m, + aCallEvent, aCall, aInfo), + NS_DISPATCH_SYNC); +} - if (!pc) // This must be an event on a dead PC. Ignore +// Demux the call event to the right PeerConnection +void PeerConnectionCtx::onCallEvent_m(ccapi_call_event_e aCallEvent, + CSF::CC_CallPtr aCall, + CSF::CC_CallInfoPtr aInfo) { + CSFLogDebug(logTag, "onCallEvent()"); + PeerConnectionWrapper pc(aCall->getPeerConnection()); + if (!pc.impl()) // This must be an event on a dead PC. Ignore return; CSFLogDebug(logTag, "Calling PC"); - pc->impl()->onCallEvent(aCallEvent, aCall, aInfo); + pc.impl()->onCallEvent(aCallEvent, aCall, aInfo); } } // namespace sipcc diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h index e8ef9de1e039..cb50690b3096 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h @@ -17,12 +17,14 @@ namespace sipcc { -// Currently SIPCC only allows a single stack instance to exist in a process -// at once. This class implements a singleton object that wraps that -// instance. It also hosts the observer class that demuxes events onto -// individual PCs. +// A class to hold some of the singleton objects we need: +// * The global PeerConnectionImpl table and its associated lock. +// * Currently SIPCC only allows a single stack instance to exist in a process +// at once. This class implements a singleton object that wraps that. +// * The observer class that demuxes events onto individual PCs. class PeerConnectionCtx : public CSF::CC_Observer { public: + static nsresult InitializeGlobal(nsIThread *mainThread); static PeerConnectionCtx* GetInstance(); static void Destroy(); @@ -37,7 +39,14 @@ class PeerConnectionCtx : public CSF::CC_Observer { PeerConnectionImpl::SipccState sipcc_state() { return mSipccState; } + // Make these classes friend so that they can access mPeerconnections. + friend class PeerConnectionImpl; + friend class PeerConnectionWrapper; + private: + // We could make these available only via accessors but it's too much trouble. + std::map mPeerConnections; + PeerConnectionCtx() : mSipccState(PeerConnectionImpl::kIdle), mCCM(NULL), mDevice(NULL) {} // This is a singleton, so don't copy construct it, etc. @@ -52,12 +61,17 @@ class PeerConnectionCtx : public CSF::CC_Observer { mSipccState = aState; } + virtual void onCallEvent_m(ccapi_call_event_e callEvent, + CSF::CC_CallPtr call, + CSF::CC_CallInfoPtr info); + // SIPCC objects PeerConnectionImpl::SipccState mSipccState; // TODO(ekr@rtfm.com): refactor this out? What does it do? CSF::CallControlManagerPtr mCCM; CSF::CC_DevicePtr mDevice; - static PeerConnectionCtx *instance; + static PeerConnectionCtx *gInstance; + static nsIThread *gMainThread; }; } // namespace sipcc diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp index 063df524e55e..ac49b8a9e128 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -106,162 +106,104 @@ void MediaConstraints::buildArray(cc_media_constraints_t** constraintarray) { (*constraintarray)->constraint_count = i; } -typedef enum { - PC_OBSERVER_CALLBACK, - PC_OBSERVER_CONNECTION, - PC_OBSERVER_CLOSEDCONNECTION, - PC_OBSERVER_DATACHANNEL, - PC_OBSERVER_ICE, - PC_OBSERVER_READYSTATE -} PeerConnectionObserverType; - -// TODO: Refactor this. class PeerConnectionObserverDispatch : public nsRunnable { public: PeerConnectionObserverDispatch(CSF::CC_CallInfoPtr aInfo, nsRefPtr aPC, - IPeerConnectionObserver* aObserver) : - mType(PC_OBSERVER_CALLBACK), mInfo(aInfo), mChannel(nullptr), mPC(aPC), mObserver(aObserver) {} - - PeerConnectionObserverDispatch(PeerConnectionObserverType aType, - nsRefPtr aChannel, - nsRefPtr aPC, - IPeerConnectionObserver* aObserver) : - mType(aType), mInfo(nullptr), mChannel(aChannel), mPC(aPC), mObserver(aObserver) {} - - PeerConnectionObserverDispatch(PeerConnectionObserverType aType, - nsRefPtr aPC, - IPeerConnectionObserver* aObserver) : - mType(aType), mInfo(nullptr), mPC(aPC), mObserver(aObserver) {} + IPeerConnectionObserver* aObserver) + : mPC(aPC), + mObserver(aObserver), + mCode(static_cast(aInfo->getStatusCode())), + mSdpStr(), + mCallState(aInfo->getCallState()), + mStateStr(aInfo->callStateToString(mCallState)) { + if (mCallState == REMOTESTREAMADD) { + MediaStreamTable *streams = NULL; + streams = aInfo->getMediaStreams(); + mRemoteStream = mPC->media()->GetRemoteStream(streams->media_stream_id); + MOZ_ASSERT(mRemoteStream); + } + if ((mCallState == CREATEOFFER) || (mCallState == CREATEANSWER)) { + mSdpStr = aInfo->getSDP(); + } + } ~PeerConnectionObserverDispatch(){} - NS_IMETHOD Run() - { - switch (mType) { - case PC_OBSERVER_CALLBACK: + NS_IMETHOD Run() { + switch (mCallState) { + case CREATEOFFER: + mObserver->OnCreateOfferSuccess(mSdpStr.c_str()); + break; + + case CREATEANSWER: + mObserver->OnCreateAnswerSuccess(mSdpStr.c_str()); + break; + + case CREATEOFFERERROR: + mObserver->OnCreateOfferError(mCode); + break; + + case CREATEANSWERERROR: + mObserver->OnCreateAnswerError(mCode); + break; + + case SETLOCALDESC: + mObserver->OnSetLocalDescriptionSuccess(mCode); + break; + + case SETREMOTEDESC: + mObserver->OnSetRemoteDescriptionSuccess(mCode); + break; + + case SETLOCALDESCERROR: + mObserver->OnSetLocalDescriptionError(mCode); + break; + + case SETREMOTEDESCERROR: + mObserver->OnSetRemoteDescriptionError(mCode); + break; + + case REMOTESTREAMADD: { - StatusCode code; - std::string s_sdpstr; - MediaStreamTable *streams = NULL; - - cc_call_state_t state = mInfo->getCallState(); - std::string statestr = mInfo->callStateToString(state); - nsDOMMediaStream* stream; uint32_t hint; - switch (state) { - case CREATEOFFER: - s_sdpstr = mInfo->getSDP(); - mObserver->OnCreateOfferSuccess(s_sdpstr.c_str()); - break; - - case CREATEANSWER: - s_sdpstr = mInfo->getSDP(); - mObserver->OnCreateAnswerSuccess(s_sdpstr.c_str()); - break; - - case CREATEOFFERERROR: - code = (StatusCode)mInfo->getStatusCode(); - mObserver->OnCreateOfferError(code); - break; - - case CREATEANSWERERROR: - code = (StatusCode)mInfo->getStatusCode(); - mObserver->OnCreateAnswerError(code); - break; - - case SETLOCALDESC: - code = (StatusCode)mInfo->getStatusCode(); - mObserver->OnSetLocalDescriptionSuccess(code); - break; - - case SETREMOTEDESC: - code = (StatusCode)mInfo->getStatusCode(); - mObserver->OnSetRemoteDescriptionSuccess(code); - break; - - case SETLOCALDESCERROR: - code = (StatusCode)mInfo->getStatusCode(); - mObserver->OnSetLocalDescriptionError(code); - break; - - case SETREMOTEDESCERROR: - code = (StatusCode)mInfo->getStatusCode(); - mObserver->OnSetRemoteDescriptionError(code); - break; - - case REMOTESTREAMADD: - { - streams = mInfo->getMediaStreams(); - nsRefPtr remoteStream = mPC->media()-> - GetRemoteStream(streams->media_stream_id); - - MOZ_ASSERT(remoteStream); - if (!remoteStream) - { - CSFLogErrorS(logTag, __FUNCTION__ << " GetRemoteStream returned NULL"); - } - else - { - stream = remoteStream->GetMediaStream(); - hint = stream->GetHintContents(); - if (hint == nsDOMMediaStream::HINT_CONTENTS_AUDIO) { - mObserver->OnAddStream(stream, "audio"); - } else if (hint == nsDOMMediaStream::HINT_CONTENTS_VIDEO) { - mObserver->OnAddStream(stream, "video"); - } else { - CSFLogErrorS(logTag, __FUNCTION__ << "Audio & Video not supported"); - MOZ_ASSERT(PR_FALSE); - } - } - break; + if (!mRemoteStream) { + CSFLogErrorS(logTag, __FUNCTION__ << " GetRemoteStream returned NULL"); + } else { + stream = mRemoteStream->GetMediaStream(); + hint = stream->GetHintContents(); + if (hint == nsDOMMediaStream::HINT_CONTENTS_AUDIO) { + mObserver->OnAddStream(stream, "audio"); + } else if (hint == nsDOMMediaStream::HINT_CONTENTS_VIDEO) { + mObserver->OnAddStream(stream, "video"); + } else { + CSFLogErrorS(logTag, __FUNCTION__ << "Audio & Video not supported"); + MOZ_ASSERT(PR_FALSE); } - default: - CSFLogDebugS(logTag, ": **** UNHANDLED CALL STATE : " << statestr); - break; } break; } - case PC_OBSERVER_CONNECTION: - CSFLogDebugS(logTag, __FUNCTION__ << ": Delivering PeerConnection onconnection"); - mObserver->NotifyConnection(); + default: + CSFLogDebugS(logTag, ": **** UNHANDLED CALL STATE : " << mStateStr); break; - case PC_OBSERVER_CLOSEDCONNECTION: - CSFLogDebugS(logTag, __FUNCTION__ << ": Delivering PeerConnection onclosedconnection"); - mObserver->NotifyClosedConnection(); - break; - case PC_OBSERVER_DATACHANNEL: - CSFLogDebugS(logTag, __FUNCTION__ << ": Delivering PeerConnection ondatachannel"); - mObserver->NotifyDataChannel(mChannel); -#ifdef MOZILLA_INTERNAL_API - NS_DataChannelAppReady(mChannel); -#endif - break; - case PC_OBSERVER_ICE: - CSFLogDebugS(logTag, __FUNCTION__ << ": Delivering PeerConnection ICE callback "); - mObserver->OnStateChange(IPeerConnectionObserver::kIceState); - break; - case PC_OBSERVER_READYSTATE: - CSFLogDebugS(logTag, __FUNCTION__ << ": Delivering PeerConnection Ready State callback "); - mObserver->OnStateChange(IPeerConnectionObserver::kReadyState); } + return NS_OK; } private: - PeerConnectionObserverType mType; - CSF::CC_CallInfoPtr mInfo; - nsRefPtr mChannel; nsRefPtr mPC; nsCOMPtr mObserver; + StatusCode mCode; + std::string mSdpStr; + cc_call_state_t mCallState; + std::string mStateStr; + nsRefPtr mRemoteStream; }; -std::map - PeerConnectionImpl::peerconnections; - NS_IMPL_THREADSAFE_ISUPPORTS1(PeerConnectionImpl, IPeerConnection) PeerConnectionImpl::PeerConnectionImpl() @@ -273,19 +215,27 @@ PeerConnectionImpl::PeerConnectionImpl() , mWindow(NULL) , mIdentity(NULL) , mSTSThread(NULL) - , mMedia(new PeerConnectionMedia(this)) - {} + , mMedia(new PeerConnectionMedia(this)) { + MOZ_ASSERT(NS_IsMainThread()); +} PeerConnectionImpl::~PeerConnectionImpl() { - Close(false); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + PeerConnectionCtx::GetInstance()->mPeerConnections.erase(mHandle); + CloseInt(false); + +#if 0 + // TODO(ekr@rtfm.com): figure out how to shut down PCCtx. + // bug 820011. + // Since this and Initialize() occur on MainThread, they can't both be // running at once // Might be more optimal to release off a timer (and XPCOM Shutdown) // to avoid churn - peerconnections.erase(mHandle); - if (peerconnections.empty()) + if (PeerConnectionCtx::GetInstance()->mPeerConnections.empty()) Shutdown(); +#endif /* We should release mPCObserver on the main thread, but also prevent a double free. nsCOMPtr mainThread; @@ -303,8 +253,7 @@ PeerConnectionImpl::MakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aRetval) nsRefPtr stream = nsDOMMediaStream::CreateSourceStream(aHint); NS_ADDREF(*aRetval = stream); - CSFLogDebugS(logTag, "PeerConnection " << static_cast(this) - << ": Created media stream " << static_cast(stream) + CSFLogDebugS(logTag, "Created media stream " << static_cast(stream) << " inner: " << static_cast(stream->GetStream())); return NS_OK; @@ -326,18 +275,11 @@ nsresult PeerConnectionImpl::CreateRemoteSourceStreamInfo(uint32_t aHint, RemoteSourceStreamInfo** aInfo) { MOZ_ASSERT(aInfo); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); nsIDOMMediaStream* stream; - - nsresult res; - if (!mThread || NS_IsMainThread()) { - res = MakeMediaStream(aHint, &stream); - } else { - mThread->Dispatch(WrapRunnableRet( - this, &PeerConnectionImpl::MakeMediaStream, aHint, &stream, &res - ), NS_DISPATCH_SYNC); - } - + + nsresult res = MakeMediaStream(aHint, &stream); if (NS_FAILED(res)) { return res; } @@ -352,8 +294,8 @@ PeerConnectionImpl::CreateRemoteSourceStreamInfo(uint32_t aHint, RemoteSourceStr return NS_OK; } - mThread->Dispatch(WrapRunnableRet( - this, &PeerConnectionImpl::MakeRemoteSource, comstream, aInfo, &res + mThread->Dispatch(WrapRunnableNMRet( + &PeerConnectionImpl::MakeRemoteSource, comstream, aInfo, &res ), NS_DISPATCH_SYNC); if (NS_FAILED(res)) { @@ -369,6 +311,7 @@ PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver, nsIThread* aThread) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aObserver); + MOZ_ASSERT(aThread); mPCObserver = aObserver; nsresult res; @@ -379,6 +322,8 @@ PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver, NS_ENSURE_SUCCESS(res, res); #endif + mThread = aThread; + #ifdef MOZILLA_INTERNAL_API // Currently no standalone unit tests for DataChannel, // which is the user of mWindow @@ -387,8 +332,8 @@ PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver, NS_ENSURE_STATE(mWindow); #endif - // The thread parameter can be passed in as NULL - mThread = aThread; + res = PeerConnectionCtx::InitializeGlobal(mThread); + NS_ENSURE_SUCCESS(res, res); PeerConnectionCtx *pcctx = PeerConnectionCtx::GetInstance(); MOZ_ASSERT(pcctx); @@ -429,7 +374,7 @@ PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver, // Store under mHandle mCall->setPeerConnection(mHandle); - peerconnections[mHandle] = this; + PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this; // Create the DTLS Identity mIdentity = DtlsIdentity::Generate(); @@ -481,6 +426,7 @@ nsresult PeerConnectionImpl::CreateFakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aRetval) { MOZ_ASSERT(aRetval); + PC_AUTO_ENTER_API_CALL(false); bool mute = false; @@ -494,8 +440,8 @@ PeerConnectionImpl::CreateFakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aR if (!mThread || NS_IsMainThread()) { res = MakeMediaStream(aHint, aRetval); } else { - mThread->Dispatch(WrapRunnableRet( - this, &PeerConnectionImpl::MakeMediaStream, aHint, aRetval, &res + mThread->Dispatch(WrapRunnableNMRet( + &PeerConnectionImpl::MakeMediaStream, aHint, aRetval, &res ), NS_DISPATCH_SYNC); } @@ -524,6 +470,8 @@ PeerConnectionImpl::ConnectDataConnection(uint16_t aLocalport, uint16_t aRemoteport, uint16_t aNumstreams) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + #ifdef MOZILLA_INTERNAL_API mDataConnection = new mozilla::DataChannelConnection(this); NS_ENSURE_TRUE(mDataConnection,NS_ERROR_FAILURE); @@ -551,6 +499,7 @@ PeerConnectionImpl::CreateDataChannel(const nsACString& aLabel, uint16_t aMaxNum, nsIDOMDataChannel** aRetval) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aRetval); #ifdef MOZILLA_INTERNAL_API @@ -580,68 +529,65 @@ PeerConnectionImpl::CreateDataChannel(const nsACString& aLabel, void PeerConnectionImpl::NotifyConnection() { - MOZ_ASSERT(NS_IsMainThread()); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); CSFLogDebugS(logTag, __FUNCTION__); #ifdef MOZILLA_INTERNAL_API - if (mPCObserver) { - PeerConnectionObserverDispatch* runnable = - new PeerConnectionObserverDispatch(PC_OBSERVER_CONNECTION, nullptr, - this, mPCObserver); - if (mThread) { - mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - return; - } - runnable->Run(); - } + RUN_ON_THREAD(mThread, + WrapRunnable(mPCObserver, + &IPeerConnectionObserver::NotifyConnection), + NS_DISPATCH_NORMAL); #endif } void PeerConnectionImpl::NotifyClosedConnection() { - MOZ_ASSERT(NS_IsMainThread()); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); CSFLogDebugS(logTag, __FUNCTION__); #ifdef MOZILLA_INTERNAL_API - if (mPCObserver) { - PeerConnectionObserverDispatch* runnable = - new PeerConnectionObserverDispatch(PC_OBSERVER_CLOSEDCONNECTION, nullptr, - this, mPCObserver); - if (mThread) { - mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - return; - } - runnable->Run(); - } + RUN_ON_THREAD(mThread, + WrapRunnable(mPCObserver, + &IPeerConnectionObserver::NotifyClosedConnection), + NS_DISPATCH_NORMAL); #endif } + +#ifdef MOZILLA_INTERNAL_API +// Not a member function so that we don't need to keep the PC live. +static void NotifyDataChannel_m(nsRefPtr aChannel, + nsCOMPtr aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + + aObserver->NotifyDataChannel(aChannel); + NS_DataChannelAppReady(aChannel); +} +#endif + void PeerConnectionImpl::NotifyDataChannel(mozilla::DataChannel *aChannel) { - MOZ_ASSERT(NS_IsMainThread()); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aChannel); CSFLogDebugS(logTag, __FUNCTION__ << ": channel: " << static_cast(aChannel)); #ifdef MOZILLA_INTERNAL_API - nsCOMPtr domchannel; - nsresult rv = NS_NewDOMDataChannel(aChannel, mWindow, - getter_AddRefs(domchannel)); + nsCOMPtr domchannel; + nsresult rv = NS_NewDOMDataChannel(aChannel, mWindow, + getter_AddRefs(domchannel)); NS_ENSURE_SUCCESS(rv,); - if (mPCObserver) { - PeerConnectionObserverDispatch* runnable = - new PeerConnectionObserverDispatch(PC_OBSERVER_DATACHANNEL, domchannel.get(), - this, mPCObserver); - if (mThread) { - mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - return; - } - runnable->Run(); - } + + RUN_ON_THREAD(mThread, + WrapRunnableNM(NotifyDataChannel_m, + domchannel.get(), + mPCObserver), + NS_DISPATCH_NORMAL); #endif } @@ -714,21 +660,23 @@ PeerConnectionImpl::ConvertConstraints( NS_IMETHODIMP PeerConnectionImpl::CreateOffer(const JS::Value& aConstraints, JSContext* aCx) { - CheckIceState(); + PC_AUTO_ENTER_API_CALL(true); - MediaConstraints* cs = new MediaConstraints(); - nsresult rv = ConvertConstraints(aConstraints, cs, aCx); + MediaConstraints cs; + nsresult rv = ConvertConstraints(aConstraints, &cs, aCx); if (rv != NS_OK) { return rv; } - return CreateOffer(*cs); + return CreateOffer(cs); } // Used by unit tests and the IDL CreateOffer. NS_IMETHODIMP PeerConnectionImpl::CreateOffer(MediaConstraints& constraints) { + PC_AUTO_ENTER_API_CALL(true); + mRole = kRoleOfferer; // TODO(ekr@rtfm.com): Interrogate SIPCC here? cc_media_constraints_t* cc_constraints = nullptr; @@ -741,21 +689,22 @@ PeerConnectionImpl::CreateOffer(MediaConstraints& constraints) NS_IMETHODIMP PeerConnectionImpl::CreateAnswer(const JS::Value& aConstraints, JSContext* aCx) { - CheckIceState(); + PC_AUTO_ENTER_API_CALL(true); - MediaConstraints* cs = new MediaConstraints(); - nsresult rv = ConvertConstraints(aConstraints, cs, aCx); + MediaConstraints cs; + nsresult rv = ConvertConstraints(aConstraints, &cs, aCx); if (rv != NS_OK) { return rv; } - CreateAnswer(*cs); - return NS_OK; + return CreateAnswer(cs); } NS_IMETHODIMP PeerConnectionImpl::CreateAnswer(MediaConstraints& constraints) { + PC_AUTO_ENTER_API_CALL(true); + mRole = kRoleAnswerer; // TODO(ekr@rtfm.com): Interrogate SIPCC here? cc_media_constraints_t* cc_constraints = nullptr; @@ -768,12 +717,13 @@ PeerConnectionImpl::CreateAnswer(MediaConstraints& constraints) NS_IMETHODIMP PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + if (!aSDP) { CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); return NS_ERROR_FAILURE; } - CheckIceState(); mLocalRequestedSDP = aSDP; mCall->setLocalDescription((cc_jsep_action_t)aAction, mLocalRequestedSDP); return NS_OK; @@ -782,27 +732,30 @@ PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) NS_IMETHODIMP PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + if (!aSDP) { CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); return NS_ERROR_FAILURE; } - CheckIceState(); mRemoteRequestedSDP = aSDP; mCall->setRemoteDescription((cc_jsep_action_t)action, mRemoteRequestedSDP); return NS_OK; } - NS_IMETHODIMP PeerConnectionImpl::AddIceCandidate(const char* aCandidate, const char* aMid, unsigned short aLevel) { - CheckIceState(); + PC_AUTO_ENTER_API_CALL(true); + mCall->addICECandidate(aCandidate, aMid, aLevel); return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::CloseStreams() { + PC_AUTO_ENTER_API_CALL(false); + if (mReadyState != PeerConnectionImpl::kClosed) { ChangeReadyState(PeerConnectionImpl::kClosing); } @@ -813,6 +766,8 @@ PeerConnectionImpl::CloseStreams() { NS_IMETHODIMP PeerConnectionImpl::AddStream(nsIDOMMediaStream* aMediaStream) { + PC_AUTO_ENTER_API_CALL(true); + uint32_t stream_id; nsresult res = mMedia->AddStream(aMediaStream, &stream_id); if (NS_FAILED(res)) @@ -835,6 +790,8 @@ PeerConnectionImpl::AddStream(nsIDOMMediaStream* aMediaStream) { NS_IMETHODIMP PeerConnectionImpl::RemoveStream(nsIDOMMediaStream* aMediaStream) { + PC_AUTO_ENTER_API_CALL(true); + uint32_t stream_id; nsresult res = mMedia->RemoveStream(aMediaStream, &stream_id); @@ -893,6 +850,7 @@ PeerConnectionImpl::GetFingerprint(char** fingerprint) NS_IMETHODIMP PeerConnectionImpl::GetLocalDescription(char** aSDP) { + PC_AUTO_ENTER_API_CALL(true); MOZ_ASSERT(aSDP); char* tmp = new char[mLocalSDP.size() + 1]; @@ -906,6 +864,7 @@ PeerConnectionImpl::GetLocalDescription(char** aSDP) NS_IMETHODIMP PeerConnectionImpl::GetRemoteDescription(char** aSDP) { + PC_AUTO_ENTER_API_CALL(true); MOZ_ASSERT(aSDP); char* tmp = new char[mRemoteSDP.size() + 1]; @@ -919,6 +878,7 @@ PeerConnectionImpl::GetRemoteDescription(char** aSDP) NS_IMETHODIMP PeerConnectionImpl::GetReadyState(uint32_t* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aState); *aState = mReadyState; @@ -928,6 +888,7 @@ PeerConnectionImpl::GetReadyState(uint32_t* aState) NS_IMETHODIMP PeerConnectionImpl::GetSipccState(uint32_t* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aState); PeerConnectionCtx* pcctx = PeerConnectionCtx::GetInstance(); @@ -938,19 +899,42 @@ PeerConnectionImpl::GetSipccState(uint32_t* aState) NS_IMETHODIMP PeerConnectionImpl::GetIceState(uint32_t* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aState); *aState = mIceState; return NS_OK; } +nsresult +PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + PR_ASSERT(!assert_ice_ready || (mIceState != kIceGathering)); + + if (mReadyState == kClosed) + return NS_ERROR_FAILURE; + return NS_OK; +} + NS_IMETHODIMP PeerConnectionImpl::Close(bool aIsSynchronous) { - if (mCall != NULL) + PC_AUTO_ENTER_API_CALL(false); + + return CloseInt(aIsSynchronous); +} + + +nsresult +PeerConnectionImpl::CloseInt(bool aIsSynchronous) +{ + PC_AUTO_ENTER_API_CALL(true); + + if (mCall != nullptr) mCall->endCall(); #ifdef MOZILLA_INTERNAL_API - if (mDataConnection != NULL) + if (mDataConnection) mDataConnection->CloseAll(); #endif @@ -964,13 +948,7 @@ PeerConnectionImpl::Close(bool aIsSynchronous) void PeerConnectionImpl::ShutdownMedia(bool aIsSynchronous) { - // Check that we are on the main thread. - if (mThread) { - bool on; - - MOZ_ASSERT(NS_SUCCEEDED(mThread->IsOnCurrentThread(&on))); - MOZ_ASSERT(on); - } + PC_AUTO_ENTER_API_CALL_NO_CHECK(); if (!mMedia) return; @@ -1000,6 +978,7 @@ void PeerConnectionImpl::onCallEvent(ccapi_call_event_e aCallEvent, CSF::CC_CallPtr aCall, CSF::CC_CallInfoPtr aInfo) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aCall.get()); MOZ_ASSERT(aInfo.get()); @@ -1042,47 +1021,50 @@ PeerConnectionImpl::onCallEvent(ccapi_call_event_e aCallEvent, void PeerConnectionImpl::ChangeReadyState(PeerConnectionImpl::ReadyState aReadyState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); mReadyState = aReadyState; - // FIXME: Dispatch on main thread. - if (mPCObserver) { - PeerConnectionObserverDispatch* runnable = - new PeerConnectionObserverDispatch(PC_OBSERVER_READYSTATE, this, mPCObserver); - if (mThread) { - mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - return; - } - runnable->Run(); - } + // Note that we are passing an nsRefPtr which + // keeps the observer live. + RUN_ON_THREAD(mThread, WrapRunnable(mPCObserver, + &IPeerConnectionObserver::OnStateChange, + IPeerConnectionObserver::kReadyState), + NS_DISPATCH_NORMAL); } -PeerConnectionWrapper *PeerConnectionImpl::AcquireInstance(const std::string& aHandle) -{ - if (peerconnections.find(aHandle) == peerconnections.end()) { - return NULL; +PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle) + : impl_(nullptr) { + if (PeerConnectionCtx::GetInstance()->mPeerConnections.find(handle) == + PeerConnectionCtx::GetInstance()->mPeerConnections.end()) { + return; } - PeerConnectionImpl *impl = peerconnections[aHandle]; - impl->AddRef(); - - return new PeerConnectionWrapper(impl); -} - -void -PeerConnectionImpl::ReleaseInstance() -{ - Release(); + impl_ = PeerConnectionCtx::GetInstance()->mPeerConnections[handle]; } const std::string& PeerConnectionImpl::GetHandle() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); return mHandle; } +// This is called from the STS thread and so we need to thunk +// to the main thread. void PeerConnectionImpl::IceGatheringCompleted(NrIceCtx *aCtx) { + RUN_ON_THREAD(mThread, + WrapRunnable(this, + &PeerConnectionImpl::IceGatheringCompleted_m, + aCtx), + NS_DISPATCH_SYNC); +} + +void +PeerConnectionImpl::IceGatheringCompleted_m(NrIceCtx *aCtx) +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aCtx); CSFLogDebugS(logTag, __FUNCTION__ << ": ctx: " << static_cast(aCtx)); @@ -1091,13 +1073,11 @@ PeerConnectionImpl::IceGatheringCompleted(NrIceCtx *aCtx) #ifdef MOZILLA_INTERNAL_API if (mPCObserver) { - PeerConnectionObserverDispatch* runnable = - new PeerConnectionObserverDispatch(PC_OBSERVER_ICE, this, mPCObserver); - if (mThread) { - mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - return; - } - runnable->Run(); + RUN_ON_THREAD(mThread, + WrapRunnable(mPCObserver, + &IPeerConnectionObserver::OnStateChange, + IPeerConnectionObserver::kIceState), + NS_DISPATCH_NORMAL); } #endif } @@ -1105,6 +1085,17 @@ PeerConnectionImpl::IceGatheringCompleted(NrIceCtx *aCtx) void PeerConnectionImpl::IceCompleted(NrIceCtx *aCtx) { + RUN_ON_THREAD(mThread, + WrapRunnable(this, + &PeerConnectionImpl::IceCompleted_m, + aCtx), + NS_DISPATCH_SYNC); +} + +void +PeerConnectionImpl::IceCompleted_m(NrIceCtx *aCtx) +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aCtx); CSFLogDebugS(logTag, __FUNCTION__ << ": ctx: " << static_cast(aCtx)); @@ -1113,13 +1104,11 @@ PeerConnectionImpl::IceCompleted(NrIceCtx *aCtx) #ifdef MOZILLA_INTERNAL_API if (mPCObserver) { - PeerConnectionObserverDispatch* runnable = - new PeerConnectionObserverDispatch(PC_OBSERVER_ICE, this, mPCObserver); - if (mThread) { - mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - return; - } - runnable->Run(); + RUN_ON_THREAD(mThread, + WrapRunnable(mPCObserver, + &IPeerConnectionObserver::OnStateChange, + IPeerConnectionObserver::kIceState), + NS_DISPATCH_NORMAL); } #endif } @@ -1127,6 +1116,7 @@ PeerConnectionImpl::IceCompleted(NrIceCtx *aCtx) void PeerConnectionImpl::IceStreamReady(NrIceMediaStream *aStream) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aStream); CSFLogDebugS(logTag, __FUNCTION__ << ": " << aStream->name().c_str()); @@ -1157,6 +1147,7 @@ GetStreams(JSContext* cx, PeerConnectionImpl* peerConnection, NS_IMETHODIMP PeerConnectionImpl::GetLocalStreams(JSContext* cx, JS::Value* streams) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); #ifdef MOZILLA_INTERNAL_API return GetStreams(cx, this, MediaStreamList::Local, streams); #else @@ -1167,6 +1158,7 @@ PeerConnectionImpl::GetLocalStreams(JSContext* cx, JS::Value* streams) NS_IMETHODIMP PeerConnectionImpl::GetRemoteStreams(JSContext* cx, JS::Value* streams) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); #ifdef MOZILLA_INTERNAL_API return GetStreams(cx, this, MediaStreamList::Remote, streams); #else diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h index ea3f1f296c84..57f9f5231201 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h @@ -42,6 +42,8 @@ using namespace mozilla; namespace sipcc { +class PeerConnectionWrapper; + struct ConstraintInfo { std::string value; bool mandatory; @@ -60,6 +62,17 @@ private: class PeerConnectionWrapper; +// Enter an API call and check that the state is OK, +// the PC isn't closed, etc. +#define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \ + do { \ + /* do/while prevents res from conflicting with locals */ \ + nsresult res = CheckApiState(assert_ice_ready); \ + if (NS_FAILED(res)) return res; \ + } while(0) +#define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread() + + class PeerConnectionImpl MOZ_FINAL : public IPeerConnection, #ifdef MOZILLA_INTERNAL_API public mozilla::DataChannelConnection::DataConnectionListener, @@ -103,8 +116,16 @@ public: static PeerConnectionImpl* CreatePeerConnection(); static void Shutdown(); + static nsresult ConvertConstraints( + const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx); + static nsresult MakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aStream); + static nsresult MakeRemoteSource(nsDOMMediaStream* aStream, RemoteSourceStreamInfo** aInfo); + + Role GetRole() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mRole; + } - Role GetRole() const { return mRole; } nsresult CreateRemoteSourceStreamInfo(uint32_t aHint, RemoteSourceStreamInfo** aInfo); // Implementation of the only observer we need @@ -120,11 +141,12 @@ public: void NotifyDataChannel(mozilla::DataChannel *aChannel); // Get the media object - const nsRefPtr& media() const { return mMedia; } + const nsRefPtr& media() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mMedia; + } // Handle system to allow weak references to be passed through C code - static PeerConnectionWrapper *AcquireInstance(const std::string& aHandle); - virtual void ReleaseInstance(); virtual const std::string& GetHandle(); // ICE events @@ -136,23 +158,33 @@ public: static void ConnectThread(void *aData); // Get the main thread - nsCOMPtr GetMainThread() { return mThread; } + nsCOMPtr GetMainThread() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mThread; + } // Get the STS thread - nsCOMPtr GetSTSThread() { return mSTSThread; } + nsCOMPtr GetSTSThread() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mSTSThread; + } // Get the DTLS identity - mozilla::RefPtr const GetIdentity() { return mIdentity; } + mozilla::RefPtr const GetIdentity() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mIdentity; + } // Create a fake media stream nsresult CreateFakeMediaStream(uint32_t hint, nsIDOMMediaStream** retval); - nsPIDOMWindow* GetWindow() const { return mWindow; } + nsPIDOMWindow* GetWindow() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mWindow; + } // Validate constraints and construct a MediaConstraints object // from a JS::Value. - nsresult ConvertConstraints( - const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx); NS_IMETHODIMP CreateOffer(MediaConstraints& aConstraints); NS_IMETHODIMP CreateAnswer(MediaConstraints& aConstraints); @@ -160,16 +192,33 @@ private: PeerConnectionImpl(const PeerConnectionImpl&rhs); PeerConnectionImpl& operator=(PeerConnectionImpl); + NS_IMETHODIMP CreateOfferInt(MediaConstraints& constraints); + NS_IMETHODIMP CreateAnswerInt(MediaConstraints& constraints); + + nsresult CloseInt(bool aIsSynchronous); void ChangeReadyState(ReadyState aReadyState); - void CheckIceState() { - PR_ASSERT(mIceState != kIceGathering); + nsresult CheckApiState(bool assert_ice_ready) const; + void CheckThread() const { + NS_ABORT_IF_FALSE(CheckThreadInt(), "Wrong thread"); + } + bool CheckThreadInt() const { +#ifdef MOZILLA_INTERNAL_API + // Thread assertions are disabled in the C++ unit tests because those + // make API calls off the main thread. + // TODO(ekr@rtfm.com): Fix the unit tests so they don't do that. + bool on; + NS_ENSURE_SUCCESS(mThread->IsOnCurrentThread(&on), false); + NS_ENSURE_TRUE(on, false); +#endif + return true; } // Shut down media. Called on any thread. void ShutdownMedia(bool isSynchronous); - nsresult MakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aStream); - nsresult MakeRemoteSource(nsDOMMediaStream* aStream, RemoteSourceStreamInfo** aInfo); + // ICE callbacks run on the right thread. + void IceGatheringCompleted_m(NrIceCtx *aCtx); + void IceCompleted_m(NrIceCtx *aCtx); // The role we are adopting Role mRole; @@ -212,9 +261,6 @@ private: nsRefPtr mMedia; - // Singleton list of all the PeerConnections - static std::map peerconnections; - public: //these are temporary until the DataChannel Listen/Connect API is removed unsigned short listenPort; @@ -225,17 +271,12 @@ public: // This is what is returned when you acquire on a handle class PeerConnectionWrapper { public: - PeerConnectionWrapper(PeerConnectionImpl *impl) : impl_(impl) {} - - ~PeerConnectionWrapper() { - if (impl_) - impl_->ReleaseInstance(); - } + PeerConnectionWrapper(const std::string& handle); PeerConnectionImpl *impl() { return impl_; } private: - PeerConnectionImpl *impl_; + nsRefPtr impl_; }; } // end sipcc namespace diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_align.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_align.h new file mode 100644 index 000000000000..389f69f0a0d0 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_align.h @@ -0,0 +1,82 @@ +/* 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/. */ + +#ifndef __CPR_ANDROID_ALIGN_H__ +#define __CPR_ANDROID_ALIGN_H__ +#include "cpr_types.h" + +/* + * Macros to determine how an address is aligned + */ +#define is_16bit_aligned(addr) (!((uint32_t)(addr) & 0x01)) +#define is_32bit_aligned(addr) (!((uint32_t)(addr) & 0x03)) +#define is_64bit_aligned(addr) (!((uint32_t)(addr) & 0x07)) + +/* + * Macro to get a mask for a specified byte alignment + */ +#define ALIGN_MASK(alignment) (~((alignment) - 1)) + +/* + * Macro to set the minimum alignment + */ +#define ALIGN_MIN(align,align_min) (((align) > (align_min)) ? (align) : (align_min)) + +/* + * Macro to round up or down "val" to be a multiple of "align", assuming + * "align" is a power of 2. if "align" is zero then no action will + * be performed + */ +#ifdef __typeof__ +#define ALIGN_UP(val,align) ((align == 0) ? (val) : (__typeof__(val))(((uint32_t)(val) + ((align) - 1)) & ~((align) - 1))) +#define ALIGN_DOWN(val,align) ((align == 0) ? (val) : (__typeof__(val))(((uint32_t)(val)) & ~((align) - 1))) +#else +#define ALIGN_UP(val,align) ((align == 0) ? (val) : (uint32_t)(((uint32_t)(val) + ((align) - 1)) & ~((align) - 1))) +#define ALIGN_DOWN(val,align) ((align == 0) ? (val) : (uint32_t)(((uint32_t)(val)) & ~((align) - 1))) +#endif + +/** + * Macro to safely write 4 bytes based on memory alignment setting + */ +#ifndef CPR_MEMORY_LITTLE_ENDIAN +#define WRITE_4BYTES_UNALIGNED(mem, value) \ + { ((char *)mem)[0] = (uint8_t)(((value) & 0xff000000) >> 24); \ + ((char *)mem)[1] = (uint8_t)(((value) & 0x00ff0000) >> 16); \ + ((char *)mem)[2] = (uint8_t)(((value) & 0x0000ff00) >> 8); \ + ((char *)mem)[3] = (uint8_t) ((value) & 0x000000ff); \ + } +#else +#define WRITE_4BYTES_UNALIGNED(mem, value) \ + { ((char *)mem)[3] = (uint8_t)(((value) & 0xff000000) >> 24); \ + ((char *)mem)[2] = (uint8_t)(((value) & 0x00ff0000) >> 16); \ + ((char *)mem)[1] = (uint8_t)(((value) & 0x0000ff00) >> 8); \ + ((char *)mem)[0] = (uint8_t) ((value) & 0x000000ff); \ + } +#endif +/** + * Macro to safely read 4 bytes based on memory alignment setting + */ +#ifndef CPR_MEMORY_LITTLE_ENDIAN +#ifdef __typeof__ +#define READ_4BYTES_UNALIGNED(mem, value) \ + __typeof__(value)((((uint8_t *)mem)[0] << 24) | (((uint8_t *)mem)[1] << 16) | \ + (((uint8_t *)mem)[2] << 8) | ((uint8_t *)mem)[3]) +#else +#define READ_4BYTES_UNALIGNED(mem, value) \ + (uint32_t)((((uint8_t *)mem)[0] << 24) | (((uint8_t *)mem)[1] << 16) | \ + (((uint8_t *)mem)[2] << 8) | ((uint8_t *)mem)[3]) +#endif +#else +#ifdef __typeof__ +#define READ_4BYTES_UNALIGNED(mem, value) \ + __typeof__(value)((((uint8_t *)mem)[3] << 24) | (((uint8_t *)mem)[2] << 16) | \ + (((uint8_t *)mem)[1] << 8) | ((uint8_t *)mem)[0]) +#else +#define READ_4BYTES_UNALIGNED(mem, value) \ + (uint32_t)((((uint8_t *)mem)[3] << 24) | (((uint8_t *)mem)[2] << 16) | \ + (((uint8_t *)mem)[1] << 8) | ((uint8_t *)mem)[0]) +#endif +#endif + +#endif /* __CPR_ANDROID_ALIGN_H__ */ diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_assert.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_assert.h new file mode 100644 index 000000000000..7321de001158 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_assert.h @@ -0,0 +1,85 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_ASSERT_H_ +#define _CPR_ANDROID_ASSERT_H_ + +#include "assert.h" + +/*-------------------------------------- + * + * Macros + * + */ + +/** + * CPR assert macro which calls cpr_assert_msg instead of abort + * + * The macro is dependent on the setting of FILE_ID which is used + * to override the __FILE__ setting. For certain compilers, i.e. + * read 'Diab 4.4b', the __FILE__ is set to a Windows type path + * name which can contain backslashes that can cause odd output. + */ +#ifdef FILE_ID +#define cpr_assert(expr) \ + ((expr) ? (void)0 : cpr_assert_msg(FILE_ID, __LINE__, #expr)) +#else +#define cpr_assert(expr) \ + ((expr) ? (void)0 : cpr_assert_msg(__FILE__, __LINE__, #expr)) +#endif + +#define cpr_assert_debug(expr) + +/* + * A side note if somehow concerned about performance. + * + * This method will pre-render the string via the compiler, + * but will use more space due to larger strings. Basically, + * good for speed and bad for memory. + * + * This is coded mostly as an example so if performance was an issue + * that the asserts could be low impact. + * + * #define cpr_assert_debug(expr) \ + * ((expr) ? (void)0 : cpr_assert_msg( \ + * __FILE__ ": line " __LINE__ ": assertion failed: " #expr)) + * + * Note that this is not allowed when using __STRING_ANSI__ + */ + +#define cpr_assert_debug_rtn(expr) + +/*-------------------------------------- + * + * Structures + * + */ + +/** + * CPR assert modes of operation + */ +typedef enum { + CPR_ASSERT_MODE_NONE, /**< Off, no message ouput */ + CPR_ASSERT_MODE_WARNING_LIMITED, /**< Warnings to syslog are limited */ + CPR_ASSERT_MODE_WARNING_ALL, /**< All warnings sent to syslog */ + CPR_ASSERT_MODE_ABORT /**< Assert failure will call abort */ +} cpr_assert_mode_e; + + +/*-------------------------------------- + * + * Globals + * + */ +extern uint32_t cpr_assert_count; + +/*-------------------------------------- + * + * Prototypes + * + */ +void +cpr_assert_msg(const char *file, const int line, const char *expression); + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_errno.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_errno.c new file mode 100644 index 000000000000..3a593de2bdd8 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_errno.c @@ -0,0 +1,184 @@ +/* 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 "cpr_errno.h" +#include + +/** + * @addtogroup OSAPIs The CPR OS Abstractions + * @brief Misc OS API Abstractions in CPR + * + * @{ + */ +static int8_t errno_table[] = +{ + CPR_EPERM, + CPR_ENOENT, + CPR_ESRCH, + CPR_EINTR, + CPR_EIO, + CPR_ENXIO, + CPR_E2BIG, + CPR_ENOEXEC, + CPR_EBADF, + CPR_ECHILD, + CPR_EAGAIN, /*10*/ + CPR_ENOMEM, + CPR_EACCES, + CPR_EFAULT, + CPR_ENOTBLK, + CPR_EBUSY, + CPR_EEXIST, + CPR_EXDEV, + CPR_ENODEV, + CPR_ENOTDIR, + CPR_EISDIR,/*20*/ + CPR_EINVAL, + CPR_ENFILE, + CPR_EMFILE, + CPR_ENOTTY, + CPR_ETXTBSY, + CPR_EFBIG, + CPR_ENOSPC, + CPR_ESPIPE, + CPR_EROFS, + CPR_EMLINK,/*30*/ + CPR_EPIPE, + CPR_EDOM, + CPR_ERANGE, + CPR_ENOMSG, + CPR_EIDRM, + CPR_ECHRNG, + CPR_EL2NSYNC, + CPR_EL3HLT, + CPR_EL3RST, + CPR_ELNRNG,/*40*/ + CPR_EUNATCH, + CPR_ENOCSI, + CPR_EL2HLT, + CPR_EDEADLK, + CPR_ENOLCK, + CPR_ECANCELED, + CPR_ENOTSUP, + CPR_EDQUOT, + CPR_EBADE, + CPR_EBADR, + CPR_EXFULL, + CPR_ENOANO, + CPR_EBADRQC, + CPR_EBADSLT, + CPR_EDEADLOCK, + CPR_EBFONT, + CPR_UNKNOWN_ERR, /* empty 58 */ + CPR_UNKNOWN_ERR, /* empty 59 */ + CPR_ENOSTR, + CPR_ENODATA, + CPR_ETIME, + CPR_ENOSR, + CPR_ENONET, + CPR_ENOPKG, + CPR_EREMOTE, + CPR_ENOLINK, + CPR_EADV, + CPR_ESRMNT, + CPR_ECOMM, + CPR_EPROTO, + CPR_UNKNOWN_ERR, /* empty 72 */ + CPR_UNKNOWN_ERR, /* empty 73 */ + CPR_EMULTIHOP, + CPR_UNKNOWN_ERR, /* empty 75 */ + CPR_UNKNOWN_ERR, /* empty 76 */ + CPR_EBADMSG, + CPR_ENAMETOOLONG, + CPR_EOVERFLOW, + CPR_ENOTUNIQ, + CPR_EBADFD, + CPR_EREMCHG, + CPR_ELIBACC, + CPR_ELIBBAD, + CPR_ELIBSCN, + CPR_ELIBMAX, + CPR_ELIBEXEC, + CPR_EILSEQ, + CPR_ENOSYS, + CPR_ELOOP, + CPR_ERESTART, + CPR_ESTRPIPE, + CPR_ENOTEMPTY, + CPR_EUSERS, + CPR_ENOTSOCK, + CPR_EDESTADDRREQ, + CPR_EMSGSIZE, + CPR_EPROTOTYPE, + CPR_ENOPROTOOPT, + /* errno index goes from 99 to 120 */ + CPR_EPROTONOSUPPORT, + CPR_ESOCKTNOSUPPORT, + CPR_EOPNOTSUPP, + CPR_EPFNOSUPPORT, + CPR_EAFNOSUPPORT, + CPR_EADDRINUSE, + CPR_EADDRNOTAVAIL, + CPR_ENETDOWN, + CPR_ENETUNREACH, + CPR_ENETRESET, + CPR_ECONNABORTED, + CPR_ECONNRESET, + CPR_ENOBUFS, + CPR_EISCONN, + CPR_ENOTCONN, + CPR_ECLOSED, + CPR_UNKNOWN_ERR, /* empty 136 */ + CPR_UNKNOWN_ERR, /* empty 137 */ + CPR_UNKNOWN_ERR, /* empty 138 */ + CPR_UNKNOWN_ERR, /* empty 139 */ + CPR_UNKNOWN_ERR, /* empty 140 */ + CPR_UNKNOWN_ERR, /* empty 141 */ + CPR_UNKNOWN_ERR, /* empty 142 */ + CPR_ESHUTDOWN, + CPR_ETOOMANYREFS, + CPR_ETIMEDOUT, + CPR_ECONNREFUSED, + CPR_EHOSTDOWN, + CPR_EHOSTUNREACH, + CPR_EALREADY, + CPR_EINPROGRESS, + CPR_ESTALE +}; + +/** + * + * @brief Translates to "cpr_errno" Macro + * + * pSIPCC uses the cpr_errno macro to print the errno + * for error conditions. This function is used to map the standard + * errno to standard CPR errors + * + * @return The CPR error number + * + */ +int16_t +cprTranslateErrno (void) +{ + int16_t e = (int16_t) errno; + + /* + * Verify against MIN and MAX errno numbers + */ + if ((e < 1) || (e > 151)) { + return CPR_UNKNOWN_ERR; + } else if (e >= 120) { + e = e - 20; + } else if (e >= 100) { + /* + * In the gap from 100 to 119 + */ + return CPR_UNKNOWN_ERR; + } + return (int16_t) errno_table[e - 1]; +} + +/** + * @} + */ diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_errno.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_errno.h new file mode 100644 index 000000000000..095b799ba5bd --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_errno.h @@ -0,0 +1,19 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_ERRNO_H_ +#define _CPR_ANDROID_ERRNO_H_ + +#include + +/* + * Maintain re-entrant nature by wrapping 'errno' + */ +/** @def cpr_errno is used by pSIPCC. MUST be defined by the CPR layer. + */ +#define cpr_errno cprTranslateErrno() + +int16_t cprTranslateErrno(void); + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_in.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_in.h new file mode 100644 index 000000000000..b9a1d90ef52e --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_in.h @@ -0,0 +1,11 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_IN_H_ +#define _CPR_ANDROID_IN_H_ + +#include +#include + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_init.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_init.c new file mode 100644 index 000000000000..8cdb017cffb4 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_init.c @@ -0,0 +1,216 @@ +/* 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/. */ + +/** + * @section intro_sec Introduction + * CPR is an OS-abstraction layer which provides the functionality set of an OS + * while hiding all the operational details from the applications. CPR also + * provides additional functionality when certain operating-systems prove + * deficient providing a consistent set of functionality for the applications + * to use. The pSipCC library uses the CPR routines to achieve portablity. It + * is the responsibility of any vendor requiring to port pSipCC into their + * platforms to have the correct implementation of the CPR layer. + * + * The CPR delivery includes two "archives/binary" for memory and string + * related operations. These binaries contain functions that the pSIPCC uses + * for it's functionality. The header files that contain the external functions + * (APIs) from these binaries are also distributed. + * + * @section sub_sec Functionality + * CPR consists of a number of functional subsystems to achieve OS abstraction. The main + * functionality provided by CPR to the pSIPCC library are + * @li Memory Management - Provided as a binary that needs to be linked into CPR + * @li Timers + * @li Inter Process Communication - Sockets, Message Queues, Mutex, Semaphores + * @li String Handling - Provided as a binary that needs to be linked into CPR + * @li Thread Abstraction + * @li Other Platform/OS Abstractions + * @li Debug/Logging Abstraction + * + * @section file_list EXTERNAL APIS + * The External APIs that need to be exposed by CPR to the pSIPCC application are + * defined in the following header files. The documentation within + * each header file lists the functions/macros that @b NEED to be defined for + * pSIPCC to work correctly. Example functions (and an implementation for + * Linux) is available for most functions defined in these headers. Look under + * the "Modules" tab to find the various subsystems and associated APIs and + * helper functions. + * @li cpr_debug.h + * @li cpr_errno.h + * @li cpr_in.h + * @li cpr_locks.h + * @li cpr_rand.h + * @li cpr_socket.h + * @li cpr_stdio.h + * @li cpr_threads.h + * @li cpr_timers.h + * @li cpr.h + * @li cpr_ipc.h + * @li cpr_stddef.h + * @li cpr_time.h + * @li cpr_types.h + * + * The function prototypes in these header files are implemented in the + * binaries related to memory and string functionality. The prototypes are + * given so that vendors can use these functions for implementation of other + * CPR parts. + * @li cpr_memory.h + * @li cpr_stdlib.h + * @li cpr_string.h + * @li cpr_strings.h + * + * @section standard_headers Standard Header Files + * The pSIPCC component expects some standard header files and associated + * functionality to be present in the native operating system of the vendors + * porting it. These header files contain some basic functionality that pSIPCC + * uses for proper operation. A list of the standard headers (from a standard + * Linux distribution) are - + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * @li + * + * + * @section Comp Compiling CPR + * A sample makefile is also provided with the Linux distribution. Running + * "make" generates a CPR executable called "cprtest". This sample makefile can + * be modified to generate a shared library if required. Modify the CC, STDINC, + * STDLIB paths to point to the correct compiler and header files in the + * environment where this is being built. + * @note The README file shows the exact commands required to build/test the CPR + * functionality. + * + * @file cpr_linux_init.c + * @brief This file contains CPR initialization routines + * + * DESCRIPTION + * Initialization routine for the Cisco Portable Runtime layer + * running in the Linux operating System. + */ + +/** + * @addtogroup Initialization The initialization module + * @ingroup CPR + * @brief The intialization module consists of APIs used to initialize or destroy the CPR layer by pSipCC + * + * @{ + */ +#include "cpr.h" +#include "cpr_stdlib.h" +#include "cpr_stdio.h" +#include "cpr_timers.h" +#include "cpr_android_locks.h" +#include "cpr_android_timers.h" +#include "plat_api.h" +#include +#ifndef ANDROID +#include +#include +#endif +#include "plat_debug.h" + +/** + * Mutex to manage message queue list. + */ +extern pthread_mutex_t msgQueueListMutex; + +/** + * Boolean to check that cprPreInit been called + */ +static boolean pre_init_called = FALSE; + +/** + * cprInfo prints out informational messages that are + * not necessarily errors, but maybe of interest to + * someone debugging a problem. Examples of this are + * requesting a msg from a msg queue that is empty, + * stopping a timer that is not running, etc... + */ +int32_t cprInfo = TRUE; + + +/** + * cprPreInit + * + * @brief The cprPreInit function IS called from pSIPCC @b before any components are initialized. + * + * This function @b SHOULD initialize those portions of the CPR that + * are needed before applications can start using it. The memory subsystem + * (sandbox) is initialized from this routine. + * + * + * @return CPR_SUCCESS or CPR_FAILURE + * @note pSIPCC will NOT continue and stop initialization if the return value is CPR_FAILURE. + */ +cprRC_t +cprPreInit (void) +{ + static const char fname[] = "cprPreInit"; + int32_t returnCode; + + /* + * Make function reentreant + */ + if (pre_init_called == TRUE) { + return CPR_SUCCESS; + } + pre_init_called = TRUE; + /* + * Create message queue list mutex + */ + returnCode = pthread_mutex_init(&msgQueueListMutex, NULL); + if (returnCode != 0) { + CPR_ERROR("%s: MsgQueue Mutex init failure %d\n", fname, returnCode); + return CPR_FAILURE; + } + + returnCode = cpr_timer_pre_init(); + if (returnCode != 0) { + CPR_ERROR("%s: timer pre init failed %d\n", fname, returnCode); + return CPR_FAILURE; + } + + + return CPR_SUCCESS; +} + + +/** + * cprPostInit + * + * @brief The cprPostInit function IS called from pSIPCC @b after all the components are initialized. + * + * This function @b SHOULD complete any CPR activities before the phone is + * operational. In other words a call to this function will be the last line of + * the phone initializtion routine from pSIPCC. This function implementation + * ties in a couple of debug commands to get more information on the CPR from + * the shell. + * + * + * @return CPR_SUCCESS or CPR_FAILURE + * @note pSIPCC will NOT continue and stop initialization if the return value is CPR_FAILURE. + */ +cprRC_t +cprPostInit (void) +{ +/* if (cpr_memory_mgmt_post_init() not_eq TRUE) { + return CPR_FAILURE; + } +*/ + return CPR_SUCCESS; +} + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_ipc.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_ipc.c new file mode 100644 index 000000000000..2302df1651de --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_ipc.c @@ -0,0 +1,681 @@ +/* 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 "cpr.h" +#include "cpr_stdlib.h" +#include +#include +#include +#include +#include +#include "cpr_string.h" + +/* + * If building with debug test interface, + * allow access to internal CPR functions + */ +#define STATIC static + +#define OS_MSGTQL 31 /* need to check number for MV linux and put here */ + +/* + * Internal CPR API + */ +extern pthread_t cprGetThreadId(cprThread_t thread); + +/* + * Extended internal message queue node + * + * A double-linked list holding the nessasary message information + */ +typedef struct cpr_msgq_node_s +{ + struct cpr_msgq_node_s *next; + struct cpr_msgq_node_s *prev; + void *msg; + void *pUserData; +} cpr_msgq_node_t; + +/* + * Msg queue information needed to hide OS differences in implementation. + * To use msg queues, the application code may pass in a name to the + * create function for msg queues. CPR does not use this field, it is + * solely for the convenience of the application and to aid in debugging. + * + * Note: Statistics are not protected by a mutex; therefore, there exists + * the possibility that the results may not be accurate. + * + * Note:if the depth supplied by OS is insufficient,a message queue owner may + * increase the message queue depth via cprCreateMessageQueue's depth + * parameter where the value can range from MSGTQL to CPR_MAX_MSG_Q_DEPTH. + */ +typedef struct cpr_msg_queue_s +{ + struct cpr_msg_queue_s *next; + const char *name; + pthread_t thread; + int32_t queueId; + uint16_t maxCount; + uint16_t currentCount; + uint32_t totalCount; + uint32_t sendErrors; + uint32_t reTries; + uint32_t highAttempts; + uint32_t selfQErrors; + uint16_t extendedQDepth; + uint16_t maxExtendedQDepth; + pthread_mutex_t mutex; /* lock for managing extended queue */ + pthread_cond_t cond; /* signal for queue/dequeue */ + cpr_msgq_node_t *head; /* extended queue head (newest element) */ + cpr_msgq_node_t *tail; /* extended queue tail (oldest element) */ +} cpr_msg_queue_t; + +/* + * A enumeration used to report the result of posting a message to + * a message queue + */ +typedef enum +{ + CPR_MSGQ_POST_SUCCESS, + CPR_MSGQ_POST_FAILED, + CPR_MSGQ_POST_PENDING +} cpr_msgq_post_result_e; + + +/* + * Head of list of message queues + */ +static cpr_msg_queue_t *msgQueueList = NULL; + +/* + * Mutex to manage message queue list + */ +pthread_mutex_t msgQueueListMutex; + +/* + * String to represent message queue name when it is not provided + */ +static const char unnamed_string[] = "unnamed"; + + +/* + * CPR_MAX_MSG_Q_DEPTH + * + * The maximum queue depth supported by the CPR layer. This value + * is arbitrary though the purpose is to limit the memory usage + * by CPR and avoid (nearly) unbounded situations. + * + * Note: This value should be greater than MSGTQL which is currently + * defined as 31 + */ +#define CPR_MAX_MSG_Q_DEPTH 256 + +/* + * CPR_SND_TIMEOUT_WAIT_INTERVAL + * + * The interval of time to wait in milliseconds between attempts to + * send a message to the message queue + * + * Note: 20 ms. to avoid less than a tick wake up since on most + * OSes 10ms is one 1 tick + * this should really be OS_TICK_MS * 2 or OS_TICK_MS + X + */ +#define CPR_SND_TIMEOUT_WAIT_INTERVAL 20 + +/* + * CPR_ATTEMPTS_TO_SEND + * + * The number of attempts made to send a message when the message + * would otherwise be blocked. Note in this condition the thread + * will sleep the timeout interval to allow the msg queue to be + * drained. + * + * Note: 25 attempts for upto .5 seconds at the interval of + * CPR_SND_TIMEOUT_WAIT_INTERVAL worst case. + */ +#define CPR_ATTEMPTS_TO_SEND 25 + +/* + * Also, important to note that the total timeout interval must be + * greater than the SIP's select call timeout value which is 25msec. + * This is necessary to cover the case where the SIP message queue + * is full and the select timeout occurs. + * + * Total timeout interval = CPR_SND_TIMEOUT_WAIT_INTERVAL * + * CPR_ATTEMPTS_TO_SEND; + */ + + +/* + * Prototype declarations + */ +static cpr_msgq_post_result_e +cprPostMessage(cpr_msg_queue_t *msgq, void *msg, void **ppUserData); +static void +cprPegSendMessageStats(cpr_msg_queue_t *msgq, uint16_t numAttempts); + + +/* + * Functions + */ + +/** + * Creates a message queue + * + * @param name - name of the message queue + * @param depth - the message queue depth, optional field which will + * default if set to zero(0) + * + * @return Msg queue handle or NULL if init failed, errno provided + * + * @note the actual message queue depth will be bounded by the + * standard system message queue depth and CPR_MAX_MSG_Q_DEPTH. + * If 'depth' is outside of the bounds, the value will be + * reset automatically. + */ +cprMsgQueue_t +cprCreateMessageQueue (const char *name, uint16_t depth) +{ + static const char fname[] = "cprCreateMessageQueue"; + cpr_msg_queue_t *msgq; + static int key_id = 100; /* arbitrary starting number */ + + msgq = cpr_calloc(1, sizeof(cpr_msg_queue_t)); + if (msgq == NULL) { + printf("%s: Malloc failed: %s\n", fname, + name ? name : unnamed_string); + errno = ENOMEM; + return NULL; + } + + msgq->name = name ? name : unnamed_string; + msgq->queueId = key_id++; + + pthread_cond_t _cond = PTHREAD_COND_INITIALIZER; + msgq->cond = _cond; + pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER; + msgq->mutex = _lock; + + /* + * Add message queue to list for statistics reporting + */ + pthread_mutex_lock(&msgQueueListMutex); + msgq->next = msgQueueList; + msgQueueList = msgq; + pthread_mutex_unlock(&msgQueueListMutex); + + return msgq; +} + + +/** + * Removes all messages from the queue and then destroy the message queue + * + * @param msgQueue - message queue to destroy + * + * @return CPR_SUCCESS or CPR_FAILURE, errno provided + */ +cprRC_t +cprDestroyMessageQueue (cprMsgQueue_t msgQueue) +{ + static const char fname[] = "cprDestroyMessageQueue"; + cpr_msg_queue_t *msgq; + void *msg; + + msgq = (cpr_msg_queue_t *) msgQueue; + if (msgq == NULL) { + /* Bad application! */ + CPR_ERROR("%s: Invalid input\n", fname); + errno = EINVAL; + return CPR_FAILURE; + } + + /* Drain message queue */ + msg = cprGetMessage(msgQueue, FALSE, NULL); + while (msg != NULL) { + cpr_free(msg); + msg = cprGetMessage(msgQueue, FALSE, NULL); + } + + /* Remove message queue from list */ + pthread_mutex_lock(&msgQueueListMutex); + if (msgq == msgQueueList) { + msgQueueList = msgq->next; + } else { + cpr_msg_queue_t *msgql = msgQueueList; + + while ((msgql->next != NULL) && (msgql->next != msgq)) { + msgql = msgql->next; + } + if (msgql->next == msgq) { + msgql->next = msgq->next; + } + } + pthread_mutex_unlock(&msgQueueListMutex); + + /* Remove message queue mutex */ + if (pthread_mutex_destroy(&msgq->mutex) != 0) { + CPR_ERROR("%s: Failed to destroy msg queue (%s) mutex: %d\n", + fname, msgq->name, errno); + } + + cpr_free(msgq); + return CPR_SUCCESS; +} + + +/** + * Associate a thread with the message queue + * + * @param msgQueue - msg queue to set + * @param thread - CPR thread to associate with queue + * + * @return CPR_SUCCESS or CPR_FAILURE + * + * @note Nothing is done to prevent overwriting the thread ID + * when the value has already been set. + */ +cprRC_t +cprSetMessageQueueThread (cprMsgQueue_t msgQueue, cprThread_t thread) +{ + static const char fname[] = "cprSetMessageQueueThread"; + cpr_msg_queue_t *msgq; + + if ((!msgQueue) || (!thread)) { + CPR_ERROR("%s: Invalid input\n", fname); + return CPR_FAILURE; + } + + msgq = (cpr_msg_queue_t *) msgQueue; + if (msgq->thread != 0) { + CPR_ERROR("%s: over-writing previously msgq thread name for %s", + fname, msgq->name); + } + + msgq->thread = cprGetThreadId(thread); + return CPR_SUCCESS; +} + + +/** + * Retrieve a message from a particular message queue + * + * @param[in] msgQueue - msg queue from which to retrieve the message + * @param[in] waitForever - boolean to either wait forever (TRUE) or not + * wait at all (FALSE) if the msg queue is empty. + * @param[out] ppUserData - pointer to a pointer to user defined data + * + * @return Retrieved message buffer or NULL if failure occurred or + * the waitForever flag was set to false and no messages were + * on the queue. + * + * @note If ppUserData is defined, the value will be initialized to NULL + */ +void * +cprGetMessage (cprMsgQueue_t msgQueue, boolean waitForever, void **ppUserData) +{ + static const char fname[] = "cprGetMessage"; + + void *buffer = 0; + cpr_msg_queue_t *msgq; + cpr_msgq_node_t *node; + struct timespec timeout; + struct timeval tv; + struct timezone *tz; + + /* Initialize ppUserData */ + if (ppUserData) { + *ppUserData = NULL; + } + + msgq = (cpr_msg_queue_t *) msgQueue; + if (msgq == NULL) { + /* Bad application! */ + CPR_ERROR("%s: Invalid input\n", fname); + errno = EINVAL; + return NULL; + } + + /* + * If waitForever is set, block on the message queue + * until a message is received, else return after + * 25msec of waiting + */ + pthread_mutex_lock(&msgq->mutex); + + if (!waitForever) + { + // We'll wait till 25uSec from now + gettimeofday(&tv, &tz); + timeout.tv_nsec = (tv.tv_usec * 1000) + 25000; + timeout.tv_sec = tv.tv_sec; + + pthread_cond_timedwait(&msgq->cond, &msgq->mutex, &timeout); + + } + else + { + while(msgq->tail==NULL) + { + pthread_cond_wait(&msgq->cond, &msgq->mutex); + } + } + + // If there is a message on the queue, de-queue it + if (msgq->tail) + { + node = msgq->tail; + msgq->tail = node->prev; + if (msgq->tail) { + msgq->tail->next = NULL; + } + if (msgq->head == node) { + msgq->head = NULL; + } + msgq->currentCount--; + /* + * Pull out the data + */ + if (ppUserData) { + *ppUserData = node->pUserData; + } + buffer = node->msg; + + } + + pthread_mutex_unlock(&msgq->mutex); + + return buffer; +} + + +/** + * Place a message on a particular queue. Note that caller may + * block (see comments below) + * + * @param msgQueue - msg queue on which to place the message + * @param msg - pointer to the msg to place on the queue + * @param ppUserData - pointer to a pointer to user defined data + * + * @return CPR_SUCCESS or CPR_FAILURE, errno provided + * + * @note 1. Messages queues are set to be non-blocking, those cases + * where the system call fails with a would-block error code + * (EAGAIN) the function will attempt other mechanisms described + * below. + * @note 2. If enabled with an extended message queue, either via a + * call to cprCreateMessageQueue with depth value or a call to + * cprSetExtendMessageQueueDepth() (when unit testing), the message + * will be added to the extended message queue and the call will + * return successfully. When room becomes available on the + * system's message queue, those messages will be added. + * @note 3. If the message queue becomes full and no space is availabe + * on the extended message queue, then the function will attempt + * to resend the message up to CPR_ATTEMPTS_TO_SEND and the + * calling thread will *BLOCK* CPR_SND_TIMEOUT_WAIT_INTERVAL + * milliseconds after each failed attempt. If unsuccessful + * after all attempts then EGAIN error code is returned. + * @note 4. This applies to all CPR threads, including the timer thread. + * So it is possible that the timer thread would be forced to + * sleep which would have the effect of delaying all active + * timers. The work to fix this rare situation is not considered + * worth the effort to fix....so just leaving as is. + */ +cprRC_t +cprSendMessage (cprMsgQueue_t msgQueue, void *msg, void **ppUserData) +{ + static const char fname[] = "cprSendMessage"; + static const char error_str[] = "%s: Msg not sent to %s queue: %s\n"; + cpr_msgq_post_result_e rc; + cpr_msg_queue_t *msgq; + int16_t attemptsToSend = CPR_ATTEMPTS_TO_SEND; + uint16_t numAttempts = 0; + + /* Bad application? */ + if (msgQueue == NULL) { + CPR_ERROR(error_str, fname, "undefined", "invalid input"); + errno = EINVAL; + return CPR_FAILURE; + } + + msgq = (cpr_msg_queue_t *) msgQueue; + + /* + * Attempt to send message + */ + do { + + /* + * Post the message to the Queue + */ + rc = cprPostMessage(msgq, msg, ppUserData); + + if (rc == CPR_MSGQ_POST_SUCCESS) { + cprPegSendMessageStats(msgq, numAttempts); + return CPR_SUCCESS; + } else if (rc == CPR_MSGQ_POST_FAILED) { + CPR_ERROR("%s: Msg not sent to %s queue: %d\n", + fname, msgq->name, errno); + msgq->sendErrors++; + /* + * If posting to calling thread's own queue, + * then peg the self queue error. + */ + if (pthread_self() == msgq->thread) { + msgq->selfQErrors++; + } + + return CPR_FAILURE; + } + + + /* + * Did not succeed in sending the message, so continue + * to attempt up to the CPR_ATTEMPTS_TO_SEND. + */ + attemptsToSend--; + if (attemptsToSend > 0) { + /* + * Force a context-switch of the thread attempting to + * send the message, in order to help the case where + * the msg queue is full and the owning thread may get + * a a chance be scheduled so it can drain it (Note: + * no guarantees, more of a "last-ditch effort" to + * recover...especially when temporarily over-whelmed). + */ + cprSleep(CPR_SND_TIMEOUT_WAIT_INTERVAL); + msgq->reTries++; + numAttempts++; + } + } while (attemptsToSend > 0); + + CPR_ERROR(error_str, fname, msgq->name, "FULL"); + msgq->sendErrors++; + return CPR_FAILURE; +} + +/** + * Peg the statistics for successfully posting a message + * + * @param msgq - message queue + * @param numAttempts - number of attempts to post message to message queue + * + * @return none + * + * @pre (msgq not_eq NULL) + */ +static void +cprPegSendMessageStats (cpr_msg_queue_t *msgq, uint16_t numAttempts) +{ + /* + * Collect statistics + */ + msgq->totalCount++; + if (msgq->currentCount > msgq->maxCount) { + msgq->maxCount = msgq->currentCount; + } + + if (numAttempts > msgq->highAttempts) { + msgq->highAttempts = numAttempts; + } +} + +/** + * Post message to system message queue + * + * @param msgq - message queue + * @param msg - message to post + * @param ppUserData - ptr to ptr to option user data + * + * @return the post result which is CPR_MSGQ_POST_SUCCESS, + * CPR_MSGQ_POST_FAILURE or CPR_MSGQ_POST_PENDING + * + * @pre (msgq not_eq NULL) + * @pre (msg not_eq NULL) + */ +static cpr_msgq_post_result_e +cprPostMessage (cpr_msg_queue_t *msgq, void *msg, void **ppUserData) +{ + cpr_msgq_node_t *node; + + /* + * Allocate new message queue node + */ + node = cpr_malloc(sizeof(*node)); + if (!node) { + errno = ENOMEM; + return CPR_MSGQ_POST_FAILED; + } + + pthread_mutex_lock(&msgq->mutex); + + /* + * Fill in data + */ + node->msg = msg; + if (ppUserData != NULL) { + node->pUserData = *ppUserData; + } else { + node->pUserData = NULL; + } + + /* + * Push onto list + */ + node->prev = NULL; + node->next = msgq->head; + msgq->head = node; + + if (node->next) { + node->next->prev = node; + } + + if (msgq->tail == NULL) { + msgq->tail = node; + } + msgq->currentCount++; + + pthread_cond_signal(&msgq->cond); + pthread_mutex_unlock(&msgq->mutex); + + return CPR_MSGQ_POST_SUCCESS; + +} + + +/** + * cprGetMessageQueueStats + * + * Get statistics for a given message queue + * + * @param msgQueue - message queue on which to gather stats + * @param stats - pointer to struct to place statistics + * + * @return none + */ +STATIC void +cprGetMessageQueueStats (cprMsgQueue_t msgQueue, cprMsgQueueStats_t *stats) +{ + cpr_msg_queue_t *msgq; + + if (msgQueue && stats) { + msgq = (cpr_msg_queue_t *) msgQueue; + + sstrncpy(stats->name, msgq->name ? msgq->name : "undefined", + sizeof(stats->name)); + + stats->extendedDepth = msgq->maxExtendedQDepth; + stats->maxCount = msgq->maxCount; + stats->currentCount = msgq->currentCount; + stats->totalCount = msgq->totalCount; + stats->reTries = msgq->reTries; + stats->sendErrors = msgq->sendErrors; + stats->highAttempts = msgq->highAttempts; + stats->selfQErrors = msgq->selfQErrors; + } +} + + +/** + * Report statistics for all message queues + * + * @param argc - not used + * @param argv - not used + * + * @return zero(0) + * + * @note Prototype is 'canned' so return of zero is necessary + */ +int32_t +cprShowMessageQueueStats (int32_t argc, const char *argv[]) +{ + cpr_msg_queue_t *msgq; + cprMsgQueueStats_t stats; + + debugif_printf("CPR Message Queues\n"); + + pthread_mutex_lock(&msgQueueListMutex); + msgq = msgQueueList; + while (msgq != NULL) { + memset(&stats, 0, sizeof(stats)); + cprGetMessageQueueStats(msgq, &stats); + + debugif_printf("Name: %s\n", stats.name); + debugif_printf(" extended depth: %d\n", stats.extendedDepth); + debugif_printf(" max: %d\n", stats.maxCount); + debugif_printf(" active: %d\n", stats.currentCount); + debugif_printf(" total: %d\n", stats.totalCount); + debugif_printf(" retries: %d\n", stats.reTries); + debugif_printf(" high attempts: %d\n", stats.highAttempts); + debugif_printf(" send errors: %d\n", stats.sendErrors); + debugif_printf(" self queue errors: %d\n\n", stats.selfQErrors); + + msgq = msgq->next; + } + pthread_mutex_unlock(&msgQueueListMutex); + + return 0; +} + +/** + * cprGetDepth + * + * @brief get depth of a message queue + * + * The pSIPCC uses this API to look at the depth of a message queue for internal + * routing and throttling decision + * + * @param[in] msgQueue - message queue + * + * @return depth of msgQueue + * + * @pre (msgQueue not_eq NULL) + */ +uint16_t cprGetDepth (cprMsgQueue_t msgQueue) +{ + cpr_msg_queue_t *msgq; + msgq = (cpr_msg_queue_t *) msgQueue; + return msgq->currentCount; +} + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_ipc.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_ipc.h new file mode 100644 index 000000000000..a735f075d1eb --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_ipc.h @@ -0,0 +1,56 @@ +/* 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/. */ + +#ifndef _CPR_CNU_IPC_H_ +#define _CPR_CNU_IPC_H_ + +#include "cpr_threads.h" +#include + +/* Enable support for cprSetMessageQueueThread API */ +#define CPR_USE_SET_MESSAGE_QUEUE_THREAD + +/* Maximum message size allowed by CNU */ +#define CPR_MAX_MSG_SIZE 8192 + +/* Our CNU msgtype */ +#define CPR_IPC_MSG 1 + + +/* Message buffer layout */ +struct msgbuffer { + long mtype; /* Message type */ + void *msgPtr; /* Ptr to msg */ + void *usrPtr; /* Ptr to user data */ +}; + +/* For gathering statistics regarding message queues */ +typedef struct { + char name[16]; + uint16_t maxCount; + uint16_t currentCount; + uint32_t totalCount; + uint32_t rcvTimeouts; + uint32_t sendErrors; + uint32_t reTries; + uint32_t highAttempts; + uint32_t selfQErrors; + uint16_t extendedDepth; +} cprMsgQueueStats_t; + + +/* + * Mutex for updating the message queue list + */ +extern pthread_mutex_t msgQueueListMutex; + + +/** + * cprGetDepth + * + * Get depth of a message queue + */ +uint16_t cprGetDepth(cprMsgQueue_t msgQueue); + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_locks.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_locks.c new file mode 100644 index 000000000000..b945d1cd2210 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_locks.c @@ -0,0 +1,209 @@ +/* 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 "cpr.h" +#include "cpr_stdlib.h" +#include "cpr_stdio.h" +#include +#include +#include + +/** + * @defgroup MutexIPCAPIs The Mutex/Semaphore IPC APIs + * @ingroup IPC + * @brief The module related to Mutex/Sempahore abstraction for the pSIPCC + * @{ + */ + + +/** + * cprCreateMutex + * + * @brief Creates a mutual exclusion block + * + * The cprCreateMutex function is called to allow the OS to perform whatever + * work is needed to create a mutex. + * + * @param[in] name - name of the mutex. If present, CPR assigns this name to + * the mutex to assist in debugging. + * + * @return Mutex handle or NULL if creation failed. If NULL, set errno + */ +cprMutex_t +cprCreateMutex (const char *name) +{ + static const char fname[] = "cprCreateMutex"; + static uint16_t id = 0; + int32_t returnCode; + cpr_mutex_t *cprMutexPtr; + pthread_mutex_t *pthreadMutexPtr; + + /* + * Malloc memory for a new mutex. CPR has its' own + * set of mutexes so malloc one for the generic + * CPR view and one for the CNU specific version. + */ + cprMutexPtr = (cpr_mutex_t *) cpr_malloc(sizeof(cpr_mutex_t)); + pthreadMutexPtr = (pthread_mutex_t *) cpr_malloc(sizeof(pthread_mutex_t)); + if ((cprMutexPtr != NULL) && (pthreadMutexPtr != NULL)) { + /* Assign name */ + cprMutexPtr->name = name; + + /* + * Use default mutex attributes. TBD: if we do not + * need cnuMutexAttributes global get rid of it + */ + returnCode = pthread_mutex_init(pthreadMutexPtr, NULL); + if (returnCode != 0) { + CPR_ERROR("%s - Failure trying to init Mutex %s: %d\n", + fname, name, returnCode); + cpr_free(pthreadMutexPtr); + cpr_free(cprMutexPtr); + return (cprMutex_t)NULL; + } + + /* + * TODO - It would be nice for CPR to keep a linked + * list of active mutexes for debugging purposes + * such as a show command or walking the list to ensure + * that an application does not attempt to create + * the same mutex twice. + */ + cprMutexPtr->u.handlePtr = pthreadMutexPtr; + cprMutexPtr->lockId = ++id; + return (cprMutex_t)cprMutexPtr; + } + + /* + * Since the code malloced two pointers ensure both + * are freed since one malloc call could have worked + * and the other failed. + */ + if (pthreadMutexPtr != NULL) { + cpr_free(pthreadMutexPtr); + } else if (cprMutexPtr != NULL) { + cpr_free(cprMutexPtr); + } + + /* Malloc failed */ + CPR_ERROR("%s - Malloc for mutex %s failed.\n", fname, name); + errno = ENOMEM; + return (cprMutex_t)NULL; +} + + +/** + * cprDestroyMutex + * + * @brief Destroys the mutex passed in. + * + * The cprDestroyMutex function is called to destroy a mutex. It is the + * application's responsibility to ensure that the mutex is unlocked when + * destroyed. Unpredictiable behavior will occur if an application + * destroys a locked mutex. + * + * @param[in] mutex - mutex to destroy + * + * @return CPR_SUCCESS or CPR_FAILURE. errno should be set for CPR_FAILURE. + */ +cprRC_t +cprDestroyMutex (cprMutex_t mutex) +{ + static const char fname[] = "cprDestroyMutex"; + cpr_mutex_t *cprMutexPtr; + int32_t rc; + + cprMutexPtr = (cpr_mutex_t *) mutex; + if (cprMutexPtr != NULL) { + rc = pthread_mutex_destroy(cprMutexPtr->u.handlePtr); + if (rc != 0) { + CPR_ERROR("%s - Failure destroying Mutex %s: %d\n", + fname, cprMutexPtr->name, rc); + return CPR_FAILURE; + } + cprMutexPtr->lockId = 0; + cpr_free(cprMutexPtr->u.handlePtr); + cpr_free(cprMutexPtr); + return CPR_SUCCESS; + } + + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; +} + + +/** + * cprGetMutex + * + * @brief Acquire ownership of a mutex + * + * This function locks the mutex referenced by the mutex parameter. If the mutex + * is locked by another thread, the calling thread will block until the mutex is + * released. + * + * @param[in] mutex - Which mutex to acquire + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t +cprGetMutex (cprMutex_t mutex) +{ + static const char fname[] = "cprGetMutex"; + cpr_mutex_t *cprMutexPtr; + int32_t rc; + + cprMutexPtr = (cpr_mutex_t *) mutex; + if (cprMutexPtr != NULL) { + rc = pthread_mutex_lock((pthread_mutex_t *) cprMutexPtr->u.handlePtr); + if (rc != 0) { + CPR_ERROR("%s - Error acquiring mutex %s: %d\n", + fname, cprMutexPtr->name, rc); + return CPR_FAILURE; + } + return CPR_SUCCESS; + } + + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; +} + + +/** + * cprReleaseMutex + * + * @brief Release ownership of a mutex + * + * This function unlocks the mutex referenced by the mutex parameter. + * @param[in] mutex - Which mutex to release + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t +cprReleaseMutex (cprMutex_t mutex) +{ + static const char fname[] = "cprReleaseMutex"; + cpr_mutex_t *cprMutexPtr; + int32_t rc; + + cprMutexPtr = (cpr_mutex_t *) mutex; + if (cprMutexPtr != NULL) { + rc = pthread_mutex_unlock((pthread_mutex_t *) cprMutexPtr->u.handlePtr); + if (rc != 0) { + CPR_ERROR("%s - Error releasing mutex %s: %d\n", + fname, cprMutexPtr->name, rc); + return CPR_FAILURE; + } + return CPR_SUCCESS; + } + + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; +} + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_locks.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_locks.h new file mode 100644 index 000000000000..5e1d8df0c6f8 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_locks.h @@ -0,0 +1,13 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_LOCKS_H_ +#define _CPR_ANDROID_LOCKS_H_ + +/* + * System Mutexes. + */ +extern pthread_mutex_t linuxThreadMutex; +#endif + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_private.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_private.h new file mode 100644 index 000000000000..96f2147a1d08 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_private.h @@ -0,0 +1,13 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_PRIVATE_H_ +#define _CPR_ANDROID_PRIVATE_H_ + +extern pthread_mutexattr_t cprMutexAttributes; + +cpr_status_e cprLockInit(void); +cpr_status_e cprTimerInit(void); + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_rand.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_rand.h new file mode 100644 index 000000000000..a64aaeafc548 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_rand.h @@ -0,0 +1,15 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_RAND_H_ +#define _CPR_ANDROID_RAND_H_ + +/* @def Defines to generate a random number */ +#define cpr_srand(seed) srand(seed) +#define cpr_rand() rand() + + + +#endif + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_socket.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_socket.c new file mode 100644 index 000000000000..4e271b416faa --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_socket.c @@ -0,0 +1,965 @@ +/* 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 "cpr_types.h" +#include "cpr_stdio.h" +#include "cpr_assert.h" +#include "cpr_socket.h" +#include "cpr_debug.h" +#include "cpr_rand.h" +#include "cpr_timers.h" +#include "cpr_errno.h" +#include "cpr_stdlib.h" +#include "cpr_string.h" +#include +#include +#include + + +//const cpr_in6_addr_t in6addr_any = IN6ADDR_ANY_INIT; +const cpr_ip_addr_t ip_addr_invalid = {0}; + +#define IN6ADDRSZ 16 +#define INT16SZ 2 +#define INADDRSZ 4 + +#define MAX_RETRY_FOR_EAGAIN 10 + +/* Forward declarations of internal (helper) functions */ +static int cpr_inet_pton4(const char *src, uint8_t *dst, int pton); +static int cpr_inet_pton6(const char *src, uint8_t *dst); + +/** + * cprBind + * + * @brief The cprBind function is the CPR wrapper for the "Bind" socket call. + * + * The cprBind() function shall assign a local socket address address to a + * socket identified by descriptor socket that has no local socket address + * assigned. Sockets created with the cprSocket() function are initially + * unnamed; they are identified only by their address family. + * + * @param[in] soc - The socket previously created using cprAccept that is to be + * bound + * @param[out] addr - The address of the socket that is to be bound + * @param[in] addr_len Points to a cpr_socklen_t structure which on specifies the length + * of the supplied cpr_sockaddr_t structure. + * + * @return CPR_SUCCESS on success otherwise, CPR_FAILURE. cpr_errno needs to be set in this case. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] socket is not a valid socket descriptor. + * @li [CPR_ENOTSOCK] The descriptor references a file, not a socket + * + */ +cpr_status_e +cprBind (cpr_socket_t soc, + CONST cpr_sockaddr_t * RESTRICT addr, + cpr_socklen_t addr_len) +{ + cprAssert(addr != NULL, CPR_FAILURE); + + return ((bind(soc, (struct sockaddr *)addr, addr_len) != 0) ? + CPR_FAILURE : CPR_SUCCESS); +} + +/** + * cprCloseSocket + * + * @brief The cprCloseSocket function shall destroy the socket + * + * The cprCloseSocket() function shall destroy the socket descriptor indicated + * by socket. The descriptor may be made available for return by subsequent + * calls to cprSocket(). If the linger option is set with a non-zero timeout + * and the socket has untransmitted data, then cprCloseSocket() shall block for + * up to the current linger interval until all data is transmitted. + * + * @param[in] soc - The socket that needs to be destroyed + * + * @return CPR_SUCCESS on success otherwise, CPR_FAILURE. cpr_errno needs to be set in this case. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] socket is not a valid socket descriptor. + */ +cpr_status_e +cprCloseSocket (cpr_socket_t soc) +{ + return ((close(soc) != 0) ? CPR_FAILURE : CPR_SUCCESS); +} + +/** + * cprConnect + * + * @brief The cprConnect function is the wrapper for the "connect" socket API + * + * The cprConnect() function shall attempt to make a connection on a socket. + * If the connection cannot be established immediately and non-blocking is set for + * the socket, cprConnect() shall fail and set cpr_errno to [CPR_EINPROGRESS], but + * the connection request shall not be aborted, and the connection shall be + * established asynchronously. When the connection has been established + * asynchronously, cprSelect() shall indicate that the file descriptor for the + * socket is ready for writing. + * If the initiating socket is connection-mode, then cprConnect() shall attempt to + * establish a connection to the address specified by the address argument.If the + * connection cannot be established immediately and non-blocking is not set for + * the socket, cprConnect() shall block for up to an unspecified timeout interval + * until the connection is established. If the timeout interval expires before the + * connection is established, cprConnect() shall fail and the connection attempt + * shall be aborted. + * If the initiating socket is connectionless (i.e. SOCK_DGRAM), then cprConnect() + * shall set the socket's peer address, and no connection is made. The peeraddress + * identifies where all datagrams are sent on subsequent cprSend() functions, and + * limits the remote sender for subsequent cprRecv() functions. If address is a + * null address for the protocol, the socket's peer address shall be reset. + * + * @param[in] soc - Specifies the socket created with cprSocket() to connect + * @param[in] addr - A pointer to a cpr_sockaddr_t structure containing the peer address. + * @param[in] addr_len - Points to a cpr_socklen_t structure which specifies + * the length of the supplied cpr_sockaddr_t structure. + * + * @return CPR_SUCCESS on success otherwise, CPR_FAILURE. cpr_errno needs to be set in this case. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] socket is not a valid socket descriptor. + */ +cpr_status_e +cprConnect (cpr_socket_t soc, + SUPPORT_CONNECT_CONST cpr_sockaddr_t * RESTRICT addr, + cpr_socklen_t addr_len) +{ + int retry = 0, retval; + + cprAssert(addr != NULL, CPR_FAILURE); + + retval = connect(soc, (struct sockaddr *)addr, addr_len); + + while( retval == -1 && errno == EAGAIN && retry < MAX_RETRY_FOR_EAGAIN ) { + cprSleep(100); + retry++; + retval = connect(soc, (struct sockaddr *)addr, addr_len); + } + + return ((retval!=0) ? CPR_FAILURE : CPR_SUCCESS); +} + +/** + * cprGetSockName + * + * @brief The cprGetSockName retrieves the locally-bound name of the socket + * + * The cprGetSockName() function shall retrieve the locally-bound name of the + * specified socket, store this address in the cpr_sockaddr_t struct + * structure pointed to by the "addr" argument, and store the length of this address in + * the object pointed to by the "addr_len" argument. If the actual length + * of the address is greater than the length of the supplied cpr_sockaddr_t + * structure, the stored address shall be truncated. If the socket has not been + * bound to a local name, the value stored in the object pointed to by address is + * unspecified. + * + * @param[in] soc - Specifies the socket to get the peer address from + * @param[out] addr - A pointer to a cpr_sockaddr_t structure containing the peer address. + * @param[out] addr_len - Points to a cpr_socklen_t structure which specifies + * the length of the supplied cpr_sockaddr_t structure. + * @return CPR_SUCCESS on success otherwise, CPR_FAILURE. cpr_errno needs to be set in this case. + * + * @note If successful, the address argument shall point to the address of the socket + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_EINVAL] cprListen() has not been called on the socket descriptor. + * @li [CPR_ENOTCONN] The socket is not connected + * @li [CPR_ENOTSOCK] The descriptor references a file, not a socket. + * @li [CPR_OPNOTSUPP] The operation is not supported for the socket + */ +cpr_status_e +cprGetSockName (cpr_socket_t soc, + cpr_sockaddr_t * RESTRICT addr, + cpr_socklen_t * RESTRICT addr_len) +{ + cprAssert(addr != NULL, CPR_FAILURE); + cprAssert(addr_len != NULL, CPR_FAILURE); + + return ((getsockname(soc, (struct sockaddr *)addr, addr_len) != 0) ? + CPR_FAILURE : CPR_SUCCESS); +} + +/** + * cprListen + * + * @brief The cprListen is the CPR wrapper for the "listen" socket API. + * + * The cprListen() function shall mark a connection-mode socket, specified by + * the "soc" argument, as accepting connections. The "backlog" argument + * provides a hint to the implementation which the implementation shall use to + * limit the number of outstanding connections in the socket's listen queue. + * Implementations may impose a limit on backlog and silently reduce the + * specified value. Implementations shall support values of backlog up to + * SOMAXCONN. + * If listen() is called with a backlog argument value that is less than zero + * (0), the function behaves as if it had been called with a backlog argument + * value of 0. A backlog argument of zero (0) may allow the socket to accept + * connections, in which case the length of the listen queue may be set to an + * implementation-defined minimum value. + * + * @param[in] soc - Specifies the socket to get the peer address + * @param[in] backlog - The limit on the number of outstanding connections + * + * @return CPR_SUCCESS on success otherwise, CPR_FAILURE. cpr_errno needs to be set in this case. + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_EINVAL] cprListen() has not been called on the socket descriptor. + * @li [CPR_ENOTSOCK] The descriptor references a file, not a socket. + * @li [CPR_EDESTADDRREQ] The socket is not bound to a local address + */ +cpr_status_e +cprListen (cpr_socket_t soc, + uint16_t backlog) +{ + return ((listen(soc, backlog) != 0) ? CPR_FAILURE : CPR_SUCCESS); +} + +/** + * cprRecv + * + * @brief The cprRecv() function shall receive a message from a socket. + * + * This function is normally used with connected sockets because it does not permit + * the application to retrieve the source address of received data. The cprRecv() + * function shall return the length of the message written to the buffer pointed + * to by the "buf" argument. + * For message-based sockets, e.g. SOCK_DGRAM, the entire message shall be read in + * a single operation. If the message is too long to fit in the supplied buffer + * and the "flags" argument does not have MSG_PEEK set, the excess bytes are + * discarded. If the MSG_WAITALL flag is not set, data shall be returned onlyup + * to the end of the first message. + * For stream-based sockets, e.g. SOCK_STREAM, message boundaries are ignored and + * data is returned as it becomes available; therefore, no data is discarded. + * If no messages are available at the socket and non-blocking is not set on the + * socket's file descriptor, cprRecv() shall block until a message arrives. If no + * messages are available at the socket and non-blocking is set on the socket's + * file descriptor, cprRecv() shall fail and set cpr_errno to [CPR_EAGAIN]or + * [CPR_EWOULDBLOCK]. + * The cprSelect() function can be used to determine when data is available to be + * received. The cprRecv() function is the same as cprRecvFrom() with a zero + * address_len argument. + * + * @param[in] soc - Specifies the socket to receive data + * @param[out] buf - Contains the data received + * @param[out] len - The length of the data received + * @param[in] flags - The options used for the recv. + * + * @return On success the length of the message in bytes (including zero). + * On failure SOCKET_ERROR shall be returned and cpr_errno set to + * indicate the error. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_ENOTSOCK] The descriptor references a file, not a socket. + * @li [CPR_EAGAIN] The socket is marked non-blocking and no data is + * waiting to be received. + * @li [CPR_EWOULDBLOCK] Same as CPR_EAGAIN + * @li [CPR_ENOTCONN] A receive attempt is made on a connection-mode socket that is not connected + * @li [CPR_ENOTSUPP] The specified flags are not supported for this type of socket or protocol + * + */ +ssize_t +cprRecv (cpr_socket_t soc, + void * RESTRICT buf, + size_t len, + int32_t flags) +{ + ssize_t rc; + int retry = 0; + + cprAssert(buf != NULL, CPR_FAILURE); + + rc = recv(soc, buf, len, flags); + while( rc == -1 && errno == EAGAIN && retry < MAX_RETRY_FOR_EAGAIN ) { + cprSleep(100); + retry++; + rc = recv(soc, buf, len, flags); + } + if (rc == -1) { + return SOCKET_ERROR; + } + return rc; +} + +/** + * cprRecvFrom + * + * @brief The cprRecvFrom() function shall receive a message from a specific socket. + * + * The cprRecvFrom() function shall receive a message from a socket and is + * normally used with connectionless-mode sockets because it permits the + * application to retrieve the source address of received data. This function + * shall return the length of the message written to the buffer pointed to bythe + * buffer argument. + * For message-based sockets, e.g. SOCK_DGRAM, the entire message shall be read in + * a single operation. If the message is too long to fit in the supplied buffer + * and the flags argument does not have MSG_PEEK set, the excess bytes are + * discarded. If the MSG_WAITALL flag is not set, data shall be returned onlyup + * to the end of the first message. + * For stream-based sockets, e.g. SOCK_STREAM, message boundaries are ignored and + * data is returned as it becomes available; therefore, no data is discarded. + * If no messages are available at the socket and non-blocking is not set on the + * socket's file descriptor, cprRecvFrom() shall block until a message arrives. If no + * messages are available at the socket and non-blocking is set on the socket's + * file descriptor, cprRecvFrom() shall fail and set cpr_errno to [CPR_EAGAIN]or + * [CPR_EWOULDBLOCK]. + * + * @param[in] soc - Specifies the socket to receive data + * @param[out] buf - Contains the data received + * @param[out] len - The length of the data received + * @param[in] flags - The options used for the recvFrom + * @param[out] from - A null pointer or pointer to a cpr_sockaddr_t structure in + * which the sending address is to be stored. + * @param[out] fromlen - The length of the cpr_sockaddr_t structure pointed to by + * the "from" argument. + * + * @return On success the length of the message in bytes (including zero). + * On failure SOCKET_ERROR shall be returned and cpr_errno set to + * indicate the error. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_ENOTSOCK] The descriptor references a file, not a socket. + * @li [CPR_EAGAIN] The socket is marked non-blocking and no data is + * waiting to be received. + * @li [CPR_EWOULDBLOCK] Same as CPR_EAGAIN + * @li [CPR_ENOTCONN] A receive attempt is made on a connection-mode socket that is not connected + * @li [CPR_ENOTSUPP] The specified flags are not supported for this type of socket or protocol + * + */ +ssize_t +cprRecvFrom (cpr_socket_t soc, + void * RESTRICT buf, + size_t len, + int32_t flags, + cpr_sockaddr_t * RESTRICT from, + cpr_socklen_t * RESTRICT fromlen) +{ + ssize_t rc; + int retry = 0; + + cprAssert(buf != NULL, CPR_FAILURE); + cprAssert(from != NULL, CPR_FAILURE); + cprAssert(fromlen != NULL, CPR_FAILURE); + + rc = recvfrom(soc, buf, len, flags, (struct sockaddr *)from, fromlen); + while( rc == -1 && errno == EAGAIN && retry < MAX_RETRY_FOR_EAGAIN ) { + cprSleep(100); + retry++; + rc = recvfrom(soc, buf, len, flags, (struct sockaddr *)from, fromlen); + } + + if (rc == -1) { + CPR_INFO("error in recvfrom buf=%x fromlen=%d\n", buf, *fromlen); + return SOCKET_ERROR; + } + return rc; +} + +/** + * cprSelect + * + * @brief The cprSelect() function is the CPR wrapper for the "select" socket API. + * + * The cprSelect() function returns which of the specified file descriptors is ready for + * reading, ready for writing, or has an exception pending. The function will + * block up to the specified timeout interval for one of the conditions to be + * true or until interrupted by a signal. + * + * File descriptor masks of type fd_set can be initialized and tested with + * FD_CLR(), FD_ISSET(), FD_SET(), and FD_ZERO(). The OS-implementation may + * implement these calls either as a macro definition or an actual function. + * void FD_CLR(cpr_socket_t, fd_set *) shall remove the file descriptor fd from the + * set. If fd is not a member of this set, there shall be no effect on theset. + * int FD_ISSET(cpr_socket_t, fd_set *) shall evaluate to non-zero if the file + * descriptor fd is a member of the set, and shall evaluate to zero otherwise. + * void FD_SET(cpr_socket_t, fd_set *) shall add the file descriptor fd to the set. + * If the file descriptor fd is already in this set, there shall be no effect on + * the set. + * void FD_ZERO(fd_set *) shall initialize the descriptor set to the null set. + * + * @param[in] nfds Specifies the argument range of file descriptors to be tested. The + * descriptors from zero through nfds-1 in the descriptor sets shall be examined. + * @param[in] read_fds If not a null pointer, this is a pointer to an fd_set object. + * @li On input this specifies the file descriptors to be checked for being ready to read. + * @li On output this specifies the file descriptors that are ready to read. + * @param[in] write_fds If not a null pointer, this is a pointer to an fd_set object. + * @li On input this specifies the file descriptors to be checked for being ready to write. + * @li On output this specifies the file descriptors that are ready to write. + * @param[in] except_fds If not a null pointer, this is a pointer to an fd_set object. + * @li On input this specifies the file descriptors to be checked for errors/exceptions pending. + * @li On output this specifies the file descriptors that have errors/exceptions pending. + * @param[in] timeout If not a null pointer, this points to an object of type struct cpr_timeval + * that specifies the maximum time interval to wait for the selection to complete. + * If timeout expires, the function shall return. If the parameter is a null pointer, the function + * will block indefinitely until at least one file descriptor meets the criteria. + * + * @note While this function supports multiple file descriptor types, only file descriptors referring to a + * socket are guaranteed to be supported. + * @note Note that the "nfds" parameter is not used in Windows. + * + * @return Upon successful completion, cprSelect() shall return the number of file descriptors ready. + * Otherwise, SOCKET_ERROR shall be returned and cpr_errno set to indicate the error where read_fds, + * write_fds and error_fds are not modified. + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_INTR] The function was interrupted before an event or + * timeout occurred + * @li [CPR_INVAL] An invalid timeout was specified or nfds is less + * than 0 or greater than FD_SETSIZE + * + */ +int16_t +cprSelect (uint32_t nfds, + fd_set * RESTRICT read_fds, + fd_set * RESTRICT write_fds, + fd_set * RESTRICT except_fds, + struct cpr_timeval * RESTRICT timeout) +{ + int16_t rc; + struct timeval t, *t_p; + + if (timeout != NULL) { + t.tv_sec = timeout->tv_sec; + t.tv_usec = timeout->tv_usec; + t_p = &t; + } else { + t_p = NULL; + } + + rc = (int16_t) select(nfds, read_fds, write_fds, except_fds, t_p); + if (rc == -1) { + return SOCKET_ERROR; + } + return rc; +} + +/** + * cprSend + * + * @brief The cprSend() function is the CPR wrapper for the "send" socket API. + * + * The cprSend() function shall transmit a message from the specified socket to + * its peer. The cprSend() function shall send a message only when the socket is + * connected. The length of the message to be sent is specified by the length + * argument. If the message is too long to pass through the underlying protocol, + * cprSend() shall fail and no data is transmitted. Delivery of the message is + * not guaranteed. + * If space is not available at the sending socket to hold the message to be + * transmitted, and the socket does not have non-blocking set, cprSend() shall + * block until space is available; otherwise, if non-blocking is set, the + * cprSend() call shall fail. + * + * @param[in] soc Specifies the socket created with cprSocket() to send + * @param[in] buf A pointer to the buffer of the message to send. + * @param[in] len Specifies the length in bytes of the message pointed to by the buffer argument. + * @param[in] flags The socket options + * + * @return Upon successful completion, cprSend() shall return the number of + * bytes sent. Otherwise, SOCKET_ERROR shall be returned and cpr_errno set to + * indicate the error. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_ENOTSOCK] socket does not refer to a socket descriptor + * @li [CPR_EAGAIN] The socket is marked non-blocking and no data can + * be sent + * @li [CPR_EWOULDBLOCK] Same as CPR_EAGAIN + * @li [CPR_ENOTCONN] A connection-mode socket that is not connected + * @li [CPR_ENOTSUPP] The specified flags are not supported for this + * type of socket or protocol. + * @li [CPR_EMSGSIZE] The message is too large to be sent all at once + * @li [CPR_EDESTADDRREQ] The socket has no peer address set + * + */ +ssize_t +cprSend (cpr_socket_t soc, + CONST void *buf, + size_t len, + int32_t flags) +{ + ssize_t rc; + int retry = 0; + + cprAssert(buf != NULL, CPR_FAILURE); + + rc = send(soc, buf, len, flags); + while( rc == -1 && errno == EAGAIN && retry < MAX_RETRY_FOR_EAGAIN ) { + cprSleep(100); + retry++; + rc = send(soc, buf, len, flags); + } + + if (rc == -1) { + return SOCKET_ERROR; + } + return rc; +} + +/** + * cprSendTo + * + * @brief The cprSendTo() function is the CPR wrapper for the "send" socket API. + * + * The cprSendTo() function shall send a message through a socket. If the socket + * is connectionless-mode, the message shall be sent to the address specified by + * address. If the socket is connection-mode, address shall be ignored. + * Delivery of the message is not guaranteed. + * If space is not available at the sending socket to hold the message to be + * transmitted, and the socket does not have non-blocking set, cprSendTo() shall + * block until space is available; otherwise, if non-blocking is set, the + * cprSendTo() call shall fail. + * The cprSelect() function can be used to determine when it is possible to send + * more data. + * + * @param[in] soc Specifies the socket created with cprSocket() to send + * @param[in] msg A pointer to the buffer of the message to send. + * @param[in] len Specifies the length in bytes of the message pointed to by the buffer argument. + * @param[in] flags The socket options + * @param[in] dest_addr Points to a cpr_sockaddr_t structure containing the destination + * address. + * @param[in] dest_len Specifies the length of the cpr_sockaddr_t structure pointed to by + * the "dest_addr" argument. + * + * @return Upon successful completion, cprSend() shall return the number of + * bytes sent. Otherwise, SOCKET_ERROR shall be returned and cpr_errno set to + * indicate the error. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_ENOTSOCK] socket does not refer to a socket descriptor + * @li [CPR_EAGAIN] The socket is marked non-blocking and no data can + * be sent + * @li [CPR_EWOULDBLOCK] Same as CPR_EAGAIN + * @li [CPR_ENOTCONN] A connection-mode socket that is not connected + * @li [CPR_ENOTSUPP] The specified flags are not supported for this + * type of socket or protocol. + * @li [CPR_EMSGSIZE] The message is too large to be sent all at once + * @li [CPR_EDESTADDRREQ] The socket has no peer address set + * + */ +ssize_t +cprSendTo (cpr_socket_t soc, + CONST void *msg, + size_t len, + int32_t flags, + CONST cpr_sockaddr_t *dest_addr, + cpr_socklen_t dest_len) +{ + ssize_t rc; + int retry = 0; + + cprAssert(msg != NULL, CPR_FAILURE); + cprAssert(dest_addr != NULL, CPR_FAILURE); + + rc = sendto(soc, msg, len, flags, (struct sockaddr *)dest_addr, dest_len); + while( rc == -1 && errno == EAGAIN && retry < MAX_RETRY_FOR_EAGAIN ) { + cprSleep(100); + retry++; + rc = sendto(soc, msg, len, flags, (struct sockaddr *)dest_addr, dest_len); + } + + if (rc == -1) { + return SOCKET_ERROR; + } + return rc; +} + +/** + * cprSetSockOpt + * + * @brief The cprSetSockOpt() function is used to set the socket options + * + * The cprSetSockOpt() function shall set the option specified by the + * option_name argument, at the protocol level specified by the "level" argument, + * to the value pointed to by the "opt_val" argument for the socket specified + * by the "soc" argument. + * The level argument specifies the protocol level at which the option resides. To + * set options at the socket level, specify the level argument as SOL_SOCKET. To + * set options at other levels, supply the appropriate level identifier for the + * protocol controlling the option. For example, to indicate that an option is + * interpreted by the TCP (Transport Control Protocol), set level to IPPROTO_TCP + * as defined in the header. + * The opt_name argument specifies a single option to set. The option_name + * argument and any specified options are passed uninterpreted to the appropriate + * protocol module. The header defines the socket-level options. + * + * @param[in] soc The socket on which the options need to be set + * @param[in] level The protocol level at which the option resides + * @param[in] opt_name This specifies the single option that is being set + * @param[in] opt_val The values for the option + * @param[in] opt_len The length field for the option values + * + * @return Upon successful completion, CPR_SUCCESS shall be returned; + * otherwise, CPR_FAILURE shall be returned and cpr_errno set to indicate the + * error. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_ENOTSOCK] socket does not refer to a socket descriptor + * @li [CPR_EINVAL] The specified option is invalid or the socket is + * shut down + * @li [CPR_EISCONN] The specified socket is already connected and can + * not be changed + * @li [CPR_ENOPROTOOPT] The option is not supported by the protocol + * + */ +cpr_status_e +cprSetSockOpt (cpr_socket_t soc, + uint32_t level, + uint32_t opt_name, + CONST void *opt_val, + cpr_socklen_t opt_len) +{ + cprAssert(opt_val != NULL, CPR_FAILURE); + + return ((setsockopt(soc, (int)level, (int)opt_name, opt_val, opt_len) != 0) + ? CPR_FAILURE : CPR_SUCCESS); +} + +/** + * cprSetSockNonBlock + * + * @brief The cprSetSockNonBlock() function is used to set the socket options + * + * The cprSetSockNonBlock() function shall set a socket to be non blocking. It + * uses the fcntl function on the socket desriptor to achieve this. If the fcntl + * operation fails, a CPR_FAILURE is returned and errno is set by the OS + * implementation. + * + * @param[in] soc The socket that needs to be set to non-blocking + * + * @return Upon successful completion, CPR_SUCCESS shall be returned; + * otherwise, CPR_FAILURE shall be returned and cpr_errno set to indicate the + * error. + */ +cpr_status_e +cprSetSockNonBlock (cpr_socket_t soc) +{ + + return ((fcntl(soc, F_SETFL, O_NONBLOCK) != 0) ? CPR_FAILURE : CPR_SUCCESS); +} + + +/** + * cprSocket + * + * @brief The cprSocket() is the CPR wrapper for the "socket" API + * + * The cprSocket() function shall create an unbound socket in a + * communications domain, and return a file descriptor that can be used + * in later function calls that operate on sockets. + * + * @param[in] domain The communications domain, i.e. address family, in which a socket is to + * be created + * @param[in] type The type of socket to be created. The following types must + * be supported: + * @li SOCK_STREAM Provides sequenced, reliable, bidirectional, connection-mode + * byte streams, i.e. TCP + * @li SOCK_DGRAM Provides connectionless-mode, unreliable + * datagrams of fixed maximum length, i.e. UDP + * @li SOCK_SEQPACKET Provides sequenced, reliable, bidirectional, connection-mode + * transmission paths for records. A single operation never transfers part of + * more than one record. Record boundaries are visible to the receiver via the + * MSG_EOR flag. + * @param[in] protocol The protocol to be used with the socket. + * + * @return Upon successful completion, a socket handle defined by + * cpr_socket_t shall be returned; otherwise, INVALID_SOCKET shall be returned and + * cpr_errno set to indicate the error. + * + * @note The possible error values this function should return are + * @li [CPR_EBADF] The socket argument is not a valid file descriptor + * @li [CPR_ENOTSOCK] socket does not refer to a socket descriptor + * @li [CPR_EINVAL] The specified option is invalid or the socket is + * shut down + * @li [CPR_EMFILE] No more socket descriptors available for the + * process + * @li [CPR_ENFILE] No more socket descriptors available for the + * system + * @li [CPR_EPROTOTYPE] The socket type is not supported by the + * protocol + * @li [CPR_EPROTONOSUPPORT] The protocol is not supported for the + * domain + * + */ +cpr_socket_t +cprSocket (uint32_t domain, + uint32_t type, + uint32_t protocol) +{ + cpr_socket_t s; + + s = socket((int)domain, (int)type, (int)protocol); + if (s == -1) { + return INVALID_SOCKET; + } + return s; +} + +/** + * @} + */ + + + +/* cpr_inet_pton + * Convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * @param[in] af The address family IPv4 or IPv6 + * @param[in] src The address that needs to be converted + * @param[out] dst The address after the conversion + * @return + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + */ +int +cpr_inet_pton (int af, const char *src, void *dst) +{ + + switch (af) { + case AF_INET: + return (cpr_inet_pton4(src, dst, 1)); + case AF_INET6: + return (cpr_inet_pton6(src, dst)); + default: + return (-1); + } + /* NOTREACHED */ +} + + +/** + * Utility function that sets up the socket address, using + * the name and the pid to guarantee uniqueness + * + * @param[in] addr - socket fd to bind with the IPC address. + * @param[in] name - pointer to the name of socket to bind to. + * + * + * @pre (name != NULL) + */ +void cpr_set_sockun_addr (cpr_sockaddr_un_t *addr, const char *name, pid_t pid) +{ + /* Bind to the local socket */ + memset(addr, 0, sizeof(cpr_sockaddr_un_t)); + addr->sun_family = AF_LOCAL; + snprintf((char *) addr->sun_path, sizeof(addr->sun_path), "%s_%d", name, pid); +} + +/* int + * inet_pton4(src, dst, pton) + * when last arg is 0: inet_aton(). with hexadecimal, octal and shorthand. + * when last arg is 1: inet_pton(). decimal dotted-quad only. + * return: + * 1 if `src' is a valid input, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + */ +static int +cpr_inet_pton4(const char *src, uint8_t *dst, int pton) +{ + uint32_t val; + uint32_t digit; + int base, n; + unsigned char c; + uint32_t parts[4]; + uint32_t *pp = parts; + + c = *src; + for (;;) { + /* + * Collect number up to ``.''. + * Values are specified as for C: + * 0x=hex, 0=octal, isdigit=decimal. + */ + if (!isdigit(c)) + return (0); + val = 0; base = 10; + if (c == '0') { + c = *++src; + if (c == 'x' || c == 'X') + base = 16, c = *++src; + else if (isdigit(c) && c != '9') + base = 8; + } + /* inet_pton() takes decimal only */ + if (pton && base != 10) + return (0); + for (;;) { + if (isdigit(c)) { + digit = c - '0'; + if (digit >= (uint16_t)base) + break; + val = (val * base) + digit; + c = *++src; + } else if (base == 16 && isxdigit(c)) { + digit = c + 10 - (islower(c) ? 'a' : 'A'); + if (digit >= 16) + break; + val = (val << 4) | digit; + c = *++src; + } else + break; + } + if (c == '.') { + /* + * Internet format: + * a.b.c.d + * a.b.c (with c treated as 16 bits) + * a.b (with b treated as 24 bits) + * a (with a treated as 32 bits) + */ + if (pp >= parts + 3) + return (0); + *pp++ = val; + c = *++src; + } else + break; + } + /* + * Check for trailing characters. + */ + if (c != '\0' && !isspace(c)) + return (0); + /* + * Concoct the address according to + * the number of parts specified. + */ + n = pp - parts + 1; + /* inet_pton() takes dotted-quad only. it does not take shorthand. */ + if (pton && n != 4) + return (0); + switch (n) { + + case 0: + return (0); /* initial nondigit */ + + case 1: /* a -- 32 bits */ + break; + + case 2: /* a.b -- 8.24 bits */ + if (parts[0] > 0xff || val > 0xffffff) + return (0); + val |= parts[0] << 24; + break; + + case 3: /* a.b.c -- 8.8.16 bits */ + if ((parts[0] | parts[1]) > 0xff || val > 0xffff) + return (0); + val |= (parts[0] << 24) | (parts[1] << 16); + break; + + case 4: /* a.b.c.d -- 8.8.8.8 bits */ + if ((parts[0] | parts[1] | parts[2] | val) > 0xff) + return (0); + val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8); + break; + } + if (dst) { + val = htonl(val); + memcpy(dst, &val, INADDRSZ); + } + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + */ +static int +cpr_inet_pton6(const char *src, uint8_t *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + uint8_t tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + unsigned int val; + + memset((tp = tmp), '\0', IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } else if (*src == '\0') + return (0); + if (tp + INT16SZ > endp) + return (0); + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + INADDRSZ) <= endp) && + cpr_inet_pton4(curtok, tp, 1) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + INT16SZ > endp) + return (0); + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + if (tp == endp) + return (0); + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_socket.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_socket.h new file mode 100644 index 000000000000..36aff45c6edb --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_socket.h @@ -0,0 +1,351 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_SOCKET_H_ +#define _CPR_ANDROID_SOCKET_H_ + +#include "cpr_types.h" +#include +#include +#include +#include +#include +#include +#include + +/** + * Set public CPR header file options + */ +#ifdef CPR_USE_SOCKETPAIR +#undef CPR_USE_SOCKETPAIR +#endif +#define SUPPORT_CONNECT_CONST const + +/** + * Define SOCKET_ERROR + */ +#define SOCKET_ERROR (-1) + +/** + * Define INVALID_SOCKET + */ +#define INVALID_SOCKET (-1) + +/** + * Define cpr_socket_t + */ +typedef int cpr_socket_t; + +/** + * Define cpr_socklen_t + */ +typedef socklen_t cpr_socklen_t; + +/** + * Address family, defined in sys/socket.h + * AF_UNSPEC + * AF_LOCAL / AF_UNIX + * AF_INET + * AF_INET6 + * AF_MAX + * + * AF_NETLYR2 (unique to CNU) interface directly to layer 2, bypass IP + */ +#ifndef AF_UNIX +#define AF_UNIX AF_LOCAL +#endif + + +/* + * Constants and structures defined by the internet system, + * Per RFC 790, September 1981, based on the BSD file netinet/in.h. + * IPv6 additions per RFC 2292. + */ + + +/* + * Define the following socket options as needed + * SO_DEBUG + * SO_ACCEPTCONN + * SO_REUSEADDR / SO_EXCLUSIVEADDRUSE + * SO_KEEPALIVE + * SO_DONTROUTE + * SO_BROADCAST + * SO_USELOOPBACK + * SO_LINGER / SO_DONTLINGER + * SO_OOBINLINE + * SO_SNDBUF + * SO_RCVBUF + * SO_ERROR + * SO_TYPE + * + * The following options are available for Unix-only variants + * SO_SNDLOWAT + * SO_RCVLOWAT + * SO_SNDTIMEO + * SO_RCVTIMEO + * SO_PROTOTYPE - Not documented as being supported by CNU + */ + +/* defined in netinet/in.h */ +#define SO_DONTLINGER ((int)(~SO_LINGER)) +#define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR)) + +/* + * Protocols (Base), + * reference http://www.iana.org/assignments/protocol-numbers.html + * IPPROTO_IP + * IPPROTO_GGP + * IPPROTO_ICMP + * IPPROTO_IGMP + * IPPROTO_IPV4 / IPPROTO_IPIP + * IPPROTO_TCP + * IPPROTO_EGP + * IPPROTO_PUP + * IPPROTO_UDP + * IPPROTO_IDP + * IPPROTO_IPV6 + * IPPROTO_ROUTING + * IPPROTO_FRAGMENT + * IPPROTO_ESP + * IPPROTO_AH + * IPPROTO_ICMPV6 + * IPPROTO_NONE + * IPPROTO_DSTOPTS + * IPPROTO_ND + * IPPROTO_EON + * IPPROTO_IGRP + * IPPROTO_ENCAP + * IPPROTO_IPCOMP + * IPPROTO_RAW + * IPPROTO_MAX + */ + +/* defined in netinet/in.h */ +#ifndef IPPROTO_IPV4 +#define IPPROTO_IPV4 4 +#endif + +#ifndef IPPROTO_IPIP +#define IPPROTO_IPIP IPPROTO_IPV4 +#endif + +//#define IPPROTO_RSVP 46 +#define IPPROTO_IGRP 88 /* Cisco/GXS IGRP */ +#define IPPROTO_EIGRP 88 +#define IPPROTO_IPCOMP 108 /* IP payload compression */ + +/* + * Protocols (IPv6) + * Assumming if IPV6 is not there, then none of the are + */ +#ifndef IPPROTO_IPV6 +#define IPPROTO_HOPOPTS 0 /* IPv6 hop-by-hop options */ +#define IPPROTO_IPV6 41 /* IPv6 */ +#define IPPROTO_ROUTING 43 /* IPv6 routing header */ +#define IPPROTO_FRAGMENT 44 /* IPv6 fragmentation header */ +#define IPPROTO_ICMPV6 58 /* ICMPv6 */ +#define IPPROTO_NONE 59 /* IPv6 no next header */ +#define IPPROTO_DSTOPTS 60 /* IPv6 destination options */ +#endif + +/* + * Protocols (Local) + * reference, RFC 3692 + * IPPROTO_UNX Local sockets Unix protocol + * IPPROTO_CDP Non-Standard at 254, technially this value + * is for experimentation and testing + */ + +/* defined in netinet/in.h */ + +/* + * Port/socket numbers: network standard functions + * reference http://www.iana.org/assignments/port-numbers + * IPPORT_ECHO + * IPPORT_DISCARD + * IPPORT_SYSTAT + * IPPORT_DAYTIME + * IPPORT_NETSTAT + * IPPORT_FTP + * IPPORT_SSH + * IPPORT_TELNET + * IPPORT_SMTP + * IPPORT_TIMESERVER + * IPPORT_NAMESERVER + * IPPORT_WHOIS + * IPPORT_MTP + * IPPORT_HTTP + * IPPORT_NTP + */ + +/* defined in netinet/in.h */ + +/* + * Port/socket numbers: host specific functions + * + * IPPORT_TFTP + * IPPORT_RJE + * IPPORT_FINGER + * IPPORT_TTYLINK + * IPPORT_SUPDUP + */ + +/* defined in netinet/in.h */ + +/* + * UNIX TCP sockets + * + * IPPORT_EXECSERVER + * IPPORT_LOGINSERVER + * IPPORT_CMDSERVER + * IPPORT_EFSSERVER + */ + +/* defined in netinet/in.h */ + +/* + * UNIX UDP sockets + * + * IPPORT_BIFFUDP + * IPPORT_WHOSERVER + * IPPORT_ROUTESERVER + */ + +/* defined in netinet/in.h */ + +/* + * SCCP sockets + */ +#define IPPORT_SCCP 2000 + +/* + * tbd: need to finalize placement. + * Define range of ephemeral ports used for Cisco IP Phones. + */ +#define CIPPORT_EPH_LOW 0xC000 +#define CIPPORT_EPH_HI 0xCFFF + + +/* + * Ports < IPPORT_RESERVED are reserved for + * privileged processes (e.g. root). + * + * IPPORT_RESERVED + * IPPORT_USERRESERVED + */ + +/* defined in netinet/in.h */ + +/* + * Define INADDR constants + * INADDR_ANY + * INADDR_LOOPBACK + * INADDR_BROADCAST + */ + +/* defined in netinet/in.h */ + +/* + * Define IN_CLASS constants/macros + * IN_CLASS{A|B|C|D}(x) + * IN_CLASS{A|B|C|D}_NET + * IN_CLASS{A|B|C|D}_NSHIFT + * IN_CLASS{A|B|C|D}_HOST + * IN_CLASS{A|B|C|D}_MAX + */ + + +/* + * sockaddr_storage: + * Common superset of at least AF_INET, AF_INET6 and AF_LINK sockaddr + * structures. Has sufficient size and alignment for those sockaddrs. + */ +typedef uint16_t sa_family_t; + +typedef struct +{ + sa_family_t sun_family; /* AF_LOCAL/AF_UNIX */ + char sun_path[108]; +} cpr_sockaddr_un_t; + + +/* + * Desired maximum size, alignment size and related types. + */ +#define _SS_MAXSIZE 256 /* Implementation specific max size */ + +#define cpr_sun_len(a) sizeof(a) +void cpr_set_sockun_addr(cpr_sockaddr_un_t *addr, const char *name, pid_t pid); + +/* + * To represent desired sockaddr max alignment for platform, a + * type is chosen which may depend on implementation platform architecture. + * Type chosen based on alignment size restrictions from . + * We desire to force up to (but no more than) 64-bit (8 byte) alignment, + * on platforms where it is possible to do so. (e.g not possible on ia32). + * For all currently supported platforms by our implementation + * in , (i.e. sparc, sparcv9, ia32, ia64) + * type "double" is suitable for that intent. + * + * Note: Type "double" is chosen over the more obvious integer type int64_t. + * int64_t is not a valid type for strict ANSI/ISO C compilation on ILP32. + */ +typedef double sockaddr_maxalign_t; + +#define _SS_ALIGNSIZE (sizeof (sockaddr_maxalign_t)) + +/* + * Definitions used for sockaddr_storage structure paddings design. + */ +#define _SS_PAD1SIZE (_SS_ALIGNSIZE - sizeof (sa_family_t)) +#define _SS_PAD2SIZE (_SS_MAXSIZE - (sizeof (sa_family_t)+ \ + _SS_PAD1SIZE + _SS_ALIGNSIZE)) + +#ifndef __cplusplus +typedef struct cpr_sockaddr_storage sockaddr_storage; +#endif + +/* + * IP level options + * IP_HDRINCL + * IP_TOS + * IP_TTL + */ + +/* defined in netinet/in.h */ + +/* + * TCP level options + * TCP_NODELAY + * TCP_MAXSEG + * TCP_KEEPALIVE + */ + +/* defined in netinet/tcp.h */ + + +/* TODO: Still to cleanup */ + +/* + * WinSock 2 extension -- new options + */ +#define SO_MAX_MSG_SIZE 0x2003 /* maximum message size */ + + +#define SO_NBIO 0x0400 /* Nonblocking socket I/O operation */ +#define SO_ASYNC 0x0800 /* should send asyn notification of + * I/O events */ +#define SO_VRFTABLEID 0x1000 /* set VRF routing table id */ +#define SO_SRC_SPECIFIED 0x2000 /* Specified Source Address to be used */ +#define SO_STRICT_ADDR_BIND 0x4000 /* Accept only those packets that have + * been sent to the address that this + * socket is bound to */ +/* + * Used for getting random port for tls connect + */ +#define TCP_PORT_RETRY_CNT 5 +#define TCP_PORT_MASK 0xfff + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_stdio.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_stdio.c new file mode 100644 index 000000000000..48b101bbca3d --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_stdio.c @@ -0,0 +1,148 @@ +/* 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 "cpr_stdio.h" +#include "cpr_string.h" +#include "CSFLog.h" + +/** + * @def LOG_MAX + * + * Constant represents the maximum allowed length for a message + */ +#define LOG_MAX 1024 + +/** + * @addtogroup DebugAPIs The CPR Logging Abstractions + * @ingroup CPR + * @brief The CPR Debug/Logging APIs + * + * @{ + */ + +/** + * Debug message + * + * @param _format format string + * @param ... variable arg list + * + * @return Return code from vsnprintf + * + * @pre (_format not_eq NULL) + */ +int +buginf (const char *_format, ...) +{ + char fmt_buf[LOG_MAX + 1]; + va_list ap; + int rc; + + va_start(ap, _format); + rc = vsnprintf(fmt_buf, LOG_MAX, _format, ap); + va_end(ap); + if (rc <= 0) { + return rc; + } + + CSFLogDebug("cpr", "%s", fmt_buf); + + return rc; +} + +/** + * Debug message that can be larger than #LOG_MAX + * + * @param str - a fixed constant string + * + * @return zero(0) + * + * @pre (str not_eq NULL) + */ +int +buginf_msg (const char *str) +{ + char buf[LOG_MAX + 1]; + const char *p; + int16_t len; + + // terminate buffer + buf[LOG_MAX] = NUL; + + len = (int16_t) strlen(str); + + if (len > LOG_MAX) { + p = str; + do { + memcpy(buf, p, LOG_MAX); + p += LOG_MAX; + len -= LOG_MAX; + + printf("%s",buf); + } while (len > LOG_MAX); + + if (len) { + CSFLogDebug("cpr", "%s", (char *)p); + } + } else { + CSFLogDebug("cpr", "%s", (char *) str); + } + + return 0; +} + +/** + * Error message + * + * @param _format format string + * @param ... variable arg list + * + * @return Return code from vsnprintf + * + * @pre (_format not_eq NULL) + */ +void +err_msg (const char *_format, ...) +{ + char fmt_buf[LOG_MAX + 1]; + va_list ap; + int rc; + + va_start(ap, _format); + rc = vsnprintf(fmt_buf, LOG_MAX, _format, ap); + va_end(ap); + if (rc <= 0) { + return; + } + + CSFLogError("cpr", "%s", fmt_buf); +} + + +/** + * Notice message + * + * @param _format format string + * @param ... variable arg list + * + * @return Return code from vsnprintf + * + * @pre (_format not_eq NULL) + */ +void +notice_msg (const char *_format, ...) +{ + char fmt_buf[LOG_MAX + 1]; + va_list ap; + int rc; + + va_start(ap, _format); + rc = vsnprintf(fmt_buf, LOG_MAX, _format, ap); + va_end(ap); + if (rc <= 0) { + return; + } + + CSFLogInfo("cpr", "%s", fmt_buf); +} + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_stdio.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_stdio.h new file mode 100644 index 000000000000..0d12d2751336 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_stdio.h @@ -0,0 +1,11 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_STDIO_H_ +#define _CPR_ANDROID_STDIO_H_ + +#include +#include + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_string.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_string.c new file mode 100644 index 000000000000..099fc735cd0f --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_string.c @@ -0,0 +1,173 @@ +/* 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 "cpr_types.h" +#include "cpr_stdlib.h" +#include "cpr_string.h" +#include "cpr_strings.h" + + +/** + * cpr_strdup + * + * @brief The CPR wrapper for strdup + + * The cpr_strdup shall return a pointer to a new string, which is a duplicate + * of the string pointed to by "str" argument. A null pointer is returned if the + * new string cannot be created. + * + * @param[in] str - The string that needs to be duplicated + * + * @return The duplicated string or NULL in case of no memory + * + */ +char * +cpr_strdup (const char *str) +{ + char *dup; + size_t len; + + if (!str) { + return (char *) NULL; + } + + len = strlen(str); + if (len == 0) { + return (char *) NULL; + } + len++; + + dup = cpr_malloc(len * sizeof(char)); + if (!dup) { + return (char *) NULL; + } + (void) memcpy(dup, str, len); + return dup; +} + + +/** + * cpr_strcasecmp + * + * @brief The CPR wrapper for strcasecmp + * + * The cpr_strcasecmp performs case insensitive string comparison of the "s1" + * and the "s2" strings. + * + * @param[in] s1 - The first string + * @param[in] s2 - The second string + * + * @return integer <,=,> 0 depending on whether s1 is <,=,> s2 + */ +#ifndef CPR_USE_OS_STRCASECMP +int +cpr_strcasecmp (const char *s1, const char *s2) +{ + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + /* No match if only one ptr is NULL */ + if ((!s1 && s2) || (s1 && !s2)) { + /* + * If one of these is NULL it will be the lesser of the two + * values and therefore we'll get the proper sign in the int + */ + return (int) (s1 - s2); + } + + /* Match if both ptrs the same (e.g. NULL) */ + if (s1 == s2) + return 0; + + while (*us1 != '\0' && *us2 != '\0' && toupper(*us1) == toupper(*us2)) { + us1++; + us2++; + } + + return (toupper(*us1) - toupper(*us2)); +} + +/** + * cpr_strncasecmp + * + * @brief The CPR wrapper for strncasecmp + * + * The cpr_strncasecmp performs case insensitive string comparison for specific + * length "len". + * + * @param[in] s1 - The first string + * @param[in] s2 - The second string + * @param[in] len - The length to be compared + * + * @return integer <,=,> 0 depending on whether s1 is <,=,> s2 + */ +int +cpr_strncasecmp (const char *s1, const char *s2, size_t len) +{ + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + /* No match if only one ptr is NULL */ + if ((!s1 && s2) || (s1 && !s2)) + return ((int) (s1 - s2)); + + if ((len == 0) || (s1 == s2)) + return 0; + + while (len-- > 0 && toupper(*us1) == toupper(*us2)) { + if (len == 0 || *us1 == '\0' || *us2 == '\0') + break; + us1++; + us2++; + } + + return (toupper(*us1) - toupper(*us2)); +} +#endif + +/** + * strcasestr + * + * @brief The same as strstr, but ignores case + * + * The strcasestr performs the strstr function, but ignores the case. + * This function shall locate the first occurrence in the string + * pointed to by s1 of the sequence of bytes (excluding the terminating + * null byte) in the string pointed to by s2. + * + * @param[in] s1 - The input string + * @param[in] s2 - The pattern to be matched + * + * @return A pointer to the first occurrence of string s2 found + * in string s1 or NULL if not found. If s2 is an empty + * string then s1 is returned. + */ +char * +strcasestr (const char *s1, const char *s2) +{ + unsigned int i; + + if (!s1) + return (char *) NULL; + + if (!s2 || (s1 == s2) || (*s2 == '\0')) + return (char *) s1; + + while (*s1) { + i = 0; + do { + if (s2[i] == '\0') + return (char *) s1; + if (s1[i] == '\0') + return (char *) NULL; + if (toupper(s1[i]) != toupper(s2[i])) + break; + i++; + } while (1); + s1++; + } + + return (char *) NULL; +} + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_string.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_string.h new file mode 100644 index 000000000000..963d3985d29f --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_string.h @@ -0,0 +1,30 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_STRING_H_ +#define _CPR_ANDROID_STRING_H_ + +#include +#include + +/** + * cpr_strdup + * + * @brief The CPR wrapper for strdup + + * The cpr_strdup shall return a pointer to a new string, which is a duplicate + * of the string pointed to by "str" argument. A null pointer is returned if the + * new string cannot be created. + * + * @param[in] str - The string that needs to be duplicated + * + * @return The duplicated string or NULL in case of no memory + * + */ +char * +cpr_strdup(const char *str); + + + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_strings.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_strings.h new file mode 100644 index 000000000000..0dfd75a54048 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_strings.h @@ -0,0 +1,10 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_STRINGS_H_ +#define _CPR_ANDROID_STRINGS_H_ + +#include + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_threads.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_threads.c new file mode 100644 index 000000000000..2be26dc66648 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_threads.c @@ -0,0 +1,203 @@ +/* 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 "cpr.h" +#include "cpr_stdlib.h" +#include "cpr_stdio.h" +#include +#include +#include +#include + +#define ANDROID_MIN_THREAD_PRIORITY (-20) /* tbd: check MV linux: current val from Larry port */ +#define ANDROID_MAX_THREAD_PRIORITY (+19) /* tbd: check MV linux. current val from Larry port */ + + +/** + * cprCreateThread + * + * @brief Create a thread + * + * The cprCreateThread function creates another execution thread within the + * current process. If the input parameter "name" is present, then this is used + * for debugging purposes. The startRoutine is the address of the function where + * the thread execution begins. The start routine prototype is defined as + * follows + * @code + * int32_t (*cprThreadStartRoutine)(void* data) + * @endcode + * + * @param[in] name - name of the thread created (optional) + * @param[in] startRoutine - function where thread execution begins + * @param[in] stackSize - size of the thread's stack + * @param[in] priority - thread's execution priority + * @param[in] data - parameter to pass to startRoutine + * + * Return Value: Thread handle or NULL if creation failed. + */ +cprThread_t +cprCreateThread (const char *name, + cprThreadStartRoutine startRoutine, + uint16_t stackSize, + uint16_t priority, + void *data) +{ + static const char fname[] = "cprCreateThread"; + static uint16_t id = 0; + cpr_thread_t *threadPtr; + pthread_t threadId; + pthread_attr_t attr; + + CPR_INFO("%s: creating '%s' thread\n", fname, name); + + /* Malloc memory for a new thread */ + threadPtr = (cpr_thread_t *)cpr_malloc(sizeof(cpr_thread_t)); + if (threadPtr != NULL) { + if (pthread_attr_init(&attr) != 0) { + + CPR_ERROR("%s - Failed to init attribute for thread %s\n", + fname, name); + cpr_free(threadPtr); + return (cprThread_t)NULL; + } + + if (pthread_attr_setstacksize(&attr, stackSize) != 0) { + CPR_ERROR("%s - Invalid stacksize %d specified for thread %s\n", + fname, stackSize, name); + cpr_free(threadPtr); + return (cprThread_t)NULL; + } + + if (pthread_create(&threadId, &attr, startRoutine, data) != 0) { + CPR_ERROR("%s - Creation of thread %s failed: %d\n", + fname, name, errno); + cpr_free(threadPtr); + return (cprThread_t)NULL; + } + + /* Assign name to CPR if one was passed in */ + if (name != NULL) { + threadPtr->name = name; + } + + /* + * TODO - It would be nice for CPR to keep a linked + * list of running threads for debugging purposes + * such as a show command or walking the list to ensure + * that an application does not attempt to create + * the same thread twice. + */ + threadPtr->u.handleInt = threadId; + threadPtr->threadId = ++id; + return (cprThread_t)threadPtr; + } + + /* Malloc failed */ + CPR_ERROR("%s - Malloc for thread %s failed.\n", fname, name); + errno = ENOMEM; + return (cprThread_t)NULL; +} + + +/** + * cprDestroyThread + * + * @brief Destroys the thread passed in. + * + * The cprDestroyThread function is called to destroy a thread. The thread + * parameter may be any valid thread including the calling thread itself. + * + * @param[in] thread - thread to destroy. + * + * @return CPR_SUCCESS or CPR_FAILURE. errno should be set for FAILURE case. + * + * @note In Linux there will never be a success indication as the + * calling thread will have been terminated. + */ +cprRC_t +cprDestroyThread (cprThread_t thread) +{ + static const char fname[] = "cprDestroyThread"; + cpr_thread_t *cprThreadPtr; + + cprThreadPtr = (cpr_thread_t *) thread; + if (cprThreadPtr != NULL) { + /* + * Make sure thread is trying to destroy itself. + */ + if ((pthread_t) cprThreadPtr->u.handleInt == pthread_self()) { + cprThreadPtr->threadId = 0; + cpr_free(cprThreadPtr); + pthread_exit(NULL); + return CPR_SUCCESS; + } + + CPR_ERROR("%s: Thread attempted to destroy another thread, not itself.\n", + fname); + errno = EINVAL; + return CPR_FAILURE; + } + + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; +} + +/** + * cprAdjustRelativeThreadPriority + * + * @brief The function sets the relative thread priority up or down by the given value. + * + * This function is used pSIPCC to set up the thread priority. The values of the + * priority range from -20 (Maximum priority) to +19 (Minimum priority). + * + * @param[in] relPri - nice value of the thread -20 is MAX and 19 is MIN + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t +cprAdjustRelativeThreadPriority (int relPri) +{ + const char *fname = "cprAdjustRelativeThreadPriority"; + + if (setpriority(PRIO_PROCESS, 0, relPri) == -1) { + CPR_ERROR("%s: could not set the nice..err=%d\n", + fname, errno); + return CPR_FAILURE; + } + return CPR_SUCCESS; +} + +/** + * @} + * @addtogroup ThreadInternal Helper functions for implementing threads in CPR + * @ingroup Threads + * @brief Helper functions used by CPR for thread implementation + * + * @{ + */ + +/** + * cprGetThreadId + * + * @brief Return the pthread ID for the given CPR thread. + * + * @param[in] thread - thread to query + * + * @return Thread's Id or zero(0) + * + */ +pthread_t +cprGetThreadId (cprThread_t thread) +{ + if (thread) { + return ((cpr_thread_t *)thread)->u.handleInt; + } + return 0; +} + +/** + * @} + */ diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_time.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_time.h new file mode 100644 index 000000000000..1ebba15d6f14 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_time.h @@ -0,0 +1,12 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_TIME_H_ +#define _CPR_ANDROID_TIME_H_ + +#include + +typedef size_t cpr_time_t; + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_timers.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_timers.h new file mode 100644 index 000000000000..1091448c6655 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_timers.h @@ -0,0 +1,55 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_TIMERS_H_ +#define _CPR_ANDROID_TIMERS_H_ +#include + +/* + * Linux does not provide native support for non-blocking timers + * so CPR provides that functionality for Linux. + */ + +/* + * Determine the granularity of the timers in + * milliseconds. ie how often does the TickThread + * wake up to decrement the timer intervals + */ +#define timerGranularity 10 + +//struct timerBlk_s timerBlk; + +typedef struct cpr_timer_s +{ + const char *name; + uint32_t cprTimerId; + cprMsgQueue_t callBackMsgQueue; + uint16_t applicationTimerId; + uint16_t applicationMsgId; + void *data; + union { + void *handlePtr; + }u; +}cpr_timer_t; + +/* Linked List of currently running timers */ +typedef struct timerDef +{ + int32_t duration; + boolean timerActive; + cpr_timer_t *cprTimerPtr; + struct timerDef *previous; + struct timerDef *next; +} timerBlk; + +/* Timer mutex for updating the timer linked list */ +extern pthread_mutex_t timerMutex; + +/* Start routines for the timer threads */ +extern void *linuxTimerTick(void *); + +cprRC_t cpr_timer_pre_init(void); +cprRC_t cpr_timer_de_init(void); + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_timers_using_select.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_timers_using_select.c new file mode 100644 index 000000000000..98b02a44f297 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_timers_using_select.c @@ -0,0 +1,1300 @@ +/* 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/. */ + +/** + * @brief CPR layer for Timers. + * + * This file contains the Cisco Portable Runtime layer for non-blocking + * timers. This implementation is for the Linux operating system using + * select with a timeout. + * + * Timer Service runs in its own thread and blocks on select + * call with a timeout. The value of timeout is set equal to + * the duration of the earliest expiring timer. The timer list + * is kept sorted by earliest to latest expiration times. Therefore, + * timeout value is simply the duration on the head of the list. + * + * For starting or cancelling a timer, the timer library contacts + * the timer service using a local socket based IPC. This is done + * by bringing up a connection between timer service and the client + * during timer init. + * Timer Service thread is the only thread that adds or removes + * timer blocks from the list. When timer library client wants + * to start or cancel a timer, the library sends an IPC message + * to the timer service thread on the socket connection. This + * unblocks the select call. Select also returns the amount of + * time left on the timeout when it gets unblocked. This amount is + * first subtracted from the head timer block duration. Then rest + * of the processing is carried out based on whether the request + * was to add or to remove a timer block and duration + * on all timer blocks is adjusted as required. + * When the select times out it implies the timers at the head + * of the list has expired. The list is scanned for expired timers + * and expiry processing such as posting message to the handler + * etc. is done. + * + * When there are no timers in the list, service blocks on select + * forever waiting for at least one timer to be added. + * + */ + +/** + * @defgroup Timers The Timer implementation module + * @ingroup CPR + * @brief The module related to Timer abstraction for the pSIPCC + * @addtogroup TimerAPIs The Timer APIs + * @ingroup Timers + * @brief APIs expected by pSIPCC for using Timers + * + */ + +#include "cpr.h" +#include "cpr_socket.h" +#include "cpr_stdlib.h" +#include "cpr_stdio.h" +#include "cpr_threads.h" +#include "cpr_timers.h" +#include "cpr_string.h" +#include "phntask.h" +#include +#include +#include "cpr_android_timers.h" +#include "platform_api.h" + +/*-------------------------------------------------------------------------- + * Local definitions + *-------------------------------------------------------------------------- + */ + +typedef struct timer_ipc_cmd_s +{ + cpr_timer_t *timer_ptr; + void *user_data_ptr; + uint32_t duration; +} timer_ipc_cmd_t; + + +typedef struct timer_ipc_s +{ + uint32_t msg_type; + union + { + timer_ipc_cmd_t cmd; + cprRC_t result; + }u; +} timer_ipc_t; + +#define TMR_CMD_ADD 1 +#define TMR_CMD_REMOVE 2 +#define TMR_RESULT 3 + +#define API_RETURN(_val) \ + { \ + pthread_mutex_unlock(&api_mutex); \ + return (_val); \ + }\ + +#define API_ENTER() \ +{\ + pthread_mutex_lock(&api_mutex);\ +}\ + +/* for AF_LOCAL not all implementations return client addr in recvfrom so + * using explicit path for client too. + */ +#define SERVER_PATH "/tmp/CprTmrServer" +#define CLIENT_PATH "/tmp/CprTmrClient" + + +/*-------------------------------------------------------------------------- + * Global data + *-------------------------------------------------------------------------- + */ + + +static timerBlk *timerListHead; + +static pthread_t timerThreadId; + + +static pthread_mutex_t api_mutex; + + +/* local socket used by timer library */ +static int client_sock = INVALID_SOCKET; + + +/* local socket used by the timer service */ +static int serv_sock = INVALID_SOCKET; + +static struct sockaddr_un tmr_serv_addr; +static struct sockaddr_un tmr_client_addr; + +static fd_set socks; /* descriptor set */ + + +/*-------------------------------------------------------------------------- + * External data references + *-------------------------------------------------------------------------- + */ + + +/*-------------------------------------------------------------------------- + * External function prototypes + *-------------------------------------------------------------------------- + */ + +/* + * Internal CPR function to fill in data in the sysheader. + * This is to prevent knowledge of the evil syshdr structure + * from spreading to cpr_linux_timers.c This thing is + * like kudzu... + */ +extern void fillInSysHeader(void *buffer, uint16_t cmd, uint16_t len, + void *timerMsg); + + + +/*-------------------------------------------------------------------------- + * Local scope function prototypes + *-------------------------------------------------------------------------- + */ + +static void *timerThread(void *data); +static cprRC_t start_timer_service_loop(); +static void process_expired_timers(); +static void send_api_result(cprRC_t result, struct sockaddr_un *addr, socklen_t len); + + +/** + * @addtogroup TimerAPIs The Timer APIs + * @ingroup Timers + * @{ + */ + +/** + * cprSleep + * + * @brief Suspend the calling thread + * The cprSleep function blocks the calling thread for the indicated number of + * milliseconds. + * + * @param[in] duration - Number of milliseconds the thread should sleep + * + * @return - none + */ +void +cprSleep (uint32_t duration) +{ + /* + * usleep() can only support up to one second, so split + * between sleep and usleep if one second or more + */ + if (duration >= 1000) { + (void) sleep(duration / 1000); + (void) usleep((duration % 1000) * 1000); + } else { + (void) usleep(duration * 1000); + } +} + +/** + * @} + */ + +/** + * @defgroup TimerInternal The Timer internal functions + * @ingroup Timers + * @{ + */ + +/** + * addTimerToList + * Send message to timer service to add the timer pointed by cprTimerPtr + * to the list. This routine is just sending IPC message to timer service + * but the actual addition is done by timer service using the addTimer function. + * This function is only called by CPR functions and is not visible to external + * applications. + * @param[in] cprTimerPtr - timer pointer + * @param[in] duration - timer duration in msec. + * @param[in] data - opaque data + * @return - CPR_SUCCESS or CPR_FAILURE + */ +static cprRC_t addTimerToList (cpr_timer_t *cprTimerPtr, uint32_t duration, void *data) +{ + + static const char fname[] = "addTimerToList"; + timer_ipc_t tmr_cmd = {0}; + timer_ipc_t tmr_rsp={0}; + + API_ENTER(); + + //CPR_INFO("%s: cprTimerptr=0x%x dur=%d user_data=%x\n", + // fname, cprTimerPtr, duration, data); + tmr_cmd.msg_type = TMR_CMD_ADD; + tmr_cmd.u.cmd.timer_ptr = cprTimerPtr; + tmr_cmd.u.cmd.user_data_ptr = data; + tmr_cmd.u.cmd.duration = duration; + +//CPR_INFO("%s:sending messge of type=%d\n", fname, tmr_cmd.msg_type); + /* simply post a request here to the timer service.*/ + if (client_sock != -1) { + if (sendto(client_sock, &tmr_cmd, sizeof(timer_ipc_t), 0, + (struct sockaddr *)&tmr_serv_addr, sizeof(tmr_serv_addr)) < 0) { + CPR_ERROR("Failed to tx IPC msg to timer service, errno = %s %s\n", + strerror(errno), fname); + API_RETURN(CPR_FAILURE); + } + + } else { + CPR_ERROR("can not make IPC connection, client_sock is invalid %s\n", fname); + API_RETURN(CPR_FAILURE); + } + + /* + * wait for the timer service to excute the request + * so that we get result of operation + */ + + if (recvfrom(client_sock, &tmr_rsp, sizeof(timer_ipc_t),0, NULL, NULL) < 0) { + //CPR_INFO("error in recving the result error=%s\n", strerror(errno)); + API_RETURN(CPR_FAILURE); + } else { + //CPR_INFO("received response from the timer result=%d\n", tmr_rsp.u.result); + API_RETURN(tmr_rsp.u.result); + } +} + + +/** + * addTimer + * + * Add a timer to the timer linked list. + * This function is only called by CPR functions and is not visible to external + * applications. + * + * @param[in] cprTimerPtr - pointer to the CPR timer structure + * @param[in] duration - how long before timer expires in milliseconds + * @param[in] data - information to be passed to callback function + * + * @return - CPR_SUCCESS or CPR_FAILURE + */ +static cprRC_t addTimer (cpr_timer_t *cprTimerPtr, uint32_t duration, void *data) +{ + static const char fname[] = "addTimer"; + timerBlk *timerList; + timerBlk *newTimerPtr; + + CPR_INFO("%s:adding timer=0x%x timerblk=%x\n", fname, + cprTimerPtr, cprTimerPtr->u.handlePtr); + + + /* Verify the timer has been initialized */ + newTimerPtr = (timerBlk *) cprTimerPtr->u.handlePtr; + if (newTimerPtr == NULL) { + CPR_ERROR("%s - Timer %s has not been initialized.\n", + fname, cprTimerPtr->name); + errno = EINVAL; + + return(CPR_FAILURE); + } + + /* Ensure this timer is not already running */ + if (newTimerPtr->timerActive) { + CPR_ERROR("%s - Timer %s is already active.\n", fname, cprTimerPtr->name); + errno = EAGAIN; + return(CPR_FAILURE); + + } + + /* Sanity tests passed, store the data the application passed in */ + newTimerPtr->duration = duration; + cprTimerPtr->data = data; + + /* + * Insert timer into the linked list. The timer code only + * decrements the first timer in the list to be efficient. + * Therefore, the timer at the top of the list is the timer + * that will expire first. Timers are added to the list + * in ascending order of time left before expiration and + * ticksLeft is calculated to be the difference between + * when the timer before them expires and when the newly + * inserted timer expires. + */ + + /* Check for insertion into an empty list */ + if (timerListHead == NULL) { + //CPR_INFO("no timer in the list case..\n"); + timerListHead = newTimerPtr; + } else { + + /* Insert timer into list */ + timerList = timerListHead; + while (timerList != NULL) { + + /* + * If the duration on this new timer are less than the + * timer in the list, insert this new timer before + * it in the list as it will expire first. In doing so + * the code must subtract the new timer's duration from + * the duration of the timer in the in the list to keep the deltas correct. + */ + if (newTimerPtr->duration < timerList->duration) { + //CPR_INFO("less than case..\n"); + timerList->duration -= newTimerPtr->duration; + newTimerPtr->next = timerList; + newTimerPtr->previous = timerList->previous; + if (newTimerPtr->previous) { + newTimerPtr->previous->next = newTimerPtr; + } + timerList->previous = newTimerPtr; + + /* Check for insertion at the head of list */ + if (timerListHead == timerList) { + //CPR_INFO("insert at the head case..\n"); + timerListHead = newTimerPtr; + } + break; + } else { + /* + * Else this new timer expires after the timer in + * the list. Therefore subtract the timer's duration + * from the new timer's duration. Since only the + * first timer is decremented all other timers added + * expiration must be calculated as a delta from the + * timer in front of them in the list. + */ + //CPR_INFO("greater than case..\n"); + newTimerPtr->duration -= timerList->duration; + + /* Check for insertion at the end of list */ + if (timerList->next == NULL) { + //CPR_INFO("insert at the end sub case..\n"); + newTimerPtr->previous = timerList; + timerList->next = newTimerPtr; + newTimerPtr->next = NULL; + break; + } + timerList = timerList->next; + } + } + } + + newTimerPtr->timerActive = TRUE; + return(CPR_SUCCESS); +} + + +/** + * removeTimerFromList + * Send message to timer service to remove the timer pointed by cprTimerPtr + * from the list. This routine is just sending IPC message to timer service + * and the actual removal is done by timer service using the removeTimer function.. + * This function is only called by CPR functions and is not visible to external + * applications. + * + * @param[in] cprTimerPtr - pointer to the timer to be removed from the list + * @return - CPR_SUCCESS or CPR_FAILURE + * + */ +static cprRC_t +removeTimerFromList (cpr_timer_t *cprTimerPtr) +{ + + static const char fname[] = "removeTimerFromList"; + timer_ipc_t tmr_cmd = {0}; + timer_ipc_t tmr_rsp = {0}; + + + API_ENTER(); + + //CPR_INFO("%s:remove timer from list=0x%x\n",fname, cprTimerPtr); + tmr_cmd.msg_type = TMR_CMD_REMOVE; + tmr_cmd.u.cmd.timer_ptr = cprTimerPtr; + + //CPR_INFO("sending messge of type=%d\n", tmr_cmd.msg_type); + + /* simply post a request here to the timer service.. */ + if (client_sock != -1) { + if (sendto(client_sock, &tmr_cmd, sizeof(timer_ipc_t), 0, + (struct sockaddr *)&tmr_serv_addr, sizeof(tmr_serv_addr)) < 0) { + CPR_ERROR("%s:failed to tx IPC Msg to timer service, errno = %s\n", + fname, strerror(errno)); + API_RETURN(CPR_FAILURE); + } + } else { + CPR_ERROR("%s:client_sock invalid, no IPC connection \n", fname); + API_RETURN(CPR_FAILURE); + } + + /* + * wait for the timer service to excute the request + * so that we get result of operation + */ + + if (recvfrom(client_sock, &tmr_rsp, sizeof(timer_ipc_t),0, NULL, NULL) < 0) { + //CPR_INFO("error in recving the result error=%s\n", strerror(errno)); + API_RETURN(CPR_FAILURE); + } else { + //CPR_INFO("received response from the timer result=%d\n", tmr_rsp.u.result); + API_RETURN(tmr_rsp.u.result); + } +} + + +/** + * removeTimer + * + * Remove a timer from the timer linked list. This function is only + * called by CPR functions and is not visible to applications. + * + * @param[in] cprTimerPtr - which timer to cancel + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +static cprRC_t +removeTimer (cpr_timer_t *cprTimerPtr) +{ + static const char fname[] = "removeTimer"; + timerBlk *timerList; + timerBlk *previousTimer; + timerBlk *nextTimer; + timerBlk *timerPtr; + + //CPR_INFO("removing timer..0x%x\n", cprTimerPtr); + + /* + * No need to sanitize the cprTimerPtr data as only + * internal CPR functions call us and they have already + * sanitized that data. In addition those functions + * have already grabbed the timer list mutex so no need + * to do that here. + */ + timerPtr = (timerBlk *) cprTimerPtr->u.handlePtr; + //CPR_INFO("%s: timer ptr=%x\n", fname, timerPtr); + + if (timerPtr != NULL) { + /* Walk the list looking for this timer to cancel. */ + timerList = timerListHead; + while (timerList != NULL) { + if (timerList->cprTimerPtr->cprTimerId == cprTimerPtr->cprTimerId) { + /* Removing only element in the list */ + if ((timerList->previous == NULL) && + (timerList->next == NULL)) { + timerListHead = NULL; + + /* Removing head of the list */ + } else if (timerList->previous == NULL) { + nextTimer = timerList->next; + nextTimer->previous = NULL; + timerListHead = nextTimer; + + /* Removing tail of the list */ + } else if (timerList->next == NULL) { + previousTimer = timerList->previous; + previousTimer->next = NULL; + + /* Removing from middle of the list */ + } else { + nextTimer = timerList->next; + previousTimer = timerList->previous; + previousTimer->next = nextTimer; + nextTimer->previous = previousTimer; + } + + /* Add time back to next timer in the list */ + if (timerList->next) { + timerList->next->duration += timerList->duration; + } + + /* + * Reset timer values + */ + timerList->next = NULL; + timerList->previous = NULL; + timerList->duration = -1; + timerList->timerActive = FALSE; + cprTimerPtr->data = NULL; + + return(CPR_SUCCESS); + + } + + /* Walk the list */ + timerList = timerList->next; + } + + /* + * Either the timer was not active or it was marked active, but not + * found on the timer list. If the timer was inactive then that is OK, + * but let user know about an active timer not found in the timer list. + */ + if ((timerPtr->next != NULL) || (timerPtr->previous != NULL)) { + CPR_ERROR("%s - Timer %s marked as active, " + "but was not found on the timer list.\n", + fname, cprTimerPtr->name); + timerPtr->next = NULL; + timerPtr->previous = NULL; + } + timerPtr->duration = -1; + timerPtr->cprTimerPtr->data = NULL; + timerPtr->timerActive = FALSE; + + return(CPR_SUCCESS); + + } + + /* Bad application! */ + CPR_ERROR("%s - Timer not initialized.\n", fname); + errno = EINVAL; + return(CPR_FAILURE); + +} + +/** + * @} + * @addtogroup TimerAPIs The Timer APIs + * @ingroup Timers + * @{ + */ + +/** + * cprCreateTimer + * + * @brief Initialize a timer + * + * The cprCreateTimer function is called to allow the OS to perform whatever + * work is needed to create a timer. The input name parameter is optional. If present, CPR assigns + * this name to the timer to assist in debugging. The callbackMsgQueue is the + * address of a message queue created with cprCreateMsgQueue. This is the + * queue where the timer expire message will be sent. + * So, when this timer expires a msg of type "applicationMsgId" will be sent to the msg queue + * "callbackMsgQueue" indicating that timer applicationTimerId has expired. + * + * @param[in] name - name of the timer + * @param[in] applicationTimerId - ID for this timer from the application's + * perspective + * @param[in] applicationMsgId - ID for syshdr->cmd when timer expire msg + * is sent + * @param[in] callBackMsgQueue - where to send a msg when this timer expires + * + * @return Timer handle or NULL if creation failed. + */ +cprTimer_t +cprCreateTimer (const char *name, + uint16_t applicationTimerId, + uint16_t applicationMsgId, + cprMsgQueue_t callBackMsgQueue) +{ + static const char fname[] = "cprCreateTimer"; + static uint32_t cprTimerId = 0; + cpr_timer_t *cprTimerPtr; + timerBlk *timerPtr; + + /* + * Malloc memory for a new timer. Need to + * malloc memory for the generic CPR view and + * one for the CNU specific version. + */ + cprTimerPtr = (cpr_timer_t *) cpr_malloc(sizeof(cpr_timer_t)); + timerPtr = (timerBlk *) cpr_malloc(sizeof(timerBlk)); + if ((cprTimerPtr != NULL) && (timerPtr != NULL)) { + /* Assign name (Optional) */ + cprTimerPtr->name = name; + + /* Set timer ids, msg id and callback msg queue (Mandatory) */ + cprTimerPtr->applicationTimerId = applicationTimerId; + cprTimerPtr->applicationMsgId = applicationMsgId; + cprTimerPtr->cprTimerId = cprTimerId++; + if (callBackMsgQueue == NULL) { + CPR_ERROR("%s - Callback msg queue for timer %s is NULL.\n", + fname, name); + cpr_free(timerPtr); + cpr_free(cprTimerPtr); + return NULL; + } + cprTimerPtr->callBackMsgQueue = callBackMsgQueue; + + /* + * Set remaining values in both structures to defaults + */ + timerPtr->next = NULL; + timerPtr->previous = NULL; + timerPtr->duration = -1; + timerPtr->timerActive = FALSE; + cprTimerPtr->data = NULL; + + /* + * TODO - It would be nice for CPR to keep a linked + * list of active timers for debugging purposes + * such as a show command or walking the list to ensure + * that an application does not attempt to create + * the same timer twice. + * + * TODO - It would be nice to initialize of pool of + * timers at init time and have this function just + * return a timer from the pool. Then when the + * timer expired or cancel the code would not free + * it, but just return it to the pool. + */ + timerPtr->cprTimerPtr = cprTimerPtr; + cprTimerPtr->u.handlePtr = timerPtr; + //CPR_INFO("cprTimerCreate: timer_t=%x blk=%x\n",cprTimerPtr, timerPtr); + + return cprTimerPtr; + } + + /* + * If we get here there has been a malloc failure. + */ + if (timerPtr) { + cpr_free(timerPtr); + } + if (cprTimerPtr) { + cpr_free(cprTimerPtr); + } + + /* Malloc failed */ + CPR_ERROR("%s - Malloc for timer %s failed.\n", fname, name); + errno = ENOMEM; + return NULL; +} + + +/** + * cprStartTimer + * + * @brief Start a system timer + * + * The cprStartTimer function starts a previously created timer referenced by + * the parameter timer. CPR timer granularity is 10ms. The "timer" input + * parameter is the handle returned from a previous successful call to + * cprCreateTimer. + * + * @param[in] timer - which timer to start + * @param[in] duration - how long before timer expires in milliseconds + * @param[in] data - information to be passed to callback function + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t +cprStartTimer (cprTimer_t timer, + uint32_t duration, + void *data) +{ + static const char fname[] = "cprStartTimer"; + cpr_timer_t *cprTimerPtr; + + cprTimerPtr = (cpr_timer_t *) timer; + if (cprTimerPtr != NULL) { + /* add timer to the list */ + return addTimerToList(cprTimerPtr, duration, data); + } + + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; +} + + +/** + * cprIsTimerRunning + * + * @brief Determine if a timer is active + * + * This function determines whether the passed in timer is currently active. The + * "timer" parameter is the handle returned from a previous successful call to + * cprCreateTimer. + * + * @param[in] timer - which timer to check + * + * @return True is timer is active, False otherwise + */ +boolean +cprIsTimerRunning (cprTimer_t timer) +{ + static const char fname[] = "cprIsTimerRunning"; + cpr_timer_t *cprTimerPtr; + timerBlk *timerPtr; + + //CPR_INFO("istimerrunning(): timer=0x%x\n", timer); + + cprTimerPtr = (cpr_timer_t *) timer; + if (cprTimerPtr != NULL) { + timerPtr = (timerBlk *) cprTimerPtr->u.handlePtr; + if (timerPtr == NULL) { + CPR_ERROR("%s - Timer %s has not been initialized.\n", + fname, cprTimerPtr->name); + errno = EINVAL; + return FALSE; + } + + if (timerPtr->timerActive) { + return TRUE; + } + } else { + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + } + + return FALSE; +} + + +/** + * cprCancelTimer + * + * @brief Cancels a running timer + * + * The cprCancelTimer function cancels a previously started timer referenced by + * the parameter timer. + * + * @param[in] timer - which timer to cancel + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t +cprCancelTimer (cprTimer_t timer) +{ + static const char fname[] = "cprCancelTimer"; + timerBlk *timerPtr; + cpr_timer_t *cprTimerPtr; + cprRC_t rc = CPR_SUCCESS; + + //CPR_INFO("cprCancelTimer: timer ptr=%x\n", timer); + + cprTimerPtr = (cpr_timer_t *) timer; + if (cprTimerPtr != NULL) { + timerPtr = (timerBlk *) cprTimerPtr->u.handlePtr; + if (timerPtr == NULL) { + CPR_ERROR("%s - Timer %s has not been initialized.\n", + fname, cprTimerPtr->name); + errno = EINVAL; + return CPR_FAILURE; + } + + /* + * Ensure timer is active before trying to remove it. + * If already inactive then just return SUCCESS. + */ + if (timerPtr->timerActive) { + //CPR_INFO("removing timer from the list=%x\n", timerPtr); + rc = removeTimerFromList(timer); + } + return rc; + } + + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; +} + + +/** + * cprUpdateTimer + * + * @brief Updates the expiration time for a running timer + * + * The cprUpdateTimer function cancels a previously started timer referenced by + * the parameter timer and then restarts the same timer with the duration passed + * in. + * + * @param[in] timer - which timer to update + * @param[in] duration - how long before timer expires in milliseconds + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t +cprUpdateTimer (cprTimer_t timer, uint32_t duration) +{ + static const char fname[] = "cprUpdateTimer"; + cpr_timer_t *cprTimerPtr; + void *timerData; + + cprTimerPtr = (cpr_timer_t *) timer; + if (cprTimerPtr != NULL) { + /* Grab data before cancelling timer */ + timerData = cprTimerPtr->data; + } else { + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; + } + + if (cprCancelTimer(timer) == CPR_SUCCESS) { + if (cprStartTimer(timer, duration, timerData) == CPR_SUCCESS) { + return CPR_SUCCESS; + } else { + CPR_ERROR("%s - Failed to start timer %s\n", + fname, cprTimerPtr->name); + return CPR_FAILURE; + } + } + + CPR_ERROR("%s - Failed to cancel timer %s\n", fname, cprTimerPtr->name); + return CPR_FAILURE; +} + + +/** + * cprDestroyTimer + * + * @brief Destroys a timer. + * + * This function will cancel the timer and then destroy it. It sets + * all links to NULL and then frees the timer block. + * + * @param[in] timer - which timer to destroy + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t +cprDestroyTimer (cprTimer_t timer) +{ + static const char fname[] = "cprDestroyTimer"; + cpr_timer_t *cprTimerPtr; + cprRC_t rc; + + //CPR_INFO("cprDestroyTimer:destroying timer=%x\n", timer); + + cprTimerPtr = (cpr_timer_t *) timer; + if (cprTimerPtr != NULL) { + rc = cprCancelTimer(timer); + if (rc == CPR_SUCCESS) { + cprTimerPtr->cprTimerId = 0; + cpr_free(cprTimerPtr->u.handlePtr); + cpr_free(cprTimerPtr); + return CPR_SUCCESS; + } else { + CPR_ERROR("%s - Cancel of Timer %s failed.\n", + fname, cprTimerPtr->name); + return CPR_FAILURE; + } + } + + /* Bad application! */ + CPR_ERROR("%s - NULL pointer passed in.\n", fname); + errno = EINVAL; + return CPR_FAILURE; +} + +/** + * @} + * @addtogroup TimerInternal The Timer internal functions + * @ingroup Timers + * @{ + */ + +/** + * cpr_timer_pre_init + * + * @brief Initalize timer service and client IPC + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t cpr_timer_pre_init (void) +{ + static const char fname[] = "cpr_timer_pre_init"; + int32_t returnCode; + + /* start the timer service first */ + returnCode = (int32_t)pthread_create(&timerThreadId, NULL, timerThread, NULL); + if (returnCode == -1) { + CPR_ERROR("%s: Failed to create Timer Thread : %s\n", fname, strerror(errno)); + return CPR_FAILURE; + } + + /* + * wait some time so that timer service thread is up + * TBD:we should really implement wait on timerthread using condvar. + */ + cprSleep(1000); + + return CPR_SUCCESS; +} + + +/** + * cpr_timer_de_init + * + * @brief De-Initalize timer service and client IPC + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t cpr_timer_de_init(void) +{ + // close all sockets.. + close(client_sock); + close(serv_sock); + + + // destroy api mutex + pthread_mutex_destroy(&api_mutex); + + return CPR_SUCCESS; +} + + +/** + * Timer service thread + * + */ +/** + * timerThread + * + * @brief Timer service thread + * + * This is the start function for the timer server thread. + * + * @param[in] data - The data passed in (UNUSED) + * + * @return This function eventually starts an infinite loop on a "select". + */ +void *timerThread (void *data) +{ + static const char fname[] = "timerThread"; + + //CPR_INFO("timerThread:started..\n"); +#ifndef HOST +#ifndef PTHREAD_SET_NAME +#define PTHREAD_SET_NAME(s) do { } while (0) +#endif + PTHREAD_SET_NAME("CPR Timertask"); +#endif + + /* + * Increase the timer thread priority from default priority. + * This is required to make sure timers fire with reasonable precision. + * + * NOTE: always make sure the priority is higher than sip/gsm threads; + * otherwise, we must use mutex around the following while loop. + */ + (void) cprAdjustRelativeThreadPriority(TIMER_THREAD_RELATIVE_PRIORITY); + + /* get ready to listen for timer commands and service them */ + if (start_timer_service_loop() == CPR_FAILURE) { + CPR_ERROR("%s: timer service loop failed\n", fname); + } + + return NULL; +} + + +/** + * local_bind + * Function used to do a bind on the local socket. + * + * @param[in] sock - socket descriptor to bind + * @param[in] name - name to use for binding local socket + * @return 0 if success, -1 on error (errno will be set) + * + */ +static int local_bind (int sock, char *name) +{ + struct sockaddr_un addr; + + /* construct the address structure */ + addr.sun_family = AF_LOCAL; + sstrncpy(addr.sun_path, name, sizeof(addr.sun_path)); + /* make sure file doesn't already exist */ + unlink(addr.sun_path); + + return bind(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +/** + * select_sockets + * + * Set the socket descriptors to be used for reading. + * Only server side uses select. + * + * @return The server socket number + */ +static int select_sockets (void) +{ + FD_ZERO(&socks); + + FD_SET(serv_sock, &socks); + + return (serv_sock); +} + + +/** + * read_timer_cmd + * read message received on the IPC from the client + * the only messages are timer commands {add, remove} + * + * @return CPR_SUCCESS or CPR_FAILURE + */ +static cprRC_t read_timer_cmd () +{ + static const char fname[] = "read_timer_cmd"; + int rcvlen; + timer_ipc_t tmr_cmd ={0}; + cprRC_t ret = CPR_FAILURE; + + + + rcvlen =recvfrom(serv_sock, &tmr_cmd, sizeof(timer_ipc_t), 0, + NULL, NULL); + + if (rcvlen > 0) { + //CPR_INFO("got message type=%d\n", tmr_cmd.msg_type); + switch(tmr_cmd.msg_type) { + case TMR_CMD_ADD: + //CPR_INFO("request to add timer ptr=%x duration=%d datptr=%x\n", + // tmr_cmd.u.cmd.timer_ptr, tmr_cmd.u.cmd.duration, tmr_cmd.u.cmd.user_data_ptr); + + ret = addTimer(tmr_cmd.u.cmd.timer_ptr,tmr_cmd.u.cmd.duration, + (void *)tmr_cmd.u.cmd.user_data_ptr); + + break; + + case TMR_CMD_REMOVE: + //CPR_INFO("request to remove timer ptr=%x\n", tmr_cmd.u.cmd.timer_ptr); + ret = removeTimer(tmr_cmd.u.cmd.timer_ptr); + break; + + default: + CPR_ERROR("%s:invalid ipc command = %d\n", tmr_cmd.msg_type); + ret = CPR_FAILURE; + break; + } + } else { + CPR_ERROR("%s:while reading serv_sock err =%s: Closing Socket..Timers not operational !!! \n", + fname, strerror(errno)); + (void) close(serv_sock); + serv_sock = INVALID_SOCKET; + ret = CPR_FAILURE; + } + + /* send the result back */ + send_api_result(ret, &tmr_client_addr, sizeof(tmr_client_addr)); + + return (ret); + +} + +/** + * send_api_result back to client via a socket sendto operation + * @param[in] retVal - value of result + * @param[in] addr - address to send the result to + * @param[in] len - length of addr + */ +void send_api_result(cprRC_t retVal, struct sockaddr_un *addr, socklen_t len) +{ + static const char fname[] = "send_api_result"; + timer_ipc_t tmr_rsp = {0}; + + tmr_rsp.msg_type = TMR_RESULT; + tmr_rsp.u.result = retVal; + if (sendto(serv_sock, &tmr_rsp, sizeof(timer_ipc_t),0, (struct sockaddr *)addr, len) < 0) { + CPR_ERROR("%s: error in sending on serv_sock err=%s\n", fname, strerror(errno)); + } +} + +/** + * Start the timer service loop. + * Service loop waits for timer commands from timer clients processes them. + * Waiting is done by issuing a select call with a timeout. This is the main + * function that implements the timer functionality using the select call. + * timeout value = duration on the head of the timer list. + * @return CPR_SUCCESS or CPR_FAILURE + */ +cprRC_t start_timer_service_loop (void) +{ + static const char fname[] = "start_timer_service_loop"; + int lsock = -1; + struct timeval tv; + int ret; + boolean use_timeout; + + + /* initialize server and client addresses used for sending.*/ + cpr_set_sockun_addr((cpr_sockaddr_un_t *) &tmr_serv_addr, SERVER_PATH, getpid()); + cpr_set_sockun_addr((cpr_sockaddr_un_t *) &tmr_client_addr, CLIENT_PATH, getpid()); + + /* + * init mutex and cond var. + * these are used for making API synchronous etc.. + */ + if (pthread_mutex_init(&api_mutex, NULL) != 0) { + CPR_ERROR("%s: failed to initialize api_mutex err=%s\n", fname, + strerror(errno)); + return CPR_FAILURE; + } + + + /* open a unix datagram socket for client library */ + client_sock = socket(AF_LOCAL, SOCK_DGRAM, 0); + if (client_sock == INVALID_SOCKET) { + CPR_ERROR("%s:could not create client socket error=%s\n", fname, strerror(errno)); + return CPR_FAILURE; + } + + /* bind service name to the socket */ + if (local_bind(client_sock,tmr_client_addr.sun_path) < 0) { + CPR_ERROR("%s:could not bind local socket:error=%s\n", fname, strerror(errno)); + (void) close(client_sock); + client_sock = INVALID_SOCKET; + return CPR_FAILURE; + } + + /* open another unix datagram socket for timer service */ + serv_sock = socket(AF_LOCAL, SOCK_DGRAM, 0); + if (serv_sock == INVALID_SOCKET) { + CPR_ERROR("%s:could not create server socket error=%s\n", fname, strerror(errno)); + serv_sock = INVALID_SOCKET; + close(client_sock); + client_sock = INVALID_SOCKET; + return CPR_FAILURE; + } + + if (local_bind(serv_sock, tmr_serv_addr.sun_path) < 0) { + CPR_ERROR("%s:could not bind serv socket:error=%s\n", fname, strerror(errno)); + (void) close(serv_sock); + (void) close(client_sock); + client_sock = serv_sock = INVALID_SOCKET; + return CPR_FAILURE; + } + + + while (1) { + + lsock = select_sockets(); + + /* set the timer equal to duration on head */ + if (timerListHead != NULL) { + tv.tv_sec = (timerListHead->duration)/1000; + tv.tv_usec = (timerListHead->duration%1000)*1000; + //CPR_INFO("%s:time duration on head =%d sec:%d usec (or %d msec)\n", + // fname, tv.tv_sec, tv.tv_usec, + // timerListHead->duration); + use_timeout = TRUE; + } else { + //CPR_INFO("%s:no timer in the list.. will block until there is one\n", + // fname); + use_timeout = FALSE; + } + + ret = select(lsock + 1, &socks, NULL, NULL, (use_timeout == TRUE) ? &tv:NULL); + + if (ret == -1) { + CPR_ERROR("%s:error in select err=%s\n", fname, + strerror(errno)); + return(CPR_FAILURE); + } else if (ret == 0) { + /* + * this means the head timer has expired..there could be others + */ + timerListHead->duration = 0; + process_expired_timers(); + } else { + + if (FD_ISSET(serv_sock, &socks)) { + //CPR_INFO("Got something on serv_sock..\n"); + /* first reduce the duration of the head by current run time */ + if (timerListHead != NULL) { + //CPR_INFO("set head duration to =%d prev was= %d\n", + // tv.tv_sec * 1000 + (tv.tv_usec/1000), + // timerListHead->duration); + /* set the head with the remaining duration(tv) as indicated by select */ + timerListHead->duration = tv.tv_sec * 1000 + (tv.tv_usec/1000); + } + /* read the ipc message to remove or add a timer */ + (void) read_timer_cmd(); + } + } + } + +} + +/** + * + * Process the timers expired. Generally this is called when head timer + * has expired. + * @note we need to process the list as there could be + * other timers too in the list which have expired. + * + */ +void process_expired_timers() { + static const char fname[] = "process_expired_timer"; + cprCallBackTimerMsg_t *timerMsg; + void *syshdr; + boolean processingTimers; + + /* nothing to do if no timers running */ + if (timerListHead == NULL) { + return; + } + + /* nothing to do if head has not expired */ + if (timerListHead->duration > 0) { + return; + } + + + /* There are one or more expired timers on the list */ + processingTimers = TRUE; + while (processingTimers) { + if (timerListHead != NULL) { + /* + * Send msg to queue to indicate this timer has expired + */ + if (timerListHead->duration <= 0) { + timerMsg = (cprCallBackTimerMsg_t *) + cpr_malloc(sizeof(cprCallBackTimerMsg_t)); + if (timerMsg) { + timerMsg->expiredTimerName = + timerListHead->cprTimerPtr->name; + //CPR_INFO("%s: timer %s expired..\n",fname, + // timerMsg->expiredTimerName); + + timerMsg->expiredTimerId = + timerListHead->cprTimerPtr->applicationTimerId; + timerMsg->usrData = + timerListHead->cprTimerPtr->data; + syshdr = cprGetSysHeader(timerMsg); + if (syshdr) { + fillInSysHeader(syshdr, + timerListHead->cprTimerPtr->applicationMsgId, + sizeof(cprCallBackTimerMsg_t), timerMsg); + if (cprSendMessage(timerListHead->cprTimerPtr->callBackMsgQueue, + timerMsg, (void **) &syshdr) == CPR_FAILURE) { + cprReleaseSysHeader(syshdr); + cpr_free(timerMsg); + CPR_ERROR("%s - Call to cprSendMessage failed\n", fname); + CPR_ERROR("%s - Unable to send timer %s expiration msg\n", + fname, timerListHead->cprTimerPtr->name); + } + } else { + cpr_free(timerMsg); + CPR_ERROR("%s - Call to cprGetSysHeader failed\n", fname); + CPR_ERROR("%s - Unable to send timer %s expiration msg\n", + fname, timerListHead->cprTimerPtr->name); + } + } else { + CPR_ERROR("%s - Call to cpr_malloc failed\n", fname); + CPR_ERROR("%s - Unable to send timer %s expiration msg\n", + fname, timerListHead->cprTimerPtr->name); + } + (void) removeTimer(timerListHead->cprTimerPtr); + } else { + /* The rest of the timers on the list have not yet expired */ + processingTimers = FALSE; + } + } else { + /* The timer list is now empty */ + processingTimers = FALSE; + } + } /* still more to process */ +} + +/** + * @} + */ diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_tst.c b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_tst.c new file mode 100644 index 000000000000..5fb7787ec530 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_tst.c @@ -0,0 +1,61 @@ +/* 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 "cpr.h" +#include "cpr_stdlib.h" +#include "cpr_linux_tst.h" +#include "cpr_errno.h" +#include +#include "plat_api.h" + +void +err_exit (void) +{ + *(volatile int *) 0xdeadbeef = 0x12345678; +} + + +/* main process entry function */ +int +main (int argc, char **argv, char **env) +{ + int ret; + char *q; + + buginf("CPR test...\n"); + //err_exit(); + + cprTestCmd(argc, argv); + + return 0; +} + + +long +strlib_mem_used (void) +{ + return (0); +} + +#define LOG_MAX 255 + +int +debugif_printf (const char *_format, ...) +{ + char fmt_buf[LOG_MAX + 1]; + int rc; + va_list ap; + + va_start(ap, _format); + rc = vsnprintf(fmt_buf, LOG_MAX, _format, ap); + va_end(ap); + + if (rc <= 0) { + return rc; + } + printf("%s", fmt_buf); + + return rc; +} + diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_tst.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_tst.h new file mode 100644 index 000000000000..11b9e726be17 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_tst.h @@ -0,0 +1,13 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_TST_H_ +#define _CPR_ANDROID_TST_H_ + +#define APP_NAME "SIPCC-" +#define DEB_F_PREFIX APP_NAME"%s: %s: " +#define CPR "CPR" +#define DEB_F_PREFIX_ARGS(msg_name, func_name) msg_name, func_name + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_types.h b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_types.h new file mode 100644 index 000000000000..699461c65d29 --- /dev/null +++ b/media/webrtc/signaling/src/sipcc/cpr/android/cpr_android_types.h @@ -0,0 +1,157 @@ +/* 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/. */ + +#ifndef _CPR_ANDROID_TYPES_H_ +#define _CPR_ANDROID_TYPES_H_ + +#include "sys/types.h" +#include "stddef.h" +#include "inttypes.h" + +/** + * @typedef boolean + * + * Define boolean as an unsigned byte + * + * @note There are differences within TNP header files + * @li curses.h: bool => char + * @li types.h: boolean_t => enum + * @li dki_lock.h: bool_t => int + */ +typedef uint8_t boolean; + +/* + * Define size_t + * defined in numerous header files + */ +/* DONE (sys/types.h => unsigned int) */ + +/* + * Define ssize_t + */ +/* DONE (sys/types.h => int) */ + +/* + * Define MIN/MAX + * defined in param.h + * + * The GNU versions of the MAX and MIN macros do two things better than + * the old versions: + * 1. they are more optimal as they only evaluate a & b once by creating a + * a variable of each type on the local stack. + * 2. they fix potential errors due to side-effects where a and b were + * evaluated twice, i.e. MIN(i++,j++) + * + * @note b could be cast to a's type, to help with usage where the code + * compares signed and unsigned types. + */ +#ifndef MIN +#ifdef __GNUC__ +#define MIN(a,b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; }) +#else +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#endif + +#ifndef MAX +#ifdef __GNUC__ +#define MAX(a,b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a > _b ? _a : _b; }) +#else +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#endif + + +/* + * Define NULL + * defined in numerous header files + */ +/* DONE (stddef.h) */ + +/** + * @def NUL + * + * Define NUL for string termination + */ +#ifndef NUL +#define NUL '\0' +#endif + +/** + * @def RESTRICT + * + * If suppoprting the ISO/IEC 9899:1999 standard, + * use the '__restrict' keyword + */ +#if defined(_POSIX_C_SOURCE) && defined(__GNUC__) +#define RESTRICT __restrict +#else +#define RESTRICT +#endif + +/** + * @def CONST + * + * Define CONST as @c const, if supported + */ +#define CONST const + +/** + * @def INLINE + * + * Define the appropriate setting for inlining functions + */ +#ifdef __STRICT_ANSI__ +#define INLINE +#else +#define INLINE __inline__ +#endif + +/** + * __BEGIN_DECLS and __END_DECLS + * + * Define macros for compilation by C++ compiler + */ +#ifndef __BEGIN_DECLS +#ifdef __cplusplus +#define __BEGIN_DECLS extern "C" { +#else +#define __BEGIN_DECLS +#endif +#endif + +#ifndef __END_DECLS +#ifdef __cplusplus +#define __END_DECLS } +#else +#define __END_DECLS +#endif +#endif + +/** + * Define TRUE/FALSE + * defined in several header files + */ +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +/* + * Define offsetof + */ +/* DONE (stddef.h) */ + +/** + * @def FIELDOFFSET(struct name, field name) + * + * Macro to generate offset from a given field in a structure + */ +#define FIELDOFFSET(struct_name, field_name) (size_t)(&(((struct_name *)0)->field_name)) + + +#endif diff --git a/media/webrtc/signaling/src/sipcc/cpr/linux/cpr_linux_ipc.c b/media/webrtc/signaling/src/sipcc/cpr/linux/cpr_linux_ipc.c index ceaf10c02896..e02207a92971 100644 --- a/media/webrtc/signaling/src/sipcc/cpr/linux/cpr_linux_ipc.c +++ b/media/webrtc/signaling/src/sipcc/cpr/linux/cpr_linux_ipc.c @@ -597,17 +597,20 @@ cprSendMessage (cprMsgQueue_t msgQueue, void *msg, void **ppUserData) */ if (msgq->extendedQDepth < msgq->maxExtendedQDepth) { rc = cprPostExtendedQMsg(msgq, msg, ppUserData); - + // do under lock to avoid races + if (rc == CPR_MSGQ_POST_SUCCESS) { + cprPegSendMessageStats(msgq, numAttempts); + } else { + msgq->sendErrors++; + } (void) pthread_mutex_unlock(&msgq->mutex); if (rc == CPR_MSGQ_POST_SUCCESS) { - cprPegSendMessageStats(msgq, numAttempts); return CPR_SUCCESS; } else { CPR_ERROR(error_str, fname, msgq->name, "no memory"); - msgq->sendErrors++; return CPR_FAILURE; } } diff --git a/media/webrtc/signaling/test/FakeMediaStreams.h b/media/webrtc/signaling/test/FakeMediaStreams.h index 2f1ec88be035..8c62e399984e 100644 --- a/media/webrtc/signaling/test/FakeMediaStreams.h +++ b/media/webrtc/signaling/test/FakeMediaStreams.h @@ -48,15 +48,17 @@ public: // Note: only one listener supported class Fake_MediaStream { -public: - Fake_MediaStream () : mListeners() {} + public: + Fake_MediaStream () : mListeners(), mMutex("Fake MediaStream") {} virtual ~Fake_MediaStream() { Stop(); } void AddListener(Fake_MediaStreamListener *aListener) { + mozilla::MutexAutoLock lock(mMutex); mListeners.insert(aListener); } void RemoveListener(Fake_MediaStreamListener *aListener) { + mozilla::MutexAutoLock lock(mMutex); mListeners.erase(aListener); } @@ -67,8 +69,12 @@ public: virtual void Periodic() {} + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStream); + protected: std::set mListeners; + mozilla::Mutex mMutex; // Lock to prevent the listener list from being modified while + // executing Periodic(). }; class Fake_MediaPeriodic : public nsITimerCallback { @@ -205,7 +211,7 @@ public: void SetHintContents(uint32_t aHintContents) { mHintContents = aHintContents; } private: - Fake_MediaStream *mMediaStream; + nsRefPtr mMediaStream; // tells the SDP generator about whether this // MediaStream probably has audio and/or video diff --git a/media/webrtc/signaling/test/FakeMediaStreamsImpl.h b/media/webrtc/signaling/test/FakeMediaStreamsImpl.h index a6187ee0849c..d69791b8c7f4 100644 --- a/media/webrtc/signaling/test/FakeMediaStreamsImpl.h +++ b/media/webrtc/signaling/test/FakeMediaStreamsImpl.h @@ -38,6 +38,7 @@ nsresult Fake_SourceMediaStream::Start() { } nsresult Fake_SourceMediaStream::Stop() { + mozilla::MutexAutoLock lock(mMutex); if (mTimer) mTimer->Cancel(); mPeriodic->Detach(); @@ -45,6 +46,7 @@ nsresult Fake_SourceMediaStream::Stop() { } void Fake_SourceMediaStream::Periodic() { + mozilla::MutexAutoLock lock(mMutex); // Pull more audio-samples iff pulling is enabled // and we are not asked by the signaling agent to stop //pulling data. @@ -69,6 +71,10 @@ nsresult Fake_MediaStreamBase::Start() { } nsresult Fake_MediaStreamBase::Stop() { + // Lock the mutex so that we know that after this + // has returned, periodic will not be firing again + // and so it's safe to destruct. + mozilla::MutexAutoLock lock(mMutex); mTimer->Cancel(); return NS_OK; @@ -76,6 +82,7 @@ nsresult Fake_MediaStreamBase::Stop() { // Fake_AudioStreamSource void Fake_AudioStreamSource::Periodic() { + mozilla::MutexAutoLock lock(mMutex); //Are we asked to stop pumping audio samples ? if(mStop) { return; diff --git a/media/webrtc/signaling/test/signaling_unittests.cpp b/media/webrtc/signaling/test/signaling_unittests.cpp index 7735243b64ad..30eb3d814a83 100644 --- a/media/webrtc/signaling/test/signaling_unittests.cpp +++ b/media/webrtc/signaling/test/signaling_unittests.cpp @@ -297,20 +297,20 @@ TestObserver::OnAddStream(nsIDOMMediaStream *stream, const char *type) nsDOMMediaStream *ms = static_cast(stream); - cout << "OnAddStream called hints=" << ms->GetHintContents() << endl; - state = stateSuccess; + cout << "OnAddStream called hints=" << ms->GetHintContents() << " type=" << type << " thread=" << + PR_GetCurrentThread() << endl ; + onAddStreamCalled = true; + streams.push_back(ms); + // We know that the media stream is secretly a Fake_SourceMediaStream, // so now we can start it pulling from us Fake_SourceMediaStream *fs = static_cast(ms->GetStream()); - nsresult ret; test_utils->sts_target()->Dispatch( - WrapRunnableRet(fs, &Fake_SourceMediaStream::Start, &ret), - NS_DISPATCH_SYNC); - - streams.push_back(ms); + WrapRunnable(fs, &Fake_SourceMediaStream::Start), + NS_DISPATCH_NORMAL); return NS_OK; } @@ -475,14 +475,15 @@ class ParsedSDP { class SignalingAgent { public: - SignalingAgent() { - Init(); - } + SignalingAgent() : pc(nullptr) {} + ~SignalingAgent() { - Close(); + pc->GetMainThread()->Dispatch( + WrapRunnable(this, &SignalingAgent::Close), + NS_DISPATCH_SYNC); } - void Init() + void Init(nsCOMPtr thread) { size_t found = 2; ASSERT_TRUE(found > 0); @@ -493,7 +494,7 @@ class SignalingAgent { pObserver = new TestObserver(pc); ASSERT_TRUE(pObserver); - ASSERT_EQ(pc->Initialize(pObserver, nullptr, nullptr), NS_OK); + ASSERT_EQ(pc->Initialize(pObserver, nullptr, thread), NS_OK); ASSERT_TRUE_WAIT(sipcc_state() == sipcc::PeerConnectionImpl::kStarted, kDefaultTimeout); @@ -520,7 +521,10 @@ class SignalingAgent { void Close() { cout << "Close" << endl; + pc->Close(false); + pc = nullptr; + // Shutdown is synchronous evidently. // ASSERT_TRUE(pObserver->WaitForObserverCall()); // ASSERT_EQ(pc->sipcc_state(), sipcc::PeerConnectionInterface::kIdle); @@ -794,6 +798,24 @@ class SignalingEnvironment : public ::testing::Environment { class SignalingTest : public ::testing::Test { public: + static void SetUpTestCase() { + nsIThread *thread; + + nsresult rv = NS_NewThread(&thread); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + gThread = thread; + } + + void SetUp() { + a1_.Init(gThread); + a2_.Init(gThread); + } + + static void TearDownTestCase() { + gThread = nullptr; + } + void CreateOffer(sipcc::MediaConstraints& constraints, uint32_t offerFlags, uint32_t sdpCheck) { a1_.CreateOffer(constraints, offerFlags, sdpCheck); @@ -882,10 +904,13 @@ public: } protected: + static nsCOMPtr gThread; SignalingAgent a1_; // Canonically "caller" SignalingAgent a2_; // Canonically "callee" }; +nsCOMPtr SignalingTest::gThread; + TEST_F(SignalingTest, JustInit) { @@ -1232,6 +1257,7 @@ TEST_F(SignalingTest, FullCallTrickle) OfferAnswerTrickle(constraints, constraints, SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV); + std::cerr << "ICE handshake completed" << std::endl; PR_Sleep(kDefaultTimeout * 2); // Wait for some data to get written a1_.CloseSendStreams(); diff --git a/media/webrtc/trunk/src/video_engine/vie_impl.cc b/media/webrtc/trunk/src/video_engine/vie_impl.cc index 8805cd62cfe4..ef39ff1056f1 100644 --- a/media/webrtc/trunk/src/video_engine/vie_impl.cc +++ b/media/webrtc/trunk/src/video_engine/vie_impl.cc @@ -216,11 +216,13 @@ int VideoEngine::SetAndroidObjects(void* javaVM, void* javaContext) { "Could not set capture Android VM"); return -1; } +#ifdef WEBRTC_INCLUDE_INTERNAL_VIDEO_RENDER if (SetRenderAndroidVM(javaVM) != 0) { WEBRTC_TRACE(kTraceError, kTraceVideo, g_vie_active_instance_counter, "Could not set render Android VM"); return -1; } +#endif return 0; #else WEBRTC_TRACE(kTraceError, kTraceVideo, g_vie_active_instance_counter, diff --git a/memory/replace/dmd/DMD.cpp b/memory/replace/dmd/DMD.cpp index 16a516524e3e..85be93a3563d 100644 --- a/memory/replace/dmd/DMD.cpp +++ b/memory/replace/dmd/DMD.cpp @@ -1311,6 +1311,22 @@ OptionLong(const char* aArg, const char* aOptionName, long aMin, long aMax, static const size_t gMaxSampleBelowSize = 100 * 1000 * 1000; // bytes +// Default to sampling with a sample-below size that's a prime number close to +// 4096. +// +// Using a sample-below size ~= 4096 is much faster than using a sample-below +// size of 1, and it's not much less accurate in practice, so it's a reasonable +// default. +// +// Using a prime sample-below size makes our sampling more random. If we used +// instead a sample-below size of 4096, for example, then if all our allocation +// sizes were even (which they likely are, due to how jemalloc rounds up), our +// alloc counter would take on only even values. +// +// In contrast, using a prime sample-below size lets us explore all possible +// values of the alloc counter. +static const size_t gDefaultSampleBelowSize = 4093; + static void BadArg(const char* aArg) { @@ -1324,8 +1340,9 @@ BadArg(const char* aArg) StatusMsg(" enables it with non-default options.\n"); StatusMsg("\n"); StatusMsg("The following options are allowed; defaults are shown in [].\n"); - StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [1]\n", - int(gMaxSampleBelowSize)); + StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n" + " (prime numbers recommended).\n", + int(gMaxSampleBelowSize), int(gDefaultSampleBelowSize)); StatusMsg(" --mode= Which mode to run in? [normal]\n"); StatusMsg("\n"); exit(1); @@ -1344,7 +1361,7 @@ Init(const malloc_table_t* aMallocTable) // Set defaults of things that can be affected by the $DMD env var. gMode = Normal; - gSampleBelowSize = 1; + gSampleBelowSize = gDefaultSampleBelowSize; // DMD is controlled by the |DMD| environment variable. // - If it's unset or empty or "0", DMD doesn't run. @@ -1737,7 +1754,8 @@ Dump(Writer aWriter) size_t totalUsableSize = unreportedUsableSize + reportedUsableSize; WriteTitle("Invocation\n"); - W("$DMD = '%s'\n\n", gDMDEnvVar); + W("$DMD = '%s'\n", gDMDEnvVar); + W("Sample-below size = %lld\n\n", (long long)(gSampleBelowSize)); PrintSortedGroups(aWriter, "Double-reported", "double-reported", *gDoubleReportBlockGroupTable, kNoSize, kNoSize); @@ -1807,6 +1825,9 @@ RunTestMode(FILE* fp) { Writer writer(FpWrite, fp); + // The first part of this test requires sampling to be disabled. + gSampleBelowSize = 1; + // 0th Dump. Zero for everything. Dump(writer); diff --git a/memory/replace/dmd/test-expected.dmd b/memory/replace/dmd/test-expected.dmd index b455e74e05d6..bbd8815c5079 100644 --- a/memory/replace/dmd/test-expected.dmd +++ b/memory/replace/dmd/test-expected.dmd @@ -3,6 +3,7 @@ Invocation ------------------------------------------------------------------ $DMD = '--mode=test' +Sample-below size = 1 ------------------------------------------------------------------ Double-reported blocks @@ -35,6 +36,7 @@ Invocation ------------------------------------------------------------------ $DMD = '--mode=test' +Sample-below size = 1 ------------------------------------------------------------------ Double-reported blocks @@ -204,6 +206,7 @@ Invocation ------------------------------------------------------------------ $DMD = '--mode=test' +Sample-below size = 1 ------------------------------------------------------------------ Double-reported blocks @@ -286,6 +289,7 @@ Invocation ------------------------------------------------------------------ $DMD = '--mode=test' +Sample-below size = 128 ------------------------------------------------------------------ Double-reported blocks diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 59a31bd2eb29..922ef1fe2369 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -631,6 +631,7 @@ pref("ui.scrolling.min_scrollable_distance", -1); // Enable accessibility mode if platform accessibility is enabled. pref("accessibility.accessfu.activate", 2); +pref("accessibility.accessfu.quicknav_modes", "Link,Heading,FormElement,ListItem"); // Mobile manages state by autodetection pref("network.manage-offline-status", true); diff --git a/mobile/android/base/AwesomeBarTabs.java b/mobile/android/base/AwesomeBarTabs.java index c12fce2bc083..434da0c91204 100644 --- a/mobile/android/base/AwesomeBarTabs.java +++ b/mobile/android/base/AwesomeBarTabs.java @@ -128,7 +128,7 @@ public class AwesomeBarTabs extends TabHost { mListTouchListener = new View.OnTouchListener() { public boolean onTouch(View view, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) hideSoftInput(view); return false; } @@ -156,10 +156,12 @@ public class AwesomeBarTabs extends TabHost { public void onPageSelected(int position) { tabWidget.setCurrentTab(position); styleSelectedTab(); + hideSoftInput(mViewPager); } }); for (int i = 0; i < mTabs.length; i++) { + mTabs[i].setListTouchListener(mListTouchListener); addAwesomeTab(mTabs[i].getTag(), mTabs[i].getTitleStringId(), i); diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 02cc86dadf82..43f075c44f02 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -17,7 +17,7 @@ import org.mozilla.gecko.ui.PanZoomController; import org.mozilla.gecko.ui.PanZoomTarget; import org.mozilla.gecko.util.EventDispatcher; import org.mozilla.gecko.util.FloatUtils; -import org.mozilla.gecko.util.GeckoEventResponder; +import org.mozilla.gecko.util.GeckoEventListener; import org.json.JSONException; import org.json.JSONObject; @@ -33,7 +33,7 @@ import java.util.HashMap; import java.util.Map; public class GeckoLayerClient - implements GeckoEventResponder, LayerView.Listener, PanZoomTarget + implements GeckoEventListener, LayerView.Listener, PanZoomTarget { private static final String LOGTAG = "GeckoLayerClient"; @@ -45,7 +45,6 @@ public class GeckoLayerClient private IntSize mScreenSize; private IntSize mWindowSize; private DisplayPortMetrics mDisplayPort; - private DisplayPortMetrics mReturnDisplayPort; private boolean mRecordDrawTimes; private final DrawTimingQueue mDrawTimingQueue; @@ -465,23 +464,6 @@ public class GeckoLayerClient } } - /** Implementation of GeckoEventResponder. */ - public String getResponse() { - // We are responding to the events handled in handleMessage() above with the - // display port we calculated. Different messages will generate different - // display ports and put them in mReturnDisplayPort, so we just return that. - // Note that mReturnDisplayPort is always touched on the Gecko thread, so - // no synchronization is needed for it. - if (mReturnDisplayPort == null) { - return ""; - } - try { - return mReturnDisplayPort.toJSON(); - } finally { - mReturnDisplayPort = null; - } - } - void setZoomConstraints(ZoomConstraints constraints) { mZoomConstraints = constraints; } diff --git a/mobile/android/base/gfx/Layer.java b/mobile/android/base/gfx/Layer.java index dde603eaa427..d195fd05c0ac 100644 --- a/mobile/android/base/gfx/Layer.java +++ b/mobile/android/base/gfx/Layer.java @@ -22,7 +22,6 @@ public abstract class Layer { protected Rect mPosition; protected float mResolution; - protected boolean mUsesDefaultProgram = true; public Layer() { this(null); @@ -68,15 +67,6 @@ public abstract class Layer { return RectUtils.scale(new RectF(mPosition), context.zoomFactor / mResolution); } - /** - * Returns the region of the layer that is considered valid. The default - * implementation of this will return the bounds of the layer, but this - * may be overridden. - */ - public Region getValidRegion(RenderContext context) { - return new Region(RectUtils.round(getBounds(context))); - } - /** * Call this before modifying the layer. Note that, for TileLayers, "modifying the layer" * includes altering the underlying CairoImage in any way. Thus you must call this function @@ -133,10 +123,6 @@ public abstract class Layer { mNewResolution = newResolution; } - public boolean usesDefaultProgram() { - return mUsesDefaultProgram; - } - /** * Subclasses may override this method to perform custom layer updates. This will be called * with the transaction lock held. Subclass implementations of this method must call the diff --git a/mobile/android/base/gfx/LayerRenderer.java b/mobile/android/base/gfx/LayerRenderer.java index bf768cd53678..54852b7f9be6 100644 --- a/mobile/android/base/gfx/LayerRenderer.java +++ b/mobile/android/base/gfx/LayerRenderer.java @@ -576,13 +576,7 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener { /* Draw any extra layers that were added (likely plugins) */ if (mExtraLayers.size() > 0) { for (Layer layer : mExtraLayers) { - if (!layer.usesDefaultProgram()) - deactivateDefaultProgram(); - layer.draw(mPageContext); - - if (!layer.usesDefaultProgram()) - activateDefaultProgram(); } } diff --git a/mobile/android/base/tests/testBookmarksTab.java.in b/mobile/android/base/tests/testBookmarksTab.java.in index 81c811fd4f79..563ee4b745cf 100644 --- a/mobile/android/base/tests/testBookmarksTab.java.in +++ b/mobile/android/base/tests/testBookmarksTab.java.in @@ -59,9 +59,6 @@ public class testBookmarksTab extends BaseTest { View child; list = getBookmarksList(); - // Clear VKB so that list is not obscured - mActions.sendSpecialKey(Actions.SpecialKey.BACK); - mSolo.waitForText(url); mAsserter.ok(list != null, "checking that bookmarks list exists", list.toString()); @@ -107,8 +104,6 @@ public class testBookmarksTab extends BaseTest { private void testContextMenu(String url) { list = getBookmarksList(); - // Clear VKB so that list is not obscured - mActions.sendSpecialKey(Actions.SpecialKey.BACK); mSolo.waitForText(url); // wait for the bookmarks list to be populated diff --git a/mobile/android/base/tests/testHistoryTab.java.in b/mobile/android/base/tests/testHistoryTab.java.in index c6937c2c8ea8..2ade9585d66e 100644 --- a/mobile/android/base/tests/testHistoryTab.java.in +++ b/mobile/android/base/tests/testHistoryTab.java.in @@ -32,7 +32,7 @@ import java.util.GregorianCalendar; public class testHistoryTab extends PixelTest { private static final String ABOUT_HOME_URL = "about:home"; private static final String OPEN_NEW_TAB = "Open in New Tab"; - private static final int WAIT_FOR_CHILD_TIMEOUT = 2000; + private static final int WAIT_FOR_CHILD_TIMEOUT = 4000; private static ListView listview = null; private String[] bookmarks = new String[] { "http://mochi.test:8888/tests/robocop/robocop_blank_01.html" @@ -61,8 +61,36 @@ public class testHistoryTab extends PixelTest { loadAndPaint(url); mActions.sendSpecialKey(Actions.SpecialKey.MENU); - mSolo.waitForText("Bookmark"); - mSolo.clickOnText("Bookmark"); + mSolo.waitForText("Settings"); + + // On ICS+ phones, there is no button labeled "Bookmarks" + // instead we have to just dig through every button on the screen + ArrayList images = mSolo.getCurrentViews(); + for (int i = 0; i < images.size(); i++) { + final View view = images.get(i); + boolean found = false; + found = "Bookmark".equals(view.getContentDescription()); + + // on older android versions, try looking at the button's text + if (!found) { + if (view instanceof TextView) { + found = "Bookmark".equals(((TextView)view).getText()); + } + } + + if (found) { + int[] xy = new int[2]; + view.getLocationOnScreen(xy); + + final int viewWidth = view.getWidth(); + final int viewHeight = view.getHeight(); + final float x = xy[0] + (viewWidth / 2.0f); + float y = xy[1] + (viewHeight / 2.0f); + + mSolo.clickOnScreen(x, y); + } + } + mAsserter.is(mSolo.waitForText("Bookmark added"), true, "bookmark added successfully"); testList(url); @@ -76,7 +104,7 @@ public class testHistoryTab extends PixelTest { mActions.sendSpecialKey(Actions.SpecialKey.BACK); mSolo.waitForText(url); - mAsserter.ok(listview != null, "checking that history list exists", listview.toString()); + mAsserter.isnot(listview, null, "checking that history list exists"); mAsserter.is(listview.getChildCount(), 3, "history list has 3 children"); @@ -132,8 +160,6 @@ public class testHistoryTab extends PixelTest { private void testContextMenu(String url) { listview = getHistoryList(); - // clear VKB - mActions.sendSpecialKey(Actions.SpecialKey.BACK); mSolo.waitForText(url); // wait for the history list to be populated @@ -186,8 +212,7 @@ public class testHistoryTab extends PixelTest { private void testClick(String url) { listview = getHistoryList(); - // clear VKB - mActions.sendSpecialKey(Actions.SpecialKey.BACK); + mSolo.waitForText(url); View child = listview.getChildAt(0); @@ -218,11 +243,10 @@ public class testHistoryTab extends PixelTest { Activity awesomeBarActivity = clickOnAwesomeBar(); mSolo.clickOnText("History"); - final ArrayList views = mSolo.getCurrentListViews(); - listview = null; boolean success = waitForTest(new BooleanTest() { public boolean test() { + final ArrayList views = mSolo.getCurrentListViews(); for (ListView view : views) { if (view.getTag() == "history") { listview = view; diff --git a/mobile/android/base/ui/Axis.java b/mobile/android/base/ui/Axis.java index 06ac81dde85f..f2a1d0a319ea 100644 --- a/mobile/android/base/ui/Axis.java +++ b/mobile/android/base/ui/Axis.java @@ -343,8 +343,9 @@ abstract class Axis { // if overscroll is disabled and we're trying to overscroll, reset the displacement // to remove any excess. Using getExcess alone isn't enough here since it relies on - // getOverscroll which doesn't take into account any new displacment being applied - if (getOverScrollMode() == View.OVER_SCROLL_NEVER) { + // getOverscroll which doesn't take into account any new displacment being applied. + // If we using a subscroller, we don't want to alter the scrolling being done + if (getOverScrollMode() == View.OVER_SCROLL_NEVER && !mSubscroller.scrolling()) { if (mDisplacement + getOrigin() < getPageStart()) { mDisplacement = getPageStart() - getOrigin(); stopFling(); diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index aeb72445328c..d5b29f07ccb7 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -411,6 +411,12 @@ var BrowserApp = { aTarget.pause(); }); + NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.showControls"), + NativeWindow.contextmenus.mediaContext("media-hidingcontrols"), + function(aTarget) { + aTarget.setAttribute("controls", true); + }); + NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.shareMedia"), NativeWindow.contextmenus.SelectorContext("video"), function(aTarget) { @@ -1457,6 +1463,9 @@ var NativeWindow = { return true; if (!paused && aMode == "media-playing") return true; + let controls = aElt.controls; + if (!controls && aMode == "media-hidingcontrols") + return true; } return false; } diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties index 330e3502f8ca..4afa3a656ceb 100644 --- a/mobile/android/locales/en-US/chrome/browser.properties +++ b/mobile/android/locales/en-US/chrome/browser.properties @@ -231,6 +231,7 @@ contextmenu.addSearchEngine=Add Search Engine contextmenu.playMedia=Play contextmenu.pauseMedia=Pause contextmenu.shareMedia=Share Video +contextmenu.showControls=Show controls contextmenu.copy=Copy contextmenu.copyAll=Copy All diff --git a/netwerk/sctp/src/user_recv_thread.c b/netwerk/sctp/src/user_recv_thread.c index e175222c1008..98cf1b642f23 100755 --- a/netwerk/sctp/src/user_recv_thread.c +++ b/netwerk/sctp/src/user_recv_thread.c @@ -28,14 +28,14 @@ * */ +/* to make sure some OSs define in6_pktinfo */ +#define _GNU_SOURCE + #if defined(INET) || defined(INET6) #include #if !defined(__Userspace_os_Windows) #include -/* to make sure some OSs define in6_pktinfo */ -#define __USE_GNU #include -#undef __USE_GNU #include #include #if !defined(__Userspace_os_FreeBSD) diff --git a/netwerk/sctp/src/usrsctp.h b/netwerk/sctp/src/usrsctp.h index 65e616ac8b13..9948ddda6e62 100644 --- a/netwerk/sctp/src/usrsctp.h +++ b/netwerk/sctp/src/usrsctp.h @@ -35,6 +35,9 @@ extern "C" { #endif +/* to make sure some OSs define in6_pktinfo */ +#define _GNU_SOURCE + #include #ifdef _WIN32 #include @@ -43,10 +46,7 @@ extern "C" { #include #else #include -/* to make sure some OSs define in6_pktinfo */ -#define __USE_GNU #include -#undef __USE_GNU #endif #ifndef MSG_NOTIFICATION diff --git a/python/mozboot/mozboot/osx.py b/python/mozboot/mozboot/osx.py index 3d4cef5e5432..e00399701a60 100644 --- a/python/mozboot/mozboot/osx.py +++ b/python/mozboot/mozboot/osx.py @@ -64,15 +64,6 @@ would prefer to do this manually, hit CTRL+c, install Homebrew yourself, ensure "brew" is in your $PATH, and relaunch bootstrap. ''' -HOMEBREW_XQUARTZ = ''' -Homebrew needs XQuartz installed in order to build some dependencies. Please -download and install XQuartz from the following URL: - - http://xquartz.macosforge.org/downloads/SL/XQuartz-2.7.3.dmg\ - -When that has finished, please relaunch bootstrap. -''' - HOMEBREW_PACKAGES = ''' We are now installing all required packages via Homebrew. You will see a lot of output as packages are built. @@ -159,22 +150,12 @@ class OSXBootstrapper(BaseBootstrapper): subprocess.check_call(['ruby', tf.name]) - def ensure_xquartz(self): - if os.path.exists('/Applications/Utilities/XQuartz.app'): - return - - print(HOMEBREW_XQUARTZ) - sys.exit(1) - def ensure_homebrew_packages(self): brew = self.which('brew') assert brew is not None installed = self.check_output([brew, 'list']).split() - if 'python' not in installed: - self.ensure_xquartz() - packages = [ # We need to install Python because Mercurial requires the Python # development headers which are missing from OS X (at least on diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py index cf64fdd42ac9..e9c33130f638 100644 --- a/python/mozbuild/mozbuild/mach_commands.py +++ b/python/mozbuild/mozbuild/mach_commands.py @@ -129,3 +129,10 @@ class Warnings(MachCommandBase): print('%s:%d [%s] %s' % (filename, warning['line'], warning['flag'], warning['message'])) +@CommandProvider +class Package(MachCommandBase): + """Package the built product for distribution.""" + + @Command('package', help='Package the built product for distribution as an APK, DMG, etc.') + def package(self): + return self._run_make(directory=".", target='package', ensure_exit_code=False) diff --git a/services/common/tests/unit/test_utils_json.js b/services/common/tests/unit/test_utils_json.js index 6ba0f403a218..d3924e24a6fc 100644 --- a/services/common/tests/unit/test_utils_json.js +++ b/services/common/tests/unit/test_utils_json.js @@ -3,6 +3,7 @@ Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://services-common/utils.js"); +Cu.import("resource://gre/modules/osfile.jsm") function run_test() { initTestLogging(); @@ -108,3 +109,33 @@ add_test(function test_load_logging() { run_next_test(); })); }); + +add_test(function test_writeJSON_readJSON() { + _("Round-trip some JSON through the promise-based JSON writer."); + + let contents = { + "a": 12345.67, + "b": { + "c": "héllö", + }, + "d": undefined, + "e": null, + }; + + function checkJSON(json) { + do_check_eq(contents.a, json.a); + do_check_eq(contents.b.c, json.b.c); + do_check_eq(contents.d, json.d); + do_check_eq(contents.e, json.e); + run_next_test(); + }; + + function doRead() { + CommonUtils.readJSON(path) + .then(checkJSON, do_throw); + } + + let path = OS.Path.join(OS.Constants.Path.profileDir, "bar.json"); + CommonUtils.writeJSON(contents, path) + .then(doRead, do_throw); +}); diff --git a/services/common/utils.js b/services/common/utils.js index 0922f2fce8ec..988bf354d01e 100644 --- a/services/common/utils.js +++ b/services/common/utils.js @@ -10,6 +10,7 @@ Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/osfile.jsm") Cu.import("resource://services-common/log4moz.js"); this.CommonUtils = { @@ -329,6 +330,33 @@ this.CommonUtils = { return over ? atob(b64.substr(0, len - over)) : atob(b64); }, + /** + * Parses a JSON file from disk using OS.File and promises. + * + * @param path the file to read. Will be passed to `OS.File.read()`. + * @return a promise that resolves to the JSON contents of the named file. + */ + readJSON: function(path) { + let decoder = new TextDecoder(); + let promise = OS.File.read(path); + return promise.then(function onSuccess(array) { + return JSON.parse(decoder.decode(array)); + }); + }, + + /** + * Write a JSON object to the named file using OS.File and promises. + * + * @param contents a JS object. Will be serialized. + * @param path the path of the file to write. + * @return a promise, as produced by OS.File.writeAtomic. + */ + writeJSON: function(contents, path) { + let encoder = new TextEncoder(); + let array = encoder.encode(JSON.stringify(contents)); + return OS.File.writeAtomic(path, array, {tmpPath: path + ".tmp"}); + }, + /** * Load a JSON file from disk in the profile directory. * diff --git a/services/healthreport/HealthReportComponents.manifest b/services/healthreport/HealthReportComponents.manifest index 1157c14eccc1..8599d25648d4 100644 --- a/services/healthreport/HealthReportComponents.manifest +++ b/services/healthreport/HealthReportComponents.manifest @@ -11,4 +11,5 @@ category app-startup HealthReportService service,@mozilla.org/healthreport/servi category healthreport-js-provider AppInfoProvider resource://gre/modules/services/healthreport/providers.jsm category healthreport-js-provider SysInfoProvider resource://gre/modules/services/healthreport/providers.jsm +category healthreport-js-provider ProfileMetadataProvider resource://gre/modules/services/healthreport/profile.jsm diff --git a/services/healthreport/Makefile.in b/services/healthreport/Makefile.in index d419f8e16897..da51fdff9462 100644 --- a/services/healthreport/Makefile.in +++ b/services/healthreport/Makefile.in @@ -12,6 +12,7 @@ include $(DEPTH)/config/autoconf.mk modules := \ healthreporter.jsm \ policy.jsm \ + profile.jsm \ providers.jsm \ $(NULL) diff --git a/services/healthreport/profile.jsm b/services/healthreport/profile.jsm new file mode 100644 index 000000000000..b559ca9efca0 --- /dev/null +++ b/services/healthreport/profile.jsm @@ -0,0 +1,228 @@ +/* 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"; + +this.EXPORTED_SYMBOLS = [ + "ProfileCreationTimeAccessor", + "ProfileMetadataProvider", +]; + +const {utils: Cu, classes: Cc, interfaces: Ci} = Components; + +const DEFAULT_PROFILE_MEASUREMENT_NAME = "org.mozilla.profile"; +const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; +const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"}; + +Cu.import("resource://gre/modules/commonjs/promise/core.js"); +Cu.import("resource://gre/modules/osfile.jsm") +Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm"); +Cu.import("resource://services-common/log4moz.js"); +Cu.import("resource://services-common/utils.js"); + +// Profile creation time access. +// This is separate from the provider to simplify testing and enable extraction +// to a shared location in the future. +function ProfileCreationTimeAccessor(profile) { + this.profilePath = profile || OS.Constants.Path.profileDir; + if (!this.profilePath) { + throw new Error("No profile directory."); + } +} +ProfileCreationTimeAccessor.prototype = { + /** + * There are three ways we can get our creation time: + * + * 1. From our own saved value (to avoid redundant work). + * 2. From the on-disk JSON file. + * 3. By calculating it from the filesystem. + * + * If we have to calculate, we write out the file; if we have + * to touch the file, we persist in-memory. + * + * @return a promise that resolves to the profile's creation time. + */ + get created() { + if (this._created) { + return Promise.resolve(this._created); + } + + function onSuccess(times) { + if (times && times.created) { + return this._created = times.created; + } + return onFailure.call(this, null, times); + } + + function onFailure(err, times) { + return this.computeAndPersistTimes(times) + .then(function onSuccess(created) { + return this._created = created; + }.bind(this)); + } + + return this.readTimes() + .then(onSuccess.bind(this), + onFailure.bind(this)); + }, + + /** + * Explicitly make `file`, a filename, a full path + * relative to our profile path. + */ + getPath: function (file) { + return OS.Path.join(this.profilePath, file); + }, + + /** + * Return a promise which resolves to the JSON contents + * of the time file in this accessor's profile. + */ + readTimes: function (file="times.json") { + return CommonUtils.readJSON(this.getPath(file)); + }, + + /** + * Return a promise representing the writing of `contents` + * to `file` in the specified profile. + */ + writeTimes: function (contents, file="times.json") { + return CommonUtils.writeJSON(contents, this.getPath(file)); + }, + + /** + * Merge existing contents with a 'created' field, writing them + * to the specified file. Promise, naturally. + */ + computeAndPersistTimes: function (existingContents, file="times.json") { + let path = this.getPath(file); + function onOldest(oldest) { + let contents = existingContents || {}; + contents.created = oldest; + return this.writeTimes(contents, path) + .then(function onSuccess() { + return oldest; + }); + } + + return this.getOldestProfileTimestamp() + .then(onOldest.bind(this)); + }, + + /** + * Traverse the contents of the profile directory, finding the oldest file + * and returning its creation timestamp. + */ + getOldestProfileTimestamp: function () { + let oldest = Date.now() + 1000; + let iterator = new OS.File.DirectoryIterator(this.profilePath); +dump("Iterating over profile " + this.profilePath); + if (!iterator) { + throw new Error("Unable to fetch oldest profile entry: no profile iterator."); + } + + function onEntry(entry) { + if ("winLastWriteDate" in entry) { + // Under Windows, additional information allow us to sort files immediately + // without having to perform additional I/O. + let timestamp = entry.winCreationDate.getTime(); + if (timestamp < oldest) { + oldest = timestamp; + } + return; + } + + // Under other OSes, we need to call OS.File.stat. + function onStatSuccess(info) { + let date = info.creationDate; + let timestamp = date.getTime(); + dump("CREATION DATE: " + entry.path + " = " + date); + if (timestamp < oldest) { + oldest = timestamp; + } + } + return OS.File.stat(entry.path) + .then(onStatSuccess); + } + + let promise = iterator.forEach(onEntry); + + function onSuccess() { + iterator.close(); + return oldest; + } + + function onFailure(reason) { + iterator.close(); + throw new Error("Unable to fetch oldest profile entry: " + reason); + } + + return promise.then(onSuccess, onFailure); + }, +} + +/** + * Measurements pertaining to the user's profile. + */ +function ProfileMetadataMeasurement(name=DEFAULT_PROFILE_MEASUREMENT_NAME) { + MetricsMeasurement.call(this, name, 1); +} +ProfileMetadataMeasurement.prototype = { + __proto__: MetricsMeasurement.prototype, + + fields: { + // Profile creation date. Number of days since Unix epoch. + "profileCreation": REQUIRED_UINT32_TYPE, + }, +}; + +/** + * Turn a millisecond timestamp into a day timestamp. + * + * @param msec a number of milliseconds since epoch. + * @return the number of whole days denoted by the input. + */ +function truncate(msec) { + return Math.floor(msec / MILLISECONDS_PER_DAY); +} + +/** + * A MetricsProvider for profile metadata, such as profile creation time. + */ +function ProfileMetadataProvider(name="ProfileMetadataProvider") { + MetricsProvider.call(this, name); +} +ProfileMetadataProvider.prototype = { + __proto__: MetricsProvider.prototype, + + getProfileCreationDays: function () { + let accessor = new ProfileCreationTimeAccessor(); + + return accessor.created + .then(truncate); + }, + + collectConstantMeasurements: function () { + let result = this.createResult(); + result.expectMeasurement("org.mozilla.profile"); + result.populate = this._populateConstants.bind(this); + return result; + }, + + _populateConstants: function (result) { + let name = DEFAULT_PROFILE_MEASUREMENT_NAME; + result.addMeasurement(new ProfileMetadataMeasurement(name)); + function onSuccess(days) { + result.setValue(name, "profileCreation", days); + result.finish(); + } + function onFailure(ex) { + result.addError(ex); + result.finish(); + } + return this.getProfileCreationDays() + .then(onSuccess, onFailure); + }, +}; + diff --git a/services/healthreport/tests/xpcshell/test_load_modules.js b/services/healthreport/tests/xpcshell/test_load_modules.js index d41ef5bad76f..5a85abab05c4 100644 --- a/services/healthreport/tests/xpcshell/test_load_modules.js +++ b/services/healthreport/tests/xpcshell/test_load_modules.js @@ -6,6 +6,7 @@ const modules = [ "healthreporter.jsm", "policy.jsm", + "profile.jsm", "providers.jsm", ]; diff --git a/services/healthreport/tests/xpcshell/test_profile.js b/services/healthreport/tests/xpcshell/test_profile.js new file mode 100644 index 000000000000..3ab9523cf35f --- /dev/null +++ b/services/healthreport/tests/xpcshell/test_profile.js @@ -0,0 +1,213 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {utils: Cu} = Components; + +const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; + +// Create profile directory before use. +// It can be no older than a day ago…. +let profile_creation_lower = Date.now() - MILLISECONDS_PER_DAY; +do_get_profile(); + +Cu.import("resource://gre/modules/commonjs/promise/core.js"); +Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm"); +Cu.import("resource://gre/modules/services/healthreport/profile.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +function MockProfileMetadataProvider(name="MockProfileMetadataProvider") { + ProfileMetadataProvider.call(this, name); +} +MockProfileMetadataProvider.prototype = { + __proto__: ProfileMetadataProvider.prototype, + + getProfileCreationDays: function getProfileCreationDays() { + return Promise.resolve(1234); + }, +}; + + +function run_test() { + run_next_test(); +} + +/** + * Treat the provided function as a generator of promises, + * suitable for use with Task.spawn. Success runs next test; + * failure throws. + */ +function testTask(promiseFunction) { + Task.spawn(promiseFunction).then(run_next_test, do_throw); +} + +/** + * Ensure that OS.File works in our environment. + * This test can go once there are xpcshell tests for OS.File. + */ +add_test(function use_os_file() { + Cu.import("resource://gre/modules/osfile.jsm") + + // Ensure that we get constants, too. + do_check_neq(OS.Constants.Path.profileDir, null); + + let iterator = new OS.File.DirectoryIterator("."); + iterator.forEach(function onEntry(entry) { + print("Got " + entry.path); + }).then(function onSuccess() { + iterator.close(); + print("Done."); + run_next_test(); + }, function onFail() { + iterator.close(); + do_throw("Iterating over current directory failed."); + }); +}); + +function getAccessor() { + let acc = new ProfileCreationTimeAccessor(); + print("Profile is " + acc.profilePath); + return acc; +} + +add_test(function test_time_accessor_no_file() { + let acc = getAccessor(); + + // There should be no file yet. + acc.readTimes() + .then(function onSuccess(json) { + do_throw("File existed!"); + }, + function onFailure() { + run_next_test(); + }); +}); + +add_test(function test_time_accessor_named_file() { + let acc = getAccessor(); + + testTask(function () { + // There should be no file yet. + yield acc.writeTimes({created: 12345}, "test.json"); + yield acc.readTimes("test.json") + .then(function onSuccess(json) { + print("Read: " + JSON.stringify(json)); + do_check_eq(12345, json.created); + run_next_test(); + }); + }); +}); + +add_test(function test_time_accessor_creates_file() { + let lower = profile_creation_lower; + + // Ensure that provided contents are merged, and existing + // files can be overwritten. These two things occur if we + // read and then decide that we have to write. + let acc = getAccessor(); + let existing = {abc: "123", easy: "abc"}; + let expected; + + testTask(function () { + yield acc.computeAndPersistTimes(existing, "test2.json") + .then(function onSuccess(created) { + let upper = Date.now() + 1000; + print(lower + " < " + created + " <= " + upper); + do_check_true(lower < created); + do_check_true(upper >= created); + expected = created; + }); + yield acc.readTimes("test2.json") + .then(function onSuccess(json) { + print("Read: " + JSON.stringify(json)); + do_check_eq("123", json.abc); + do_check_eq("abc", json.easy); + do_check_eq(expected, json.created); + }); + }); +}); + +add_test(function test_time_accessor_all() { + let lower = profile_creation_lower; + let acc = getAccessor(); + let expected; + testTask(function () { + yield acc.created + .then(function onSuccess(created) { + let upper = Date.now() + 1000; + do_check_true(lower < created); + do_check_true(upper >= created); + expected = created; + }); + yield acc.created + .then(function onSuccess(again) { + do_check_eq(expected, again); + }); + }); +}); + +add_test(function test_constructor() { + let provider = new ProfileMetadataProvider("named"); + run_next_test(); +}); + +add_test(function test_profile_files() { + let provider = new ProfileMetadataProvider(); + + function onSuccess(answer) { + let now = Date.now() / MILLISECONDS_PER_DAY; + print("Got " + answer + ", versus now = " + now); + do_check_true(answer < now); + run_next_test(); + } + + function onFailure(ex) { + do_throw("Directory iteration failed: " + ex); + } + + provider.getProfileCreationDays().then(onSuccess, onFailure); +}); + +// A generic test helper. We use this with both real +// and mock providers in these tests. +function test_collect_constant(provider, valueTest) { + let result = provider.collectConstantMeasurements(); + do_check_true(result instanceof MetricsCollectionResult); + + result.onFinished(function onFinished() { + do_check_eq(result.expectedMeasurements.size, 1); + do_check_true(result.expectedMeasurements.has("org.mozilla.profile")); + let m = result.measurements.get("org.mozilla.profile"); + do_check_true(!!m); + valueTest(m.getValue("profileCreation")); + + run_next_test(); + }); + + result.populate(result); +} + +add_test(function test_collect_constant_mock() { + let provider = new MockProfileMetadataProvider(); + function valueTest(v) { + do_check_eq(v, 1234); + } + test_collect_constant(provider, valueTest); +}); + +add_test(function test_collect_constant_real() { + let provider = new ProfileMetadataProvider(); + function valueTest(v) { + let ms = v * MILLISECONDS_PER_DAY; + let lower = profile_creation_lower; + let upper = Date.now() + 1000; + print("Day: " + v); + print("msec: " + ms); + print("Lower: " + lower); + print("Upper: " + upper); + do_check_true(lower <= ms); + do_check_true(upper >= ms); + } + test_collect_constant(provider, valueTest); +}); diff --git a/services/healthreport/tests/xpcshell/xpcshell.ini b/services/healthreport/tests/xpcshell/xpcshell.ini index 069c43ef5dd5..31c8689f7365 100644 --- a/services/healthreport/tests/xpcshell/xpcshell.ini +++ b/services/healthreport/tests/xpcshell/xpcshell.ini @@ -3,6 +3,7 @@ head = head.js tail = [test_load_modules.js] +[test_profile.js] [test_policy.js] [test_healthreporter.js] [test_provider_appinfo.js] diff --git a/testing/marionette/client/marionette/marionette_test.py b/testing/marionette/client/marionette/marionette_test.py index 0db0ad814084..8c96134879c2 100644 --- a/testing/marionette/client/marionette/marionette_test.py +++ b/testing/marionette/client/marionette/marionette_test.py @@ -6,6 +6,7 @@ import imp import os import re import sys +import time import types import unittest import weakref @@ -124,11 +125,13 @@ class MarionetteTestCase(CommonTestCase): def setUp(self): CommonTestCase.setUp(self) + self.start_time = time.time() self.marionette.test_name = self.test_name self.marionette.execute_script("log('TEST-START: %s:%s')" % (self.filepath.replace('\\', '\\\\'), self.methodName)) def tearDown(self): + self.duration = time.time() - self.start_time self.marionette.set_context("content") self.marionette.execute_script("log('TEST-END: %s:%s')" % (self.filepath.replace('\\', '\\\\'), self.methodName)) diff --git a/testing/marionette/client/marionette/runtests.py b/testing/marionette/client/marionette/runtests.py index c93d9de212e4..886b1936c55b 100644 --- a/testing/marionette/client/marionette/runtests.py +++ b/testing/marionette/client/marionette/runtests.py @@ -490,6 +490,7 @@ class MarionetteTestRunner(object): testcase = doc.createElement('testcase') testcase.setAttribute('classname', cls_name) testcase.setAttribute('name', unicode(test).split()[0]) + testcase.setAttribute('time', str(test.duration)) testsuite.appendChild(testcase) if result in ['failure', 'error', 'skipped']: @@ -502,8 +503,7 @@ class MarionetteTestRunner(object): testsuite = doc.createElement('testsuite') testsuite.setAttribute('name', 'Marionette') - # convert elapsedtime to integer milliseconds - testsuite.setAttribute('time', str(int(self.elapsedtime.total_seconds() * 1000))) + testsuite.setAttribute('time', str(self.elapsedtime.total_seconds())) testsuite.setAttribute('tests', str(sum([results.testsRun for results in results_list]))) diff --git a/testing/marionette/client/setup.py b/testing/marionette/client/setup.py index a082e56432e5..3e4bcda2c3a0 100644 --- a/testing/marionette/client/setup.py +++ b/testing/marionette/client/setup.py @@ -1,7 +1,7 @@ import os from setuptools import setup, find_packages -version = '0.5.10' +version = '0.5.11' # get documentation from the README try: diff --git a/testing/marionette/components/marionettecomponent.js b/testing/marionette/components/marionettecomponent.js index 6246f82e4550..610f0da406ce 100644 --- a/testing/marionette/components/marionettecomponent.js +++ b/testing/marionette/components/marionettecomponent.js @@ -18,7 +18,7 @@ const ServerSocket = CC("@mozilla.org/network/server-socket;1", Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource:///modules/services-common/log4moz.js"); +Cu.import("resource://gre/modules/services-common/log4moz.js"); function MarionetteComponent() { this._loaded = false; diff --git a/testing/xpcshell/head.js b/testing/xpcshell/head.js index 6b5f800777d4..234656a20c4c 100644 --- a/testing/xpcshell/head.js +++ b/testing/xpcshell/head.js @@ -17,6 +17,7 @@ var _passedChecks = 0, _falsePassedChecks = 0; var _todoChecks = 0; var _cleanupFunctions = []; var _pendingTimers = []; +var _profileInitialized = false; function _dump(str) { let start = /^TEST-/.test(str) ? "\n" : ""; @@ -728,15 +729,17 @@ function do_register_cleanup(aFunction) * @return nsILocalFile of the profile directory. */ function do_get_profile() { - // Since we have a profile, we will notify profile shutdown topics at - // the end of the current test, to ensure correct cleanup on shutdown. - do_register_cleanup(function() { - let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. - getService(Components.interfaces.nsIObserverService); - obsSvc.notifyObservers(null, "profile-change-net-teardown", null); - obsSvc.notifyObservers(null, "profile-change-teardown", null); - obsSvc.notifyObservers(null, "profile-before-change", null); - }); + if (!_profileInitialized) { + // Since we have a profile, we will notify profile shutdown topics at + // the end of the current test, to ensure correct cleanup on shutdown. + do_register_cleanup(function() { + let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + obsSvc.notifyObservers(null, "profile-change-net-teardown", null); + obsSvc.notifyObservers(null, "profile-change-teardown", null); + obsSvc.notifyObservers(null, "profile-before-change", null); + }); + } let env = Components.classes["@mozilla.org/process/environment;1"] .getService(Components.interfaces.nsIEnvironment); @@ -768,12 +771,21 @@ function do_get_profile() { dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService) .registerProvider(provider); - // The methods of 'provider' will entrain this scope so null out everything + let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + + if (!_profileInitialized) { + obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile"); + _profileInitialized = true; + } + + // The methods of 'provider' will retain this scope so null out everything // to avoid spurious leak reports. env = null; profd = null; dirSvc = null; provider = null; + obsSvc = null; return file.clone(); } diff --git a/toolkit/components/downloads/SQLFunctions.cpp b/toolkit/components/downloads/SQLFunctions.cpp index 5a4fff89e7a9..3a52ea3af9ff 100644 --- a/toolkit/components/downloads/SQLFunctions.cpp +++ b/toolkit/components/downloads/SQLFunctions.cpp @@ -106,7 +106,7 @@ GenerateRandomBytes(uint32_t aSize, // On Unix, we'll just read in from /dev/urandom. #elif defined(XP_UNIX) - NS_ENSURE_ARG_MAX(aSize, PR_INT32_MAX); + NS_ENSURE_ARG_MAX(aSize, INT32_MAX); PRFileDesc* urandom = PR_Open("/dev/urandom", PR_RDONLY, 0); nsresult rv = NS_ERROR_FAILURE; if (urandom) { diff --git a/toolkit/components/osfile/osfile_async_front.jsm b/toolkit/components/osfile/osfile_async_front.jsm index daf0ca4ef468..40aa5650b9be 100644 --- a/toolkit/components/osfile/osfile_async_front.jsm +++ b/toolkit/components/osfile/osfile_async_front.jsm @@ -34,12 +34,12 @@ const DEBUG = false; // The constructor for file errors. let OSError; if (OS.Constants.Win) { - Components.utils.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm"); - Components.utils.import("resource://gre/modules/osfile/ospath_win_back.jsm"); + Components.utils.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", this); + Components.utils.import("resource://gre/modules/osfile/ospath_win_back.jsm", this); OSError = OS.Shared.Win.Error; } else if (OS.Constants.libc) { - Components.utils.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm"); - Components.utils.import("resource://gre/modules/osfile/ospath_unix_back.jsm"); + Components.utils.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", this); + Components.utils.import("resource://gre/modules/osfile/ospath_unix_back.jsm", this); OSError = OS.Shared.Unix.Error; } else { throw new Error("I am neither under Windows nor under a Posix system"); @@ -47,7 +47,24 @@ if (OS.Constants.Win) { let Type = OS.Shared.Type; // The library of promises. -Components.utils.import("resource://gre/modules/commonjs/promise/core.js"); +Components.utils.import("resource://gre/modules/commonjs/promise/core.js", this); + +// If profileDir is not available, osfile.jsm has been imported before the +// profile is setup. In this case, we need to observe "profile-do-change" +// and set OS.Constants.Path.profileDir as soon as it becomes available. +if (!("profileDir" in OS.Constants.Path) || !("localProfileDir" in OS.Constants.Path)) { + Components.utils.import("resource://gre/modules/Services.jsm", this); + let observer = function observer() { + Services.obs.removeObserver(observer, "profile-do-change"); + + let profileDir = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile).path; + OS.Constants.Path.profileDir = profileDir; + + let localProfileDir = Services.dirsvc.get("ProfLD", Components.interfaces.nsIFile).path; + OS.Constants.Path.localProfileDir = localProfileDir; + }; + Services.obs.addObserver(observer, "profile-do-change", false); +} /** * Return a shallow clone of the enumerable properties of an object diff --git a/toolkit/components/osfile/tests/xpcshell/test_profiledir.js b/toolkit/components/osfile/tests/xpcshell/test_profiledir.js new file mode 100644 index 000000000000..1ff309d4ce8c --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_profiledir.js @@ -0,0 +1,45 @@ +/* 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"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +function run_test() { + run_next_test(); +} + +add_test(function test_initialize_profileDir() { + // Profile has not been set up yet, check that "profileDir" isn't either. + do_check_false(!!OS.Constants.Path.profileDir); + do_check_false(!!OS.Constants.Path.localProfileDir); + + // Set up profile. + do_get_profile(); + + // Now that profile has been set up, check that "profileDir" is set. + do_check_true("profileDir" in OS.Constants.Path); + do_check_true(!!OS.Constants.Path.profileDir); + do_check_eq(OS.Constants.Path.profileDir, + Services.dirsvc.get("ProfD", Components.interfaces.nsIFile).path); + + do_check_true("localProfileDir" in OS.Constants.Path); + do_check_true(!!OS.Constants.Path.localProfileDir); + do_check_eq(OS.Constants.Path.localProfileDir, + Services.dirsvc.get("ProfLD", Components.interfaces.nsIFile).path); + + let promise = OS.File.makeDir(OS.Path.join(OS.Constants.Path.profileDir, "foobar")); + promise.then( + function onSuccess() { + do_print("Directory creation succeeded"); + run_next_test(); + }, + + function onFailure(reason) { + do_fail(reason); + run_next_test(); + } + ); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini index 9346009bdf45..c020ffa792f0 100644 --- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini +++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini @@ -3,3 +3,4 @@ head = tail = [test_osfile_async.js] +[test_profiledir.js] diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 5d97c0a4a054..79207505128d 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -2487,5 +2487,11 @@ "high": "5000", "n_buckets": 15, "description": "Time (ms) it takes to figure out extension last modified time" + }, + "TELEMETRY_MEMORY_REPORTER_MS": { + "kind": "exponential", + "high": "5000", + "n_buckets": 10, + "description": "Time (ms) it takes to run memory reporters when sending a telemetry ping" } } diff --git a/toolkit/components/telemetry/TelemetryPing.js b/toolkit/components/telemetry/TelemetryPing.js index dab894e7cd94..504d9e172719 100644 --- a/toolkit/components/telemetry/TelemetryPing.js +++ b/toolkit/components/telemetry/TelemetryPing.js @@ -423,6 +423,8 @@ TelemetryPing.prototype = { return; } + let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS"); + let startTime = new Date(); let e = mgr.enumerateReporters(); while (e.hasMoreElements()) { let mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter); @@ -439,6 +441,7 @@ TelemetryPing.prototype = { catch (e) { } } + histogram.add(new Date() - startTime); }, handleMemoryReport: function handleMemoryReport(id, path, units, amount) { diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index d81b521640a5..0bdcab9af571 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -78,10 +78,6 @@ const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; - -var XPIProvider; - - this.__defineGetter__("gRDF", function gRDFGetter() { delete this.gRDF; return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. @@ -297,7 +293,7 @@ function copyRowProperties(aRow, aProperties, aTarget) { return aTarget; } -var XPIDatabase = { +this.XPIDatabase = { // true if the database connection has been opened initialized: false, // A cache of statements that are used and need to be finalized on shutdown diff --git a/xpcom/base/nsMemoryInfoDumper.cpp b/xpcom/base/nsMemoryInfoDumper.cpp index cf7cdd47fe93..33d536e1c1de 100644 --- a/xpcom/base/nsMemoryInfoDumper.cpp +++ b/xpcom/base/nsMemoryInfoDumper.cpp @@ -547,7 +547,7 @@ nsMemoryInfoDumper::DumpMemoryReportsToFileImpl( // Note that |mrFilename| is missing the "incomplete-" prefix; we'll tack // that on in a moment. nsCString mrFilename; - MakeFilename("memory-report", aIdentifier, ".json.gz", mrFilename); + MakeFilename("memory-report", aIdentifier, "json.gz", mrFilename); nsCOMPtr mrTmpFile; nsresult rv; @@ -640,7 +640,7 @@ nsMemoryInfoDumper::DumpMemoryReportsToFileImpl( // if present). nsCString dmdFilename; - MakeFilename("dmd", aIdentifier, ".txt.gz", dmdFilename); + MakeFilename("dmd", aIdentifier, "txt.gz", dmdFilename); nsCOMPtr dmdFile; rv = OpenTempFile(dmdFilename, getter_AddRefs(dmdFile)); diff --git a/xpcom/ds/TimeStamp.h b/xpcom/ds/TimeStamp.h index 401ef2ce8304..d80cd059b24f 100644 --- a/xpcom/ds/TimeStamp.h +++ b/xpcom/ds/TimeStamp.h @@ -83,6 +83,18 @@ public: mValue -= aOther.mValue; return *this; } + TimeDuration operator*(const double aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } + TimeDuration operator*(const int32_t aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } + TimeDuration operator*(const uint32_t aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } + TimeDuration operator*(const int64_t aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } double operator/(const TimeDuration& aOther) { return static_cast(mValue) / aOther.mValue; } diff --git a/xpcom/ds/nsISupportsArray.idl b/xpcom/ds/nsISupportsArray.idl index 126792d95529..e0163969d26b 100644 --- a/xpcom/ds/nsISupportsArray.idl +++ b/xpcom/ds/nsISupportsArray.idl @@ -15,8 +15,6 @@ * http://groups.google.com/groups?q=nsisupportsarray+group:netscape.public.mozilla.xpcom&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=al8412%245ab2%40ripley.netscape.com&rnum=8 */ -native nsISupportsArrayEnumFunc(nsISupportsArrayEnumFunc); - %{C++ class nsIBidirectionalEnumerator; @@ -32,15 +30,12 @@ class nsISupportsArray; #define NS_SUPPORTSARRAY_CONTRACTID "@mozilla.org/supports-array;1" #define NS_SUPPORTSARRAY_CLASSNAME "Supports Array" -// Enumerator callback function. Return PR_FALSE to stop -typedef bool (*nsISupportsArrayEnumFunc)(nsISupports* aElement, void *aData); - nsresult NS_NewArrayEnumerator(nsISimpleEnumerator* *result, nsISupportsArray* array); %} -[scriptable, uuid(2d375c0f-8a34-4397-bea1-ab4d6beae0ab)] +[scriptable, uuid(241addc8-3608-4e73-8083-2fd6fa09eba2)] interface nsISupportsArray : nsICollection { [notxpcom] boolean Equals([const] in nsISupportsArray other); @@ -74,9 +69,6 @@ interface nsISupportsArray : nsICollection { void Compact(); - [notxpcom, noscript] - boolean EnumerateForwards(in nsISupportsArrayEnumFunc aFunc, - in voidPtr aData); nsISupportsArray clone(); diff --git a/xpcom/ds/nsSupportsArray.cpp b/xpcom/ds/nsSupportsArray.cpp index d7b791fb352d..48c36f8fd503 100644 --- a/xpcom/ds/nsSupportsArray.cpp +++ b/xpcom/ds/nsSupportsArray.cpp @@ -571,18 +571,6 @@ nsSupportsArray::SizeTo(int32_t aSize) return true; } -NS_IMETHODIMP_(bool) -nsSupportsArray::EnumerateForwards(nsISupportsArrayEnumFunc aFunc, void* aData) -{ - int32_t aIndex = -1; - bool running = true; - - while (running && (++aIndex < (int32_t)mCount)) { - running = (*aFunc)(mArray[aIndex], aData); - } - return running; -} - NS_IMETHODIMP nsSupportsArray::Enumerate(nsIEnumerator* *result) { @@ -594,15 +582,6 @@ nsSupportsArray::Enumerate(nsIEnumerator* *result) return NS_OK; } -static bool -CopyElement(nsISupports* aElement, void *aData) -{ - nsresult rv; - nsISupportsArray* newArray = (nsISupportsArray*)aData; - rv = newArray->AppendElement(aElement); - return NS_SUCCEEDED(rv); -} - NS_IMETHODIMP nsSupportsArray::Clone(nsISupportsArray** aResult) { @@ -610,8 +589,13 @@ nsSupportsArray::Clone(nsISupportsArray** aResult) nsresult rv = NS_NewISupportsArray(getter_AddRefs(newArray)); NS_ENSURE_SUCCESS(rv, rv); - bool ok = EnumerateForwards(CopyElement, newArray); - NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); + uint32_t count = 0; + Count(&count); + for (uint32_t i = 0; i < count; i++) { + if (!newArray->InsertElementAt(mArray[i], i)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } newArray.forget(aResult); return NS_OK; diff --git a/xpcom/ds/nsSupportsArray.h b/xpcom/ds/nsSupportsArray.h index e93523191364..fbdb881a629e 100644 --- a/xpcom/ds/nsSupportsArray.h +++ b/xpcom/ds/nsSupportsArray.h @@ -105,8 +105,6 @@ public: NS_IMETHOD Compact(void); - NS_IMETHOD_(bool) EnumerateForwards(nsISupportsArrayEnumFunc aFunc, void* aData); - NS_IMETHOD Clone(nsISupportsArray **_retval); NS_IMETHOD_(bool) InsertElementsAt(nsISupportsArray *aOther, uint32_t aIndex); diff --git a/xpfe/appshell/src/nsWindowMediator.cpp b/xpfe/appshell/src/nsWindowMediator.cpp index 560ea6aba1d8..f09fa06844e0 100644 --- a/xpfe/appshell/src/nsWindowMediator.cpp +++ b/xpfe/appshell/src/nsWindowMediator.cpp @@ -29,9 +29,9 @@ using namespace mozilla; static nsresult GetDOMWindow(nsIXULWindow* inWindow, nsCOMPtr< nsIDOMWindow>& outDOMWindow); -static bool notifyOpenWindow(nsISupports *aElement, void* aData); -static bool notifyCloseWindow(nsISupports *aElement, void* aData); -static bool notifyWindowTitleChange(nsISupports *aElement, void* aData); +static bool notifyOpenWindow(nsIWindowMediatorListener *aElement, void* aData); +static bool notifyCloseWindow(nsIWindowMediatorListener *aElement, void* aData); +static bool notifyWindowTitleChange(nsIWindowMediatorListener *aElement, void* aData); // for notifyWindowTitleChange struct WindowTitleData { @@ -91,10 +91,8 @@ NS_IMETHODIMP nsWindowMediator::RegisterWindow(nsIXULWindow* inWindow) if (!windowInfo) return NS_ERROR_OUT_OF_MEMORY; - if (mListeners) { - WindowTitleData winData = { inWindow, nullptr }; - mListeners->EnumerateForwards(notifyOpenWindow, (void*)&winData); - } + WindowTitleData winData = { inWindow, nullptr }; + mListeners.EnumerateForwards(notifyOpenWindow, &winData); MutexAutoLock lock(mListLock); if (mOldestWindow) @@ -126,10 +124,8 @@ nsWindowMediator::UnregisterWindow(nsWindowInfo *inInfo) index++; } - if (mListeners) { - WindowTitleData winData = { inInfo->mWindow.get(), nullptr }; - mListeners->EnumerateForwards(notifyCloseWindow, (void*)&winData); - } + WindowTitleData winData = { inInfo->mWindow.get(), nullptr }; + mListeners.EnumerateForwards(notifyCloseWindow, &winData); // Remove from the lists and free up if (inInfo == mOldestWindow) @@ -344,9 +340,9 @@ nsWindowMediator::UpdateWindowTitle(nsIXULWindow* inWindow, { NS_ENSURE_STATE(mReady); MutexAutoLock lock(mListLock); - if (mListeners && GetInfoFor(inWindow)) { + if (GetInfoFor(inWindow)) { WindowTitleData winData = { inWindow, inTitle }; - mListeners->EnumerateForwards(notifyWindowTitleChange, (void*)&winData); + mListeners.EnumerateForwards(notifyWindowTitleChange, &winData); } return NS_OK; @@ -727,13 +723,7 @@ nsWindowMediator::AddListener(nsIWindowMediatorListener* aListener) { NS_ENSURE_ARG_POINTER(aListener); - nsresult rv; - if (!mListeners) { - rv = NS_NewISupportsArray(getter_AddRefs(mListeners)); - if (NS_FAILED(rv)) return rv; - } - - mListeners->AppendElement(aListener); + mListeners.AppendObject(aListener); return NS_OK; } @@ -743,10 +733,7 @@ nsWindowMediator::RemoveListener(nsIWindowMediatorListener* aListener) { NS_ENSURE_ARG_POINTER(aListener); - if (!mListeners) - return NS_OK; - - mListeners->RemoveElement(aListener); + mListeners.RemoveObject(aListener); return NS_OK; } @@ -776,34 +763,28 @@ nsWindowMediator::Observe(nsISupports* aSubject, } bool -notifyOpenWindow(nsISupports *aElement, void* aData) +notifyOpenWindow(nsIWindowMediatorListener *aListener, void* aData) { - nsIWindowMediatorListener* listener = - reinterpret_cast(aElement); WindowTitleData* winData = static_cast(aData); - listener->OnOpenWindow(winData->mWindow); + aListener->OnOpenWindow(winData->mWindow); return true; } bool -notifyCloseWindow(nsISupports *aElement, void* aData) +notifyCloseWindow(nsIWindowMediatorListener *aListener, void* aData) { - nsIWindowMediatorListener* listener = - reinterpret_cast(aElement); WindowTitleData* winData = static_cast(aData); - listener->OnCloseWindow(winData->mWindow); + aListener->OnCloseWindow(winData->mWindow); return true; } bool -notifyWindowTitleChange(nsISupports *aElement, void* aData) +notifyWindowTitleChange(nsIWindowMediatorListener *aListener, void* aData) { - nsIWindowMediatorListener* listener = - reinterpret_cast(aElement); WindowTitleData* titleData = reinterpret_cast(aData); - listener->OnWindowTitleChange(titleData->mWindow, titleData->mTitle); + aListener->OnWindowTitleChange(titleData->mWindow, titleData->mTitle); return true; } diff --git a/xpfe/appshell/src/nsWindowMediator.h b/xpfe/appshell/src/nsWindowMediator.h index 88ca91d754a6..a7809dd7f3b6 100644 --- a/xpfe/appshell/src/nsWindowMediator.h +++ b/xpfe/appshell/src/nsWindowMediator.h @@ -9,12 +9,12 @@ #include "mozilla/Mutex.h" #include "nsCOMPtr.h" #include "nsIWindowMediator.h" -#include "nsISupportsArray.h" #include "nsIObserver.h" #include "nsTArray.h" #include "nsXPIDLString.h" #include "nsWeakReference.h" #include "nsCRT.h" +#include "nsCOMArray.h" class nsAppShellWindowEnumerator; class nsASXULWindowEarlyToLateEnumerator; @@ -23,6 +23,7 @@ class nsASDOMWindowFrontToBackEnumerator; class nsASXULWindowFrontToBackEnumerator; class nsASDOMWindowBackToFrontEnumerator; class nsASXULWindowBackToFrontEnumerator; +class nsIWindowMediatorListener; struct nsWindowInfo; struct PRLock; @@ -68,7 +69,7 @@ private: bool mReady; mozilla::Mutex mListLock; - nsCOMPtr mListeners; + nsCOMArray mListeners; }; #endif