author | Wes Kocher <wkocher@mozilla.com> |
Tue, 29 Jul 2014 17:01:18 -0700 | |
changeset 196685 | f61a27b00e05b7e0799fca19ea8194f975afa872 |
parent 196684 | 3b682051f3ad74bb34d4310e0fa3186074365d46 (current diff) |
parent 196609 | 17d70e1ff57b28a4208e37cefcab1ff9deaa7464 (diff) |
child 196686 | d80e2e170f4124d600013300e227aee7f81ad7bc |
child 196748 | 1bfa75463a211bbe7c911e72482ea8e3dff79a19 |
child 196783 | 6c4af376d77abfa1ca5a22c4682c8769b46ac255 |
push id | 46938 |
push user | kwierso@gmail.com |
push date | Wed, 30 Jul 2014 00:17:43 +0000 |
treeherder | mozilla-inbound@d80e2e170f41 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 34.0a1 |
first release with | nightly linux32
f61a27b00e05
/
34.0a1
/
20140730030201
/
files
nightly linux64
f61a27b00e05
/
34.0a1
/
20140730030201
/
files
nightly mac
f61a27b00e05
/
34.0a1
/
20140730030201
/
files
nightly win32
f61a27b00e05
/
34.0a1
/
20140730030201
/
files
nightly win64
f61a27b00e05
/
34.0a1
/
20140730030201
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
34.0a1
/
20140730030201
/
pushlog to previous
nightly linux64
34.0a1
/
20140730030201
/
pushlog to previous
nightly mac
34.0a1
/
20140730030201
/
pushlog to previous
nightly win32
34.0a1
/
20140730030201
/
pushlog to previous
nightly win64
34.0a1
/
20140730030201
/
pushlog to previous
|
--- a/accessible/base/TextRange.cpp +++ b/accessible/base/TextRange.cpp @@ -5,17 +5,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TextRange.h" #include "Accessible-inl.h" #include "HyperTextAccessible.h" #include "nsAccUtils.h" -using namespace mozilla::a11y; +namespace mozilla { +namespace a11y { //////////////////////////////////////////////////////////////////////////////// // TextPoint bool TextPoint::operator <(const TextPoint& aPoint) const { if (mContainer == aPoint.mContainer) @@ -289,8 +290,11 @@ TextRange::TextInternal(nsAString& aText void TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount, HyperTextAccessible& aContainer, int32_t aOffset, HyperTextAccessible* aStopContainer, int32_t aStopOffset) { } + +} // namespace a11y +} // namespace mozilla
--- a/accessible/moz.build +++ b/accessible/moz.build @@ -15,9 +15,9 @@ elif toolkit == 'cocoa': else: DIRS += ['other'] DIRS += ['base', 'generic', 'html', 'interfaces', 'jsat', 'xpcom'] if CONFIG['MOZ_XUL']: DIRS += ['xul'] -TEST_DIRS += ['tests'] +TEST_DIRS += ['tests/mochitest']
deleted file mode 100644 --- a/accessible/tests/mochitest/actions/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/attributes/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/bounds/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/editabletext/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/elm/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/focus/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/hittest/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/hyperlink/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/hypertext/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/jsat/moz.build +++ /dev/null @@ -1,7 +0,0 @@ -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
--- a/accessible/tests/mochitest/moz.build +++ b/accessible/tests/mochitest/moz.build @@ -1,39 +1,36 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. -DIRS += [ - 'actions', - 'attributes', - 'bounds', - 'editabletext', - 'elm', - 'focus', - 'hittest', - 'hyperlink', - 'hypertext', - 'jsat', - 'name', - 'pivot', - 'relations', - 'role', - 'scroll', - 'selectable', - 'states', - 'table', - 'text', - 'textattrs', - 'textcaret', - 'textselection', - 'treeupdate', - 'value', -] - A11Y_MANIFESTS += [ 'a11y.ini', + 'actions/a11y.ini', + 'attributes/a11y.ini', + 'bounds/a11y.ini', + 'editabletext/a11y.ini', + 'elm/a11y.ini', 'events/a11y.ini', + 'focus/a11y.ini', + 'hittest/a11y.ini', + 'hyperlink/a11y.ini', + 'hypertext/a11y.ini', + 'jsat/a11y.ini', + 'name/a11y.ini', + 'pivot/a11y.ini', + 'relations/a11y.ini', + 'role/a11y.ini', + 'scroll/a11y.ini', + 'selectable/a11y.ini', + 'states/a11y.ini', + 'table/a11y.ini', + 'text/a11y.ini', + 'textattrs/a11y.ini', + 'textcaret/a11y.ini', 'textrange/a11y.ini', + 'textselection/a11y.ini', 'tree/a11y.ini', + 'treeupdate/a11y.ini', + 'value/a11y.ini', ]
deleted file mode 100644 --- a/accessible/tests/mochitest/name/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/pivot/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/relations/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/role/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/scroll/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/selectable/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/states/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/table/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/text/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/textattrs/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/textcaret/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/textselection/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/treeupdate/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/mochitest/value/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -A11Y_MANIFESTS += ['a11y.ini'] -
deleted file mode 100644 --- a/accessible/tests/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -DIRS += ['mochitest'] -
--- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -341,16 +341,17 @@ var shell = { chromeEventHandler.addEventListener('keyup', this, true); window.addEventListener('MozApplicationManifest', this); window.addEventListener('mozfullscreenchange', this); window.addEventListener('MozAfterPaint', this); window.addEventListener('sizemodechange', this); window.addEventListener('unload', this); this.contentBrowser.addEventListener('mozbrowserloadstart', this, true); + this.contentBrowser.addEventListener('mozbrowserselectionchange', this, true); CustomEventManager.init(); WebappsHelper.init(); UserAgentOverrides.init(); IndexedDBPromptHelper.init(); CaptivePortalLoginHelper.init(); this.contentBrowser.src = homeURL; @@ -367,16 +368,17 @@ var shell = { window.removeEventListener('unload', this); window.removeEventListener('keydown', this, true); window.removeEventListener('keypress', this, true); window.removeEventListener('keyup', this, true); window.removeEventListener('MozApplicationManifest', this); window.removeEventListener('mozfullscreenchange', this); window.removeEventListener('sizemodechange', this); this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); + this.contentBrowser.removeEventListener('mozbrowserselectionchange', this, true); ppmm.removeMessageListener("content-handler", this); UserAgentOverrides.uninit(); IndexedDBPromptHelper.uninit(); }, // If this key event actually represents a hardware button, filter it here // and send a mozChromeEvent with detail.type set to xxx-button-press or @@ -509,16 +511,40 @@ var shell = { case 'mozbrowserlocationchange': if (content.document.location == 'about:blank') { return; } this.notifyContentStart(); break; + case 'mozbrowserselectionchange': + // The mozbrowserselectionchange event, may have crossed the chrome-content boundary. + // This event always dispatch to shell.js. But the offset we got from this event is + // based on tab's coordinate. So get the actual offsets between shell and evt.target. + let elt = evt.target; + let win = elt.ownerDocument.defaultView; + let offsetX = win.mozInnerScreenX; + let offsetY = win.mozInnerScreenY; + + let rect = elt.getBoundingClientRect(); + offsetX += rect.left; + offsetY += rect.top; + + let data = evt.detail; + data.offsetX = offsetX; + data.offsetY = offsetY; + + DoCommandHelper.setEvent(evt); + shell.sendChromeEvent({ + type: 'selectionchange', + detail: data, + }); + break; + case 'MozApplicationManifest': try { if (!Services.prefs.getBoolPref('browser.cache.offline.enable')) return; let contentWindow = evt.originalTarget.defaultView; let documentElement = contentWindow.document.documentElement; if (!documentElement) @@ -708,16 +734,33 @@ var CustomEventManager = { RemoteDebugger.handleEvent(detail); break; case 'captive-portal-login-cancel': CaptivePortalLoginHelper.handleEvent(detail); break; case 'inputmethod-update-layouts': KeyboardHelper.handleEvent(detail); break; + case 'do-command': + DoCommandHelper.handleEvent(detail.cmd); + break; + } + } +} + +let DoCommandHelper = { + _event: null, + setEvent: function docommand_setEvent(evt) { + this._event = evt; + }, + + handleEvent: function docommand_handleEvent(cmd) { + if (this._event) { + shell.sendEvent(this._event.target, 'mozdocommand', { cmd: cmd }); + this._event = null; } } } var WebappsHelper = { _installers: {}, _count: 0,
--- a/browser/base/content/browser-plugins.js +++ b/browser/base/content/browser-plugins.js @@ -1157,20 +1157,35 @@ var gPluginHandler = { return; let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); let submittedReport = propBag.getPropertyAsBool("submittedCrashReport"); let doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt"); let submitReports = true; // XXX followup for .getPropertyAsBool("submitReports"); let pluginName = propBag.getPropertyAsAString("pluginName"); let pluginDumpID = propBag.getPropertyAsAString("pluginDumpID"); - let browserDumpID = propBag.getPropertyAsAString("browserDumpID"); + let browserDumpID = null; + let gmpPlugin = false; + + try { + browserDumpID = propBag.getPropertyAsAString("browserDumpID"); + } catch (e) { + // For GMP crashes we don't get a browser dump. + } - // Remap the plugin name to a more user-presentable form. - pluginName = this.makeNicePluginName(pluginName); + try { + gmpPlugin = propBag.getPropertyAsBool("gmpPlugin"); + } catch (e) { + // This property is only set for GMP plugins. + } + + // For non-GMP plugins, remap the plugin name to a more user-presentable form. + if (!gmpPlugin) { + pluginName = this.makeNicePluginName(pluginName); + } let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]); let plugin = null, doc; if (target instanceof Ci.nsIObjectLoadingContent) { plugin = target; doc = plugin.ownerDocument; } else {
--- a/browser/components/loop/MozLoopService.jsm +++ b/browser/components/loop/MozLoopService.jsm @@ -236,17 +236,17 @@ let MozLoopServiceInternal = { /** * Registers with the Loop server. * * @param {String} pushUrl The push url given by the push server. * @param {Boolean} noRetry Optional, don't retry if authentication fails. */ registerWithLoopServer: function(pushUrl, noRetry) { - this.hawkRequest("/registration", "POST", { simple_push_url: pushUrl}) + this.hawkRequest("/registration", "POST", { simplePushURL: pushUrl}) .then((response) => { // If this failed we got an invalid token. storeSessionToken rejects // the gRegisteredDeferred promise for us, so here we just need to // early return. if (!this.storeSessionToken(response.headers)) return; gRegisteredDeferred.resolve();
--- a/browser/components/loop/content/js/client.js +++ b/browser/components/loop/content/js/client.js @@ -112,22 +112,16 @@ loop.Client = (function($) { if (error) { this._failureHandler(cb, error); return; } try { var urlData = JSON.parse(responseText); - // XXX Support an alternate call_url property for - // backwards compatibility whilst we switch over servers. - // Bug 1033988 will want to remove these two lines. - if (urlData.call_url) - urlData.callUrl = urlData.call_url; - cb(null, this._validate(urlData, expectedCallUrlProperties)); this.mozLoop.noteCallUrlExpiry(urlData.expiresAt); } catch (err) { console.log("Error requesting call info", err); cb(err); } });
--- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -19,17 +19,17 @@ loop.conversation = (function(OT, mozL10 * App router. * @type {loop.desktopRouter.DesktopConversationRouter} */ var router; var IncomingCallView = React.createClass({displayName: 'IncomingCallView', propTypes: { - model: React.PropTypes.func.isRequired + model: React.PropTypes.object.isRequired }, getInitialState: function() { return {showDeclineMenu: false}; }, componentDidMount: function() { window.addEventListener('click', this.clickHandler);
--- a/browser/components/loop/content/js/conversation.jsx +++ b/browser/components/loop/content/js/conversation.jsx @@ -19,17 +19,17 @@ loop.conversation = (function(OT, mozL10 * App router. * @type {loop.desktopRouter.DesktopConversationRouter} */ var router; var IncomingCallView = React.createClass({ propTypes: { - model: React.PropTypes.func.isRequired + model: React.PropTypes.object.isRequired }, getInitialState: function() { return {showDeclineMenu: false}; }, componentDidMount: function() { window.addEventListener('click', this.clickHandler);
--- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -72,32 +72,32 @@ loop.panel = (function(_, mozL10n) { 'dnd-menu': true, 'hide': !this.state.showMenu }); var availabilityText = this.state.doNotDisturb ? __("display_name_dnd_status") : __("display_name_available_status"); return ( - React.DOM.div({className: "footer component-spacer"}, - React.DOM.div({className: "do-not-disturb"}, - React.DOM.p({className: "dnd-status", onClick: this.showDropdownMenu}, - React.DOM.span(null, availabilityText), - React.DOM.i({className: availabilityStatus}) - ), - React.DOM.ul({className: availabilityDropdown, - onMouseLeave: this.hideDropdownMenu}, - React.DOM.li({onClick: this.changeAvailability("available"), - className: "dnd-menu-item dnd-make-available"}, - React.DOM.i({className: "status status-available"}), + React.DOM.div( {className:"footer component-spacer"}, + React.DOM.div( {className:"do-not-disturb"}, + React.DOM.p( {className:"dnd-status", onClick:this.showDropdownMenu}, + React.DOM.span(null, availabilityText), + React.DOM.i( {className:availabilityStatus}) + ), + React.DOM.ul( {className:availabilityDropdown, + onMouseLeave:this.hideDropdownMenu}, + React.DOM.li( {onClick:this.changeAvailability("available"), + className:"dnd-menu-item dnd-make-available"}, + React.DOM.i( {className:"status status-available"}), React.DOM.span(null, __("display_name_available_status")) - ), - React.DOM.li({onClick: this.changeAvailability("do-not-disturb"), - className: "dnd-menu-item dnd-make-unavailable"}, - React.DOM.i({className: "status status-dnd"}), + ), + React.DOM.li( {onClick:this.changeAvailability("do-not-disturb"), + className:"dnd-menu-item dnd-make-unavailable"}, + React.DOM.i( {className:"status status-dnd"}), React.DOM.span(null, __("display_name_dnd_status")) ) ) ) ) ); } }); @@ -110,36 +110,36 @@ loop.panel = (function(_, mozL10n) { render: function() { var tosHTML = __("legal_text_and_links", { "terms_of_use_url": "https://accounts.firefox.com/legal/terms", "privacy_notice_url": "www.mozilla.org/privacy/" }); if (this.state.seenToS == "unseen") { navigator.mozLoop.setLoopCharPref('seenToS', 'seen'); - return React.DOM.p({className: "terms-service", - dangerouslySetInnerHTML: {__html: tosHTML}}); + return React.DOM.p( {className:"terms-service", + dangerouslySetInnerHTML:{__html: tosHTML}}); } else { - return React.DOM.div(null); + return React.DOM.div(null ); } } }); var PanelLayout = React.createClass({displayName: 'PanelLayout', propTypes: { summary: React.PropTypes.string.isRequired }, render: function() { return ( - React.DOM.div({className: "component-spacer share generate-url"}, - React.DOM.div({className: "description"}, - React.DOM.p({className: "description-content"}, this.props.summary) - ), - React.DOM.div({className: "action"}, + React.DOM.div( {className:"component-spacer share generate-url"}, + React.DOM.div( {className:"description"}, + React.DOM.p( {className:"description-content"}, this.props.summary) + ), + React.DOM.div( {className:"action"}, this.props.children ) ) ); } }); var CallUrlResult = React.createClass({displayName: 'CallUrlResult', @@ -173,18 +173,17 @@ loop.panel = (function(_, mozL10n) { _onCallUrlReceived: function(err, callUrlData) { this.props.notifier.clear(); if (err) { this.props.notifier.errorL10n("unable_retrieve_url"); this.setState({pending: false}); } else { try { - var callUrl = new window.URL(callUrlData.callUrl || - callUrlData.call_url); + var callUrl = new window.URL(callUrlData.callUrl); // XXX the current server vers does not implement the callToken field // but it exists in the API. This workaround should be removed in the future var token = callUrlData.callToken || callUrl.pathname.split('/').pop(); navigator.mozLoop.setLoopCharPref('loopToken', token); this.setState({pending: false, callUrl: callUrl.href}); } catch(e) { @@ -197,20 +196,20 @@ loop.panel = (function(_, mozL10n) { render: function() { // XXX setting elem value from a state (in the callUrl input) // makes it immutable ie read only but that is fine in our case. // readOnly attr will suppress a warning regarding this issue // from the react lib. var cx = React.addons.classSet; return ( - PanelLayout({summary: __("share_link_header_text")}, - React.DOM.div({className: "invite"}, - React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true", - className: cx({'pending': this.state.pending})}) + PanelLayout( {summary:__("share_link_header_text")}, + React.DOM.div( {className:"invite"}, + React.DOM.input( {type:"url", value:this.state.callUrl, readOnly:"true", + className:cx({'pending': this.state.pending})} ) ) ) ); } }); /** * Panel view. @@ -219,20 +218,20 @@ loop.panel = (function(_, mozL10n) { propTypes: { notifier: React.PropTypes.object.isRequired, client: React.PropTypes.object.isRequired }, render: function() { return ( React.DOM.div(null, - CallUrlResult({client: this.props.client, - notifier: this.props.notifier}), - ToSView(null), - AvailabilityDropdown(null) + CallUrlResult( {client:this.props.client, + notifier:this.props.notifier} ), + ToSView(null ), + AvailabilityDropdown(null ) ) ); } }); var PanelRouter = loop.desktopRouter.DesktopRouter.extend({ /** * DOM document object. @@ -289,18 +288,18 @@ loop.panel = (function(_, mozL10n) { /** * Resets this router to its initial state. */ reset: function() { this._notifier.clear(); var client = new loop.Client({ baseServerUrl: navigator.mozLoop.serverUrl }); - this.loadReactComponent(PanelView({client: client, - notifier: this._notifier})); + this.loadReactComponent(PanelView( {client:client, + notifier:this._notifier} )); } }); /** * Panel initialisation. */ function init() { // Do the initial L10n setup, we do this before anything
--- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -173,18 +173,17 @@ loop.panel = (function(_, mozL10n) { _onCallUrlReceived: function(err, callUrlData) { this.props.notifier.clear(); if (err) { this.props.notifier.errorL10n("unable_retrieve_url"); this.setState({pending: false}); } else { try { - var callUrl = new window.URL(callUrlData.callUrl || - callUrlData.call_url); + var callUrl = new window.URL(callUrlData.callUrl); // XXX the current server vers does not implement the callToken field // but it exists in the API. This workaround should be removed in the future var token = callUrlData.callToken || callUrl.pathname.split('/').pop(); navigator.mozLoop.setLoopCharPref('loopToken', token); this.setState({pending: false, callUrl: callUrl.href}); } catch(e) {
--- a/browser/components/loop/content/shared/css/conversation.css +++ b/browser/components/loop/content/shared/css/conversation.css @@ -3,106 +3,106 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Conversation window styles */ .conversation { position: relative; } -.conversation .controls { +.conversation-toolbar { position: absolute; z-index: 999; /* required to have it superimposed to the video element */ left: 0; right: 0; background: rgba(0, 0, 0, .70); border: 1px solid #5a5a5a; list-style-type: none; margin: 0; padding: 0; } -.conversation .controls li { +.conversation-toolbar li { float: left; font-size: 0px; /* prevents vertical bottom padding added to buttons in google chrome */ } -.conversation .controls .btn { +.conversation-toolbar .btn { width: 40px; height: 30px; background: transparent; background-repeat: no-repeat; background-position: 12px 8px; background-size: 14px 14px; border-right: 1px solid #5a5a5a; border-radius: 0; cursor: pointer; } -.conversation .controls .btn:hover { +.conversation-toolbar .btn:hover { background-color: rgba(255, 255, 255, .35); } /* Hangup button */ -.conversation .controls .btn-hangup { +.conversation-toolbar .btn-hangup { background-color: #D74345; background-image: url(../img/hangup-inverse-14x14.png); } -.conversation .controls .btn-hangup:hover { +.conversation-toolbar .btn-hangup:hover { background-color: #C53436; } @media (min-resolution: 2dppx) { - .conversation .controls .btn-hangup { + .conversation-toolbar .btn-hangup { background-image: url(../img/hangup-inverse-14x14@2x.png); } } /* Common media control buttons behavior */ -.conversation .controls .media-control { +.conversation-toolbar .media-control { background-color: transparent; opacity: 1; } -.conversation .controls .media-control:hover { +.conversation-toolbar .media-control:hover { background-color: rgba(255, 255, 255, .35); opacity: 1; } -.conversation .controls .media-control.muted { +.conversation-toolbar .media-control.muted { background-color: #0096DD; opacity: 1; } /* Audio mute button */ -.conversation .controls .local-media.btn-mute-audio { +.conversation-toolbar .local-media.btn-mute-audio { background-image: url(../img/audio-inverse-14x14.png); } -.conversation .controls .local-media.btn-mute-audio.muted { +.conversation-toolbar .local-media.btn-mute-audio.muted { background-image: url(../img/mute-inverse-14x14.png); } @media (min-resolution: 2dppx) { - .conversation .controls .local-media.btn-mute-audio { + .conversation-toolbar .local-media.btn-mute-audio { background-image: url(../img/audio-inverse-14x14@2x.png); } - .conversation .controls .local-media.btn-mute-audio.muted { + .conversation-toolbar .local-media.btn-mute-audio.muted { background-image: url(../img/mute-inverse-14x14@2x.png); } } /* Video mute button */ -.conversation .controls .local-media.btn-mute-video { +.conversation-toolbar .local-media.btn-mute-video { background-image: url(../img/video-inverse-14x14.png); } -.conversation .controls .local-media.btn-mute-video.muted { +.conversation-toolbar .local-media.btn-mute-video.muted { background-image: url(../img/facemute-14x14.png); } @media (min-resolution: 2dppx) { - .conversation .controls .local-media.btn-mute-video { + .conversation-toolbar .local-media.btn-mute-video { background-image: url(../img/video-inverse-14x14@2x.png); } - .conversation .controls .local-media.btn-mute-video.muted { + .conversation-toolbar .local-media.btn-mute-video.muted { background-image: url(../img/facemute-14x14@2x.png); } } /* Video elements */ .conversation .media video { background: #eee;
deleted file mode 100644 --- a/browser/components/loop/content/shared/css/readme.html +++ /dev/null @@ -1,274 +0,0 @@ - <!DOCTYPE html> -<!-- 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/. --> - -<!-- This file is intended to help frontend developers to easily identify what - are the available styles for the Loop UI components. --> -<html> -<head> - <meta charset="utf-8"> - <title>Loop UI shared CSS information/demo</title> - <link type="text/css" rel="stylesheet" href="common.css"> - <link type="text/css" rel="stylesheet" href="panel.css"> - <link type="text/css" rel="stylesheet" href="conversation.css"> - <style> - body { - width: 600px; - margin: 1em auto; - background: #fff; - font-size: 12px; - } - h2 { - margin-top: 3em; - } - </style> -</head> -<body> - <h1>Loop UI toolkit</h1> - - <h2>Logo icons</h2> - - <p> - <img src="../img/icon_32.png"> 32x32 transparent PNG - <img src="../img/icon_64.png"> 64x64 transparent PNG - </p> - - <p><em><strong>Note:</strong> these are temporary.</em></p> - - <h2>Share panel</h2> - - <h3>Simple</h3> - - <div class="share"> - <form class="description"> - <p>This is a simple message.</p> - </form> - <div class="action"> - <p><input type="url" value="http://loop.im/plop75"></p> - <p>Your name will appear as <a href="">Unnamed</a>.</p> - </div> - </div> - - <h3>Featuring options</h3> - - <div class="share"> - <form class="description"> - <p class="field"> - <label>Share this link with a friend to - <select> - <option>browse together</option> - <option selected="selected">video chat</option> - <option>audio chat</option> - <option>text chat</option> - </select> - </label> - </p> - <p class="field"> - <label> - Use webcam <select><option>Foo</option></select> - </label> - </p> - <p class="field"> - <label>Use whatever - <select><option>Long foo is long indeed</option></select> - </label> - </p> - <p class="preview cf"> - Preview <video></video> - </p> - </form> - <div class="action"> - <p><input type="url" value="http://loop.im/plop75"></p> - <p>Your name will appear as <a href="">Unnamed</a>.</p> - </div> - </div> - - <h2>Conversation window</h2> - - <p><em>The conversation component adapts automatically to its container to - occupy all the available space.</em></p> - - <h3>Large</h3> - - <div class="conversation"> - <div class="media nested"> - <div class="remote_wrapper"> - <video class="remote"></video> - </div> - <video class="local"></video> - </div> - </div> - - <h3>Large with controls</h3> - - <div class="conversation"> - <ul class="controls"> - <li><button class="btn btn-hangup" title="Hangup"></button></li> - <li><button class="btn media-control local-media btn-mute-video" title="Mute video"></button></li> - <li><button class="btn media-control local-media btn-mute-audio" title="Mute audio"></button></li> - </ul> - <div class="media nested"> - <div class="remote_wrapper"> - <video class="remote"></video> - </div> - <video class="local"></video> - </div> - </div> - - <h3>Small (think chat window)</h3> - - <div style="width: 204px"> - <div class="conversation"> - <ul class="controls"> - <li><button class="btn btn-hangup" title="Hangup"></button></li> - <li><button class="btn media-control local-media btn-mute-video" title="Mute video"></button></li> - <li><button class="btn media-control local-media btn-mute-audio" title="Mute audio"></button></li> - </ul> - <div class="media nested"> - <div class="remote_wrapper"> - <video class="remote"></video> - </div> - <video class="local"></video> - </div> - </div> - </div> - - <h3>Side by side</h3> - - <div class="conversation"> - <div class="media side-by-side"> - <div class="remote_wrapper"> - <video class="remote"></video> - </div> - <video class="local"></video> - </div> - </div> - - <h2>Controls button variants</h2> - - <h3>Nothing muted</h3> - - <div style="width: 204px; min-height: 26px"> - <div class="conversation"> - <ul class="controls"> - <li><button class="btn btn-hangup" title="Hangup"></button></li> - <li><button class="btn media-control local-media btn-mute-video" title="Mute video"></button></li> - <li><button class="btn media-control local-media btn-mute-audio" title="Mute audio"></button></li> - </ul> - </div> - </div> - - <h3>Local audio muted</h3> - - <div style="width: 204px; min-height: 26px"> - <div class="conversation"> - <ul class="controls"> - <li><button class="btn btn-hangup" title="Hangup"></button></li> - <li><button class="btn media-control local-media btn-mute-video" title="Mute video"></button></li> - <li><button class="btn media-control local-media btn-mute-audio muted" title="Mute audio"></button></li> - </ul> - </div> - </div> - - <h3>Local video muted</h3> - - <div style="width: 204px; min-height: 26px"> - <div class="conversation"> - <ul class="controls"> - <li><button class="btn btn-hangup" title="Hangup"></button></li> - <li><button class="btn media-control local-media btn-mute-video muted" title="Mute video"></button></li> - <li><button class="btn media-control local-media btn-mute-audio" title="Mute audio"></button></li> - </ul> - </div> - </div> - - <h2>Expired call url view</h2> - - <div class="expired-url-info"> - <div class="info-panel"> - <div class="firefox-logo"></div> - <h1 >Oops!</h1> - <h4 >This URL is unavailable.</h4> - </div> - <div class="promote-firefox"> - <h3>Download Firefox to make free audio and video calls!</h3> - <p> - <a class="btn btn-large btn-success" href="https://www.mozilla.org/firefox/" data-reactid=".0.1.1.0">Get Firefox</a> - </p> - </div> - </div> - - <h2>Buttons</h2> - - <h3>Using <code><a></code></h3> - - <p> - <a href="" class="btn">default</a> - <a href="" class="btn btn-info">info</a> - <a href="" class="btn btn-success">success</a> - <a href="" class="btn btn-warning">warning</a> - <a href="" class="btn btn-error">error</a> - </p> - - <h3>Inline</h3> - - <p>Click <a href="" class="btn btn-info">here</a>.</p> - - <h3>Using <code><button></code></h3> - - <p> - <button class="btn">default</button> - <button class="btn btn-info">info</button> - <button class="btn btn-success">success</button> - <button class="btn btn-warning">warning</button> - <button class="btn btn-error">error</button> - </p> - - <h3>Large buttons</h3> - - <p> - <a class="btn btn-large">default</a> - <a class="btn btn-large btn-info">info</a> - <a class="btn btn-large btn-success">success</a> - <a class="btn btn-large btn-warning">warning</a> - <a class="btn btn-large btn-error">error</a> - </p> - - <h2>Alerts</h2> - - <div class="alert alert-error"> - <button class="close"></button> - <p class="message">Oops! Something went really wrong.</p> - </div> - - <div class="alert alert-warning"> - <button class="close"></button> - <p class="message">Oops! This is a warning.</p> - </div> - - <h2>Logos</h2> - - <h3>Centered Firefox logo</h3> - - <div class="firefox-logo"></div> - - <h2>Incoming call</h2> - - <div class="incoming-call"> - <h2>Incoming call</h2> - <p> - <button class="btn btn-success btn-accept">Accept</button> - <button class="btn btn-error btn-decline">Decline</button> - </p> - </div> - - <script> - window.onload = function() { - [].forEach.call(document.querySelectorAll("video"), function(video) { - video.setAttribute("src", "http://v2v.cc/~j/theora_testsuite/320x240.ogg"); - }); - }; - </script> -</body> -</html>
--- a/browser/components/loop/content/shared/js/views.js +++ b/browser/components/loop/content/shared/js/views.js @@ -175,17 +175,17 @@ loop.shared.views = (function(_, OT, l10 handleToggleAudio: function() { this.props.publishStream("audio", !this.props.audio.enabled); }, render: function() { /* jshint ignore:start */ return ( - React.DOM.ul({className: "controls"}, + React.DOM.ul({className: "conversation-toolbar"}, React.DOM.li(null, React.DOM.button({className: "btn btn-hangup", onClick: this.handleClickHangup, title: __("hangup_button_title")})), React.DOM.li(null, MediaControlButton({action: this.handleToggleVideo, enabled: this.props.video.enabled, scope: "local", type: "video"})), React.DOM.li(null, MediaControlButton({action: this.handleToggleAudio, enabled: this.props.audio.enabled,
--- a/browser/components/loop/content/shared/js/views.jsx +++ b/browser/components/loop/content/shared/js/views.jsx @@ -175,17 +175,17 @@ loop.shared.views = (function(_, OT, l10 handleToggleAudio: function() { this.props.publishStream("audio", !this.props.audio.enabled); }, render: function() { /* jshint ignore:start */ return ( - <ul className="controls"> + <ul className="conversation-toolbar"> <li><button className="btn btn-hangup" onClick={this.handleClickHangup} title={__("hangup_button_title")}></button></li> <li><MediaControlButton action={this.handleToggleVideo} enabled={this.props.video.enabled} scope="local" type="video" /></li> <li><MediaControlButton action={this.handleToggleAudio} enabled={this.props.audio.enabled}
--- a/browser/components/loop/standalone/content/js/webapp.js +++ b/browser/components/loop/standalone/content/js/webapp.js @@ -37,24 +37,24 @@ loop.webapp = (function($, _, OT, webL10 */ var PromoteFirefoxView = React.createClass({displayName: 'PromoteFirefoxView', propTypes: { helper: React.PropTypes.object.isRequired }, render: function() { if (this.props.helper.isFirefox(navigator.userAgent)) { - return React.DOM.div(null ); + return React.DOM.div(null); } return ( - React.DOM.div( {className:"promote-firefox"}, - React.DOM.h3(null, __("promote_firefox_hello_heading")), + React.DOM.div({className: "promote-firefox"}, + React.DOM.h3(null, __("promote_firefox_hello_heading")), React.DOM.p(null, - React.DOM.a( {className:"btn btn-large btn-success", - href:"https://www.mozilla.org/firefox/"}, + React.DOM.a({className: "btn btn-large btn-success", + href: "https://www.mozilla.org/firefox/"}, __("get_firefox_button") ) ) ) ); } }); @@ -64,23 +64,23 @@ loop.webapp = (function($, _, OT, webL10 var CallUrlExpiredView = React.createClass({displayName: 'CallUrlExpiredView', propTypes: { helper: React.PropTypes.object.isRequired }, render: function() { /* jshint ignore:start */ return ( - React.DOM.div( {className:"expired-url-info"}, - React.DOM.div( {className:"info-panel"}, - React.DOM.div( {className:"firefox-logo"} ), - React.DOM.h1(null, __("call_url_unavailable_notification_heading")), + React.DOM.div({className: "expired-url-info"}, + React.DOM.div({className: "info-panel"}, + React.DOM.div({className: "firefox-logo"}), + React.DOM.h1(null, __("call_url_unavailable_notification_heading")), React.DOM.h4(null, __("call_url_unavailable_notification_message")) - ), - PromoteFirefoxView( {helper:this.props.helper} ) + ), + PromoteFirefoxView({helper: this.props.helper}) ) ); /* jshint ignore:end */ } }); /** * Conversation launcher view. A ConversationModel is associated and attached
--- a/browser/components/loop/test/desktop-local/panel_test.js +++ b/browser/components/loop/test/desktop-local/panel_test.js @@ -195,17 +195,17 @@ describe("loop.panel", function() { }); }); describe("loop.panel.PanelView", function() { var fakeClient, callUrlData, view; beforeEach(function() { callUrlData = { - call_url: "http://call.invalid/", + callUrl: "http://call.invalid/", expiresAt: 1000 }; fakeClient = { requestCallUrl: function(_, cb) { cb(null, callUrlData); } }; @@ -224,17 +224,17 @@ describe("loop.panel", function() { }); describe("loop.panel.CallUrlResult", function() { var fakeClient, callUrlData, view; beforeEach(function() { callUrlData = { - call_url: "http://call.invalid/fakeToken", + callUrl: "http://call.invalid/fakeToken", expiresAt: 1000 }; fakeClient = { requestCallUrl: function(_, cb) { cb(null, callUrlData); } }; @@ -267,28 +267,28 @@ describe("loop.panel", function() { client: fakeClient })); expect(view.state.pending).eql(true); }); it("should update state with the call url received", function() { expect(view.state.pending).eql(false); - expect(view.state.callUrl).eql(callUrlData.call_url); + expect(view.state.callUrl).eql(callUrlData.callUrl); }); it("should clear the pending state when a response is received", function() { expect(view.state.pending).eql(false); }); it("should update CallUrlResult with the call url", function() { var urlField = view.getDOMNode().querySelector("input[type='url']"); - expect(urlField.value).eql(callUrlData.call_url); + expect(urlField.value).eql(callUrlData.callUrl); }); it("should reset all pending notifications", function() { sinon.assert.calledOnce(view.props.notifier.clear); }); it("should notify the user when the operation failed", function() { fakeClient.requestCallUrl = function(_, cb) {
--- a/browser/components/loop/test/xpcshell/head.js +++ b/browser/components/loop/test/xpcshell/head.js @@ -42,22 +42,23 @@ function setupFakeLoopServer() { * This is used to fake push registration and notifications for * MozLoopService tests. There is only one object created per test instance, as * once registration has taken place, the object cannot currently be changed. */ let mockPushHandler = { // This sets the registration result to be returned when initialize // is called. By default, it is equivalent to success. registrationResult: null, + registrationPushURL: undefined, /** * MozLoopPushHandler API */ initialize: function(registerCallback, notificationCallback) { - registerCallback(this.registrationResult); + registerCallback(this.registrationResult, this.registrationPushURL); this._notificationCallback = notificationCallback; }, /** * Test-only API to simplify notifying a push notification result. */ notify: function(version) { this._notificationCallback(version);
--- a/browser/components/loop/test/xpcshell/test_loopservice_registration.js +++ b/browser/components/loop/test/xpcshell/test_loopservice_registration.js @@ -1,11 +1,13 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +Cu.import("resource://services-common/utils.js"); + /** * This file is to test general registration. Note that once successful * registration has taken place, we can no longer test the server side * parts as the service protects against this, hence, they need testing in * other test files. */ /** @@ -44,17 +46,24 @@ add_test(function test_register_websocke }); }); /** * Tests that we get a success response when both websocket and Loop server * registration are complete. */ add_test(function test_register_success() { + mockPushHandler.registrationPushURL = kEndPointUrl; + loopServer.registerPathHandler("/registration", (request, response) => { + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + let data = JSON.parse(body); + Assert.equal(data.simplePushURL, kEndPointUrl, + "Should send correct push url"); + response.setStatusLine(null, 200, "OK"); response.processAsync(); response.finish(); }); MozLoopService.register(mockPushHandler).then(() => { run_next_test(); }, err => { do_throw("shouldn't error on a successful request");
new file mode 100644 --- /dev/null +++ b/browser/components/loop/ui/README.md @@ -0,0 +1,10 @@ +Loop UI Components Showcase +=========================== + +This app file showcases all Loop's view components. + +If you want to modify the app, launch the following command: + + browser/components/loop/build-jsx --watch + +And start editing the `ui-showcase.jsx` file.
new file mode 100644 --- /dev/null +++ b/browser/components/loop/ui/fake-l10n.js @@ -0,0 +1,14 @@ +/* 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/. */ + +/** + * /!\ FIXME: THIS IS A HORRID HACK which fakes both the mozL10n and webL10n + * objects and makes them returning "fake string" for any requested string id. + * @type {Object} + */ +document.webL10n = document.mozL10n = { + get: function() { + return "fake text"; + } +};
new file mode 100644 --- /dev/null +++ b/browser/components/loop/ui/fake-mozLoop.js @@ -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/. */ + +/** + * Faking the mozLoop object which doesn't exist in regular web pages. + * @type {Object} + */ +navigator.mozLoop = { + getLoopCharPref: function() {} +};
new file mode 100644 --- /dev/null +++ b/browser/components/loop/ui/index.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<!-- 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/. --> +<html> + <head> + <meta charset="utf-8"> + <title>Loop UI Components Showcase</title> + <link rel="stylesheet" type="text/css" href="../content/shared/css/common.css"> + <link rel="stylesheet" type="text/css" href="../content/shared/css/conversation.css"> + <link rel="stylesheet" type="text/css" href="../content/shared/css/panel.css"> + <link rel="stylesheet" type="text/css" href="ui-showcase.css"> + </head> + <body> + <div id="main"></div> + <script src="fake-mozLoop.js"></script> + <script src="fake-l10n.js"></script> + <script src="../content/libs/sdk.js"></script> + <script src="../content/shared/libs/react-0.10.0.js"></script> + <script src="../content/shared/libs/jquery-2.1.0.js"></script> + <script src="../content/shared/libs/lodash-2.4.1.js"></script> + <script src="../content/shared/libs/backbone-1.1.2.js"></script> + <script src="../content/shared/js/models.js"></script> + <script src="../content/shared/js/router.js"></script> + <script src="../content/shared/js/views.js"></script> + <script src="../content/js/client.js"></script> + <script src="../content/js/desktopRouter.js"></script> + <script src="../standalone/content/js/webapp.js"></script> + <script src="../content/js/panel.js"></script> + <script src="../content/js/conversation.js"></script> + <script src="ui-showcase.js"></script> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/browser/components/loop/ui/ui-showcase.css @@ -0,0 +1,47 @@ +/* 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/. */ + +.showcase { + width: 730px; + margin: 0 auto; +} + +.showcase > header { + position: fixed; + top: 0; + background-color: #fbfbfb; + z-index: 1000; + width: 100%; + padding-bottom: 1em; +} + +.showcase .menu > a { + margin-right: .5em; +} + +.showcase > section { + position: relative; + padding-top: 10em; + clear: both; +} + +.showcase > section > h1 { + border-bottom: 1px solid #aaa; +} + +.showcase > section .comp { + margin: 0 auto; /* width is usually set programmatically */ +} + +.showcase > section .comp.dashed { + border: 1px dashed #ccc; +} + +.showcase > section > .example { + margin-bottom: 6em; +} + +.showcase > section .example > h3 { + border-bottom: 1px dashed #aaa; +}
new file mode 100644 --- /dev/null +++ b/browser/components/loop/ui/ui-showcase.js @@ -0,0 +1,133 @@ +/** @jsx React.DOM */ + +/* 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/. */ + +/* jshint newcap:false */ +/* global loop:true, React */ + +(function() { + "use strict"; + + // 1. Desktop components + // 1.1 Panel + var PanelView = loop.panel.PanelView; + // 1.2. Conversation Window + var IncomingCallView = loop.conversation.IncomingCallView; + + // 2. Standalone webapp + var CallUrlExpiredView = loop.webapp.CallUrlExpiredView; + + // 3. Shared components + var ConversationToolbar = loop.shared.views.ConversationToolbar; + var ConversationView = loop.shared.views.ConversationView; + + // Local helpers + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } + + var Example = React.createClass({displayName: 'Example', + render: function() { + var cx = React.addons.classSet; + return ( + React.DOM.div({className: "example"}, + React.DOM.h3(null, this.props.summary), + React.DOM.div({className: cx({comp: true, dashed: this.props.dashed}), + style: this.props.style || {}}, + this.props.children + ) + ) + ); + } + }); + + var Section = React.createClass({displayName: 'Section', + render: function() { + return ( + React.DOM.section({id: this.props.name}, + React.DOM.h1(null, this.props.name), + this.props.children + ) + ); + } + }); + + var ShowCase = React.createClass({displayName: 'ShowCase', + render: function() { + return ( + React.DOM.div({className: "showcase"}, + React.DOM.header(null, + React.DOM.h1(null, "Loop UI Components Showcase"), + React.DOM.nav({className: "menu"}, + React.Children.map(this.props.children, function(section) { + return ( + React.DOM.a({className: "btn btn-info", href: "#" + section.props.name}, + section.props.name + ) + ); + }) + ) + ), + this.props.children + ) + ); + } + }); + + var App = React.createClass({displayName: 'App', + render: function() { + return ( + ShowCase(null, + Section({name: "PanelView"}, + Example({summary: "332px wide", dashed: "true", style: {width: "332px"}}, + PanelView(null) + ) + ), + + Section({name: "IncomingCallView"}, + Example({summary: "Default", dashed: "true", style: {width: "280px"}}, + IncomingCallView(null) + ) + ), + + Section({name: "ConversationToolbar"}, + Example({summary: "Default"}, + ConversationToolbar({video: {enabled: true}, audio: {enabled: true}}) + ), + Example({summary: "Video muted"}, + ConversationToolbar({video: {enabled: false}, audio: {enabled: true}}) + ), + Example({summary: "Audio muted"}, + ConversationToolbar({video: {enabled: true}, audio: {enabled: false}}) + ) + ), + + Section({name: "ConversationView"}, + Example({summary: "Default"}, + ConversationView({video: {enabled: true}, audio: {enabled: true}}) + ) + ), + + Section({name: "CallUrlExpiredView"}, + Example({summary: "Firefox User"}, + CallUrlExpiredView({helper: {isFirefox: returnTrue}}) + ), + Example({summary: "Non-Firefox User"}, + CallUrlExpiredView({helper: {isFirefox: returnFalse}}) + ) + ) + ) + ); + } + }); + + window.addEventListener("DOMContentLoaded", function() { + React.renderComponent(App(null), document.body); + }); +})();
new file mode 100644 --- /dev/null +++ b/browser/components/loop/ui/ui-showcase.jsx @@ -0,0 +1,133 @@ +/** @jsx React.DOM */ + +/* 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/. */ + +/* jshint newcap:false */ +/* global loop:true, React */ + +(function() { + "use strict"; + + // 1. Desktop components + // 1.1 Panel + var PanelView = loop.panel.PanelView; + // 1.2. Conversation Window + var IncomingCallView = loop.conversation.IncomingCallView; + + // 2. Standalone webapp + var CallUrlExpiredView = loop.webapp.CallUrlExpiredView; + + // 3. Shared components + var ConversationToolbar = loop.shared.views.ConversationToolbar; + var ConversationView = loop.shared.views.ConversationView; + + // Local helpers + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } + + var Example = React.createClass({ + render: function() { + var cx = React.addons.classSet; + return ( + <div className="example"> + <h3>{this.props.summary}</h3> + <div className={cx({comp: true, dashed: this.props.dashed})} + style={this.props.style || {}}> + {this.props.children} + </div> + </div> + ); + } + }); + + var Section = React.createClass({ + render: function() { + return ( + <section id={this.props.name}> + <h1>{this.props.name}</h1> + {this.props.children} + </section> + ); + } + }); + + var ShowCase = React.createClass({ + render: function() { + return ( + <div className="showcase"> + <header> + <h1>Loop UI Components Showcase</h1> + <nav className="menu">{ + React.Children.map(this.props.children, function(section) { + return ( + <a className="btn btn-info" href={"#" + section.props.name}> + {section.props.name} + </a> + ); + }) + }</nav> + </header> + {this.props.children} + </div> + ); + } + }); + + var App = React.createClass({ + render: function() { + return ( + <ShowCase> + <Section name="PanelView"> + <Example summary="332px wide" dashed="true" style={{width: "332px"}}> + <PanelView /> + </Example> + </Section> + + <Section name="IncomingCallView"> + <Example summary="Default" dashed="true" style={{width: "280px"}}> + <IncomingCallView /> + </Example> + </Section> + + <Section name="ConversationToolbar"> + <Example summary="Default"> + <ConversationToolbar video={{enabled: true}} audio={{enabled: true}} /> + </Example> + <Example summary="Video muted"> + <ConversationToolbar video={{enabled: false}} audio={{enabled: true}} /> + </Example> + <Example summary="Audio muted"> + <ConversationToolbar video={{enabled: true}} audio={{enabled: false}} /> + </Example> + </Section> + + <Section name="ConversationView"> + <Example summary="Default"> + <ConversationView video={{enabled: true}} audio={{enabled: true}} /> + </Example> + </Section> + + <Section name="CallUrlExpiredView"> + <Example summary="Firefox User"> + <CallUrlExpiredView helper={{isFirefox: returnTrue}} /> + </Example> + <Example summary="Non-Firefox User"> + <CallUrlExpiredView helper={{isFirefox: returnFalse}} /> + </Example> + </Section> + </ShowCase> + ); + } + }); + + window.addEventListener("DOMContentLoaded", function() { + React.renderComponent(<App />, document.body); + }); +})();
--- a/browser/devtools/projecteditor/moz.build +++ b/browser/devtools/projecteditor/moz.build @@ -1,6 +1,6 @@ # vim: set filetype=python: # 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/. -TEST_DIRS += ['test'] \ No newline at end of file +BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
deleted file mode 100644 --- a/browser/devtools/projecteditor/test/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -BROWSER_CHROME_MANIFESTS += ['browser.ini'] -
--- a/browser/extensions/moz.build +++ b/browser/extensions/moz.build @@ -1,8 +1,7 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. -TEST_DIRS += ['pdfjs/test'] - +BROWSER_CHROME_MANIFESTS += ['pdfjs/test/browser.ini']
deleted file mode 100644 --- a/browser/extensions/pdfjs/test/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -BROWSER_CHROME_MANIFESTS += ['browser.ini'] -
--- a/browser/fuel/moz.build +++ b/browser/fuel/moz.build @@ -1,8 +1,8 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. DIRS += ['public', 'src'] -TEST_DIRS += ['test'] +BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
deleted file mode 100644 --- a/browser/fuel/test/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -BROWSER_CHROME_MANIFESTS += ['browser.ini'] -
--- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -1,15 +1,17 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. -TEST_DIRS += ['test'] +BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] +MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini'] +XPCSHELL_TESTS_MANIFESTS += ['test/unit/social/xpcshell.ini'] EXTRA_JS_MODULES += [ 'BrowserNewTabPreloader.jsm', 'BrowserUITelemetry.jsm', 'Chat.jsm', 'ContentClick.jsm', 'ContentLinkHandler.jsm', 'ContentSearch.jsm',
deleted file mode 100644 --- a/browser/modules/test/chrome/moz.build +++ /dev/null @@ -1,8 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] -
deleted file mode 100644 --- a/browser/modules/test/moz.build +++ /dev/null @@ -1,10 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -DIRS += ['chrome', 'unit'] - -BROWSER_CHROME_MANIFESTS += ['browser.ini'] -
deleted file mode 100644 --- a/browser/modules/test/unit/moz.build +++ /dev/null @@ -1,7 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -XPCSHELL_TESTS_MANIFESTS += ['social/xpcshell.ini']
--- a/caps/nsNullPrincipal.cpp +++ b/caps/nsNullPrincipal.cpp @@ -13,16 +13,18 @@ #include "nsNullPrincipal.h" #include "nsNullPrincipalURI.h" #include "nsMemory.h" #include "nsIUUIDGenerator.h" #include "nsID.h" #include "nsNetUtil.h" #include "nsIClassInfoImpl.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" #include "nsNetCID.h" #include "nsError.h" #include "nsIScriptSecurityManager.h" #include "nsPrincipal.h" #include "nsScriptSecurityManager.h" #include "pratom.h" using namespace mozilla; @@ -61,21 +63,34 @@ nsNullPrincipal::Release() nsNullPrincipal::nsNullPrincipal() { } nsNullPrincipal::~nsNullPrincipal() { } +/* static */ already_AddRefed<nsNullPrincipal> +nsNullPrincipal::CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom) +{ + nsRefPtr<nsNullPrincipal> nullPrin = new nsNullPrincipal(); + nsresult rv = nullPrin->Init(aInheritFrom->GetAppId(), + aInheritFrom->GetIsInBrowserElement()); + return NS_SUCCEEDED(rv) ? nullPrin.forget() : nullptr; +} + #define NS_NULLPRINCIPAL_PREFIX NS_NULLPRINCIPAL_SCHEME ":" nsresult -nsNullPrincipal::Init() +nsNullPrincipal::Init(uint32_t aAppId, bool aInMozBrowser) { + MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); + mAppId = aAppId; + mInMozBrowser = aInMozBrowser; + // FIXME: bug 327161 -- make sure the uuid generator is reseeding-resistant. nsresult rv; nsCOMPtr<nsIUUIDGenerator> uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsID id; rv = uuidgen->GenerateUUIDInPlace(&id); @@ -251,31 +266,31 @@ nsNullPrincipal::GetJarPrefix(nsACString { aJarPrefix.Truncate(); return NS_OK; } NS_IMETHODIMP nsNullPrincipal::GetAppStatus(uint16_t* aAppStatus) { - *aAppStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED; + *aAppStatus = nsScriptSecurityManager::AppStatusForPrincipal(this); return NS_OK; } NS_IMETHODIMP nsNullPrincipal::GetAppId(uint32_t* aAppId) { - *aAppId = nsIScriptSecurityManager::NO_APP_ID; + *aAppId = mAppId; return NS_OK; } NS_IMETHODIMP nsNullPrincipal::GetIsInBrowserElement(bool* aIsInBrowserElement) { - *aIsInBrowserElement = false; + *aIsInBrowserElement = mInMozBrowser; return NS_OK; } NS_IMETHODIMP nsNullPrincipal::GetUnknownAppId(bool* aUnknownAppId) { *aUnknownAppId = false; return NS_OK; @@ -296,21 +311,29 @@ nsNullPrincipal::GetBaseDomain(nsACStrin } /** * nsISerializable implementation */ NS_IMETHODIMP nsNullPrincipal::Read(nsIObjectInputStream* aStream) { - // no-op: CID is sufficient to create a useful nsNullPrincipal, since the URI - // is not really relevant. + // Note - nsNullPrincipal use NS_GENERIC_FACTORY_CONSTRUCTOR_INIT, which means + // that the Init() method has already been invoked by the time we deserialize. + // This is in contrast to nsPrincipal, which uses NS_GENERIC_FACTORY_CONSTRUCTOR, + // in which case ::Read needs to invoke Init(). + nsresult rv = aStream->Read32(&mAppId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aStream->ReadBoolean(&mInMozBrowser); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; } NS_IMETHODIMP nsNullPrincipal::Write(nsIObjectOutputStream* aStream) { - // no-op: CID is sufficient to create a useful nsNullPrincipal, since the URI - // is not really relevant. + aStream->Write32(mAppId); + aStream->WriteBoolean(mInMozBrowser); return NS_OK; }
--- a/caps/nsNullPrincipal.h +++ b/caps/nsNullPrincipal.h @@ -9,24 +9,25 @@ * same-origin with anything but themselves. */ #ifndef nsNullPrincipal_h__ #define nsNullPrincipal_h__ #include "nsIPrincipal.h" #include "nsJSPrincipals.h" +#include "nsIScriptSecurityManager.h" #include "nsCOMPtr.h" #include "nsIContentSecurityPolicy.h" class nsIURI; #define NS_NULLPRINCIPAL_CID \ -{ 0xdd156d62, 0xd26f, 0x4441, \ - { 0x9c, 0xdb, 0xe8, 0xf0, 0x91, 0x07, 0xc2, 0x73 } } +{ 0xa0bd8b42, 0xf6bf, 0x4fb9, \ + { 0x93, 0x42, 0x90, 0xbf, 0xc9, 0xb7, 0xa1, 0xab } } #define NS_NULLPRINCIPAL_CONTRACTID "@mozilla.org/nullprincipal;1" #define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal" class nsNullPrincipal : public nsJSPrincipals { public: nsNullPrincipal(); @@ -36,24 +37,29 @@ public: // FIXME: bug 327245 -- I sorta wish there were a clean way to share the // nsJSPrincipals munging code between the various principal classes without // giving up the NS_DECL_NSIPRINCIPAL goodness. NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIPRINCIPAL NS_DECL_NSISERIALIZABLE - nsresult Init(); + static already_AddRefed<nsNullPrincipal> CreateWithInheritedAttributes(nsIPrincipal *aInheritFrom); + + nsresult Init(uint32_t aAppId = nsIScriptSecurityManager::NO_APP_ID, + bool aInMozBrowser = false); virtual void GetScriptLocation(nsACString &aStr) MOZ_OVERRIDE; #ifdef DEBUG virtual void dumpImpl() MOZ_OVERRIDE; #endif protected: virtual ~nsNullPrincipal(); nsCOMPtr<nsIURI> mURI; nsCOMPtr<nsIContentSecurityPolicy> mCSP; + uint32_t mAppId; + bool mInMozBrowser; }; #endif // nsNullPrincipal_h__
--- a/caps/nsPrincipal.cpp +++ b/caps/nsPrincipal.cpp @@ -572,58 +572,21 @@ nsPrincipal::Write(nsIObjectOutputStream // on the deserialized URIs in Read(). return NS_OK; } uint16_t nsPrincipal::GetAppStatus() { - NS_WARN_IF_FALSE(mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, - "Asking for app status on a principal with an unknown app id"); - // Installed apps have a valid app id (not NO_APP_ID or UNKNOWN_APP_ID) - // and they are not inside a mozbrowser. - if (mAppId == nsIScriptSecurityManager::NO_APP_ID || - mAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID || mInMozBrowser) { + if (mAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { + NS_WARNING("Asking for app status on a principal with an unknown app id"); return nsIPrincipal::APP_STATUS_NOT_INSTALLED; } - - nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID); - NS_ENSURE_TRUE(appsService, nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - nsCOMPtr<mozIApplication> app; - appsService->GetAppByLocalId(mAppId, getter_AddRefs(app)); - NS_ENSURE_TRUE(app, nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - uint16_t status = nsIPrincipal::APP_STATUS_INSTALLED; - NS_ENSURE_SUCCESS(app->GetAppStatus(&status), - nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - nsAutoCString origin; - NS_ENSURE_SUCCESS(GetOrigin(getter_Copies(origin)), - nsIPrincipal::APP_STATUS_NOT_INSTALLED); - nsString appOrigin; - NS_ENSURE_SUCCESS(app->GetOrigin(appOrigin), - nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - // We go from string -> nsIURI -> origin to be sure we - // compare two punny-encoded origins. - nsCOMPtr<nsIURI> appURI; - NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(appURI), appOrigin), - nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - nsAutoCString appOriginPunned; - NS_ENSURE_SUCCESS(GetOriginForURI(appURI, getter_Copies(appOriginPunned)), - nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - if (!appOriginPunned.Equals(origin)) { - return nsIPrincipal::APP_STATUS_NOT_INSTALLED; - } - - return status; + return nsScriptSecurityManager::AppStatusForPrincipal(this); } /************************************************************************************************************************/ static const char EXPANDED_PRINCIPAL_SPEC[] = "[Expanded Principal]"; NS_IMPL_CLASSINFO(nsExpandedPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, NS_EXPANDEDPRINCIPAL_CID)
--- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -6,16 +6,17 @@ #include "nsScriptSecurityManager.h" #include "mozilla/ArrayUtils.h" #include "js/OldDebugAPI.h" #include "xpcprivate.h" #include "XPCWrapper.h" +#include "nsIAppsService.h" #include "nsILoadContext.h" #include "nsIServiceManager.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptContext.h" #include "nsIURL.h" #include "nsINestedURI.h" #include "nspr.h" #include "nsJSPrincipals.h" @@ -51,16 +52,17 @@ #include "nsDOMJSUtils.h" #include "nsAboutProtocolUtils.h" #include "nsIClassInfo.h" #include "nsIURIFixup.h" #include "nsCDefaultURIFixup.h" #include "nsIChromeRegistry.h" #include "nsIContentSecurityPolicy.h" #include "nsIAsyncVerifyRedirectCallback.h" +#include "mozIApplication.h" #include "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include <stdint.h> #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "nsContentUtils.h" #include "nsCxPusher.h" #include "nsJSUtils.h" @@ -247,16 +249,67 @@ nsScriptSecurityManager::SecurityCompare // SecurityHashURI is consistent with SecurityCompareURIs because NS_SecurityHashURI // is consistent with NS_SecurityCompareURIs. See nsNetUtil.h. uint32_t nsScriptSecurityManager::SecurityHashURI(nsIURI* aURI) { return NS_SecurityHashURI(aURI); } +uint16_t +nsScriptSecurityManager::AppStatusForPrincipal(nsIPrincipal *aPrin) +{ + uint32_t appId = aPrin->GetAppId(); + bool inMozBrowser = aPrin->GetIsInBrowserElement(); + NS_WARN_IF_FALSE(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + "Asking for app status on a principal with an unknown app id"); + // Installed apps have a valid app id (not NO_APP_ID or UNKNOWN_APP_ID) + // and they are not inside a mozbrowser. + if (appId == nsIScriptSecurityManager::NO_APP_ID || + appId == nsIScriptSecurityManager::UNKNOWN_APP_ID || inMozBrowser) + { + return nsIPrincipal::APP_STATUS_NOT_INSTALLED; + } + + nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(appsService, nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + nsCOMPtr<mozIApplication> app; + appsService->GetAppByLocalId(appId, getter_AddRefs(app)); + NS_ENSURE_TRUE(app, nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + uint16_t status = nsIPrincipal::APP_STATUS_INSTALLED; + NS_ENSURE_SUCCESS(app->GetAppStatus(&status), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + nsAutoCString origin; + NS_ENSURE_SUCCESS(aPrin->GetOrigin(getter_Copies(origin)), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + nsString appOrigin; + NS_ENSURE_SUCCESS(app->GetOrigin(appOrigin), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + // We go from string -> nsIURI -> origin to be sure we + // compare two punny-encoded origins. + nsCOMPtr<nsIURI> appURI; + NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(appURI), appOrigin), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + nsAutoCString appOriginPunned; + NS_ENSURE_SUCCESS(nsPrincipal::GetOriginForURI(appURI, getter_Copies(appOriginPunned)), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + if (!appOriginPunned.Equals(origin)) { + return nsIPrincipal::APP_STATUS_NOT_INSTALLED; + } + + return status; + +} + NS_IMETHODIMP nsScriptSecurityManager::GetChannelPrincipal(nsIChannel* aChannel, nsIPrincipal** aPrincipal) { NS_PRECONDITION(aChannel, "Must have channel!"); nsCOMPtr<nsISupports> owner; aChannel->GetOwner(getter_AddRefs(owner)); if (owner) { @@ -266,17 +319,21 @@ nsScriptSecurityManager::GetChannelPrinc } } // Check whether we have an nsILoadInfo that says what we should do. nsCOMPtr<nsILoadInfo> loadInfo; aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); if (loadInfo) { if (loadInfo->GetLoadingSandboxed()) { - return CallCreateInstance(NS_NULLPRINCIPAL_CONTRACTID, aPrincipal); + nsRefPtr<nsNullPrincipal> prin = + nsNullPrincipal::CreateWithInheritedAttributes(loadInfo->LoadingPrincipal()); + NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); + prin.forget(aPrincipal); + return NS_OK; } if (loadInfo->GetForceInheritPrincipal()) { NS_ADDREF(*aPrincipal = loadInfo->LoadingPrincipal()); return NS_OK; } }
--- a/caps/nsScriptSecurityManager.h +++ b/caps/nsScriptSecurityManager.h @@ -63,16 +63,18 @@ public: * Utility method for comparing two URIs. For security purposes, two URIs * are equivalent if their schemes, hosts, and ports (if any) match. This * method returns true if aSubjectURI and aObjectURI have the same origin, * false otherwise. */ static bool SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI); static uint32_t SecurityHashURI(nsIURI* aURI); + static uint16_t AppStatusForPrincipal(nsIPrincipal *aPrin); + static nsresult ReportError(JSContext* cx, const nsAString& messageTag, nsIURI* aSource, nsIURI* aTarget); static uint32_t HashPrincipalByOrigin(nsIPrincipal* aPrincipal); static bool
--- a/configure.in +++ b/configure.in @@ -8005,20 +8005,23 @@ if test "$MOZ_TREE_CAIRO"; then fi # Define macros for cairo-features.h TEE_SURFACE_FEATURE="#define CAIRO_HAS_TEE_SURFACE 1" if test "$MOZ_X11"; then XLIB_SURFACE_FEATURE="#define CAIRO_HAS_XLIB_SURFACE 1" XLIB_XRENDER_SURFACE_FEATURE="#define CAIRO_HAS_XLIB_XRENDER_SURFACE 1" PS_SURFACE_FEATURE="#define CAIRO_HAS_PS_SURFACE 1" + fi + if test "$_HAVE_FREETYPE2"; then FT_FONT_FEATURE="#define CAIRO_HAS_FT_FONT 1" MOZ_ENABLE_CAIRO_FT=1 CAIRO_FT_CFLAGS="$FT2_CFLAGS" fi + case "$MOZ_WIDGET_TOOLKIT" in qt) QT_SURFACE_FEATURE="#define CAIRO_HAS_QT_SURFACE 1" ;; cocoa | uikit) QUARTZ_SURFACE_FEATURE="#define CAIRO_HAS_QUARTZ_SURFACE 1" QUARTZ_IMAGE_SURFACE_FEATURE="#define CAIRO_HAS_QUARTZ_IMAGE_SURFACE 1" QUARTZ_FONT_FEATURE="#define CAIRO_HAS_QUARTZ_FONT 1"
--- a/content/base/src/nsCopySupport.cpp +++ b/content/base/src/nsCopySupport.cpp @@ -710,13 +710,13 @@ nsCopySupport::FireClipboardEvent(int32_ return false; } } } // Now that we have copied, update the clipboard commands. This should have // the effect of updating the enabled state of the paste menu item. if (doDefault || count) { - piWindow->UpdateCommands(NS_LITERAL_STRING("clipboard")); + piWindow->UpdateCommands(NS_LITERAL_STRING("clipboard"), nullptr, 0); } return doDefault; }
--- a/content/base/src/nsRange.cpp +++ b/content/base/src/nsRange.cpp @@ -2744,36 +2744,41 @@ static void ExtractRectFromOffset(nsIFra aR->width = point.x - aR->x; } else { aR->width = aR->XMost() - point.x; aR->x = point.x; } } static nsTextFrame* -GetTextFrameForContent(nsIContent* aContent) +GetTextFrameForContent(nsIContent* aContent, bool aFlushLayout) { nsIPresShell* presShell = aContent->OwnerDoc()->GetShell(); if (presShell) { presShell->FrameConstructor()->EnsureFrameForTextNode( static_cast<nsGenericDOMDataNode*>(aContent)); - aContent->OwnerDoc()->FlushPendingNotifications(Flush_Layout); + + if (aFlushLayout) { + aContent->OwnerDoc()->FlushPendingNotifications(Flush_Layout); + } + nsIFrame* frame = aContent->GetPrimaryFrame(); if (frame && frame->GetType() == nsGkAtoms::textFrame) { return static_cast<nsTextFrame*>(frame); } } return nullptr; } static nsresult GetPartialTextRect(nsLayoutUtils::RectCallback* aCallback, nsIContent* aContent, int32_t aStartOffset, - int32_t aEndOffset, bool aClampToEdge) + int32_t aEndOffset, bool aClampToEdge, + bool aFlushLayout) { - nsTextFrame* textFrame = GetTextFrameForContent(aContent); + nsTextFrame* textFrame = GetTextFrameForContent(aContent, aFlushLayout); if (textFrame) { nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(textFrame); for (nsTextFrame* f = textFrame; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd(); if (fend <= aStartOffset || fstart >= aEndOffset) continue; // overlapping with the offset we want @@ -2796,44 +2801,45 @@ static nsresult GetPartialTextRect(nsLay return NS_OK; } /* static */ void nsRange::CollectClientRects(nsLayoutUtils::RectCallback* aCollector, nsRange* aRange, nsINode* aStartParent, int32_t aStartOffset, nsINode* aEndParent, int32_t aEndOffset, - bool aClampToEdge) + bool aClampToEdge, bool aFlushLayout) { // Hold strong pointers across the flush nsCOMPtr<nsINode> startContainer = aStartParent; nsCOMPtr<nsINode> endContainer = aEndParent; // Flush out layout so our frames are up to date. if (!aStartParent->IsInDoc()) { return; } - aStartParent->OwnerDoc()->FlushPendingNotifications(Flush_Layout); - - // Recheck whether we're still in the document - if (!aStartParent->IsInDoc()) { - return; + if (aFlushLayout) { + aStartParent->OwnerDoc()->FlushPendingNotifications(Flush_Layout); + // Recheck whether we're still in the document + if (!aStartParent->IsInDoc()) { + return; + } } RangeSubtreeIterator iter; nsresult rv = iter.Init(aRange); if (NS_FAILED(rv)) return; if (iter.IsDone()) { // the range is collapsed, only continue if the cursor is in a text node nsCOMPtr<nsIContent> content = do_QueryInterface(aStartParent); if (content && content->IsNodeOfType(nsINode::eTEXT)) { - nsTextFrame* textFrame = GetTextFrameForContent(content); + nsTextFrame* textFrame = GetTextFrameForContent(content, aFlushLayout); if (textFrame) { int32_t outOffset; nsIFrame* outFrame; textFrame->GetChildFrameContainingOffset(aStartOffset, false, &outOffset, &outFrame); if (outFrame) { nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(outFrame); @@ -2853,20 +2859,22 @@ nsRange::CollectClientRects(nsLayoutUtil iter.Next(); nsCOMPtr<nsIContent> content = do_QueryInterface(node); if (!content) continue; if (content->IsNodeOfType(nsINode::eTEXT)) { if (node == startContainer) { int32_t offset = startContainer == endContainer ? aEndOffset : content->GetText()->GetLength(); - GetPartialTextRect(aCollector, content, aStartOffset, offset, aClampToEdge); + GetPartialTextRect(aCollector, content, aStartOffset, offset, + aClampToEdge, aFlushLayout); continue; } else if (node == endContainer) { - GetPartialTextRect(aCollector, content, 0, aEndOffset, aClampToEdge); + GetPartialTextRect(aCollector, content, 0, aEndOffset, + aClampToEdge, aFlushLayout); continue; } } nsIFrame* frame = content->GetPrimaryFrame(); if (frame) { nsLayoutUtils::GetAllInFlowRects(frame, nsLayoutUtils::GetContainingBlockForClientRect(frame), aCollector, @@ -2878,54 +2886,54 @@ nsRange::CollectClientRects(nsLayoutUtil NS_IMETHODIMP nsRange::GetBoundingClientRect(nsIDOMClientRect** aResult) { *aResult = GetBoundingClientRect(true).take(); return NS_OK; } already_AddRefed<DOMRect> -nsRange::GetBoundingClientRect(bool aClampToEdge) +nsRange::GetBoundingClientRect(bool aClampToEdge, bool aFlushLayout) { nsRefPtr<DOMRect> rect = new DOMRect(ToSupports(this)); if (!mStartParent) { return rect.forget(); } nsLayoutUtils::RectAccumulator accumulator; CollectClientRects(&accumulator, this, mStartParent, mStartOffset, - mEndParent, mEndOffset, aClampToEdge); + mEndParent, mEndOffset, aClampToEdge, aFlushLayout); nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect : accumulator.mResultRect; rect->SetLayoutRect(r); return rect.forget(); } NS_IMETHODIMP nsRange::GetClientRects(nsIDOMClientRectList** aResult) { *aResult = GetClientRects(true).take(); return NS_OK; } already_AddRefed<DOMRectList> -nsRange::GetClientRects(bool aClampToEdge) +nsRange::GetClientRects(bool aClampToEdge, bool aFlushLayout) { if (!mStartParent) { return nullptr; } nsRefPtr<DOMRectList> rectList = new DOMRectList(static_cast<nsIDOMRange*>(this)); nsLayoutUtils::RectListBuilder builder(rectList); CollectClientRects(&builder, this, mStartParent, mStartOffset, - mEndParent, mEndOffset, aClampToEdge); + mEndParent, mEndOffset, aClampToEdge, aFlushLayout); return rectList.forget(); } NS_IMETHODIMP nsRange::GetUsedFontFaces(nsIDOMFontFaceList** aResult) { *aResult = nullptr;
--- a/content/base/src/nsRange.h +++ b/content/base/src/nsRange.h @@ -212,18 +212,20 @@ public: void SelectNodeContents(nsINode& aNode, ErrorResult& aErr); void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); void SetEndAfter(nsINode& aNode, ErrorResult& aErr); void SetEndBefore(nsINode& aNode, ErrorResult& aErr); void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); void SetStartAfter(nsINode& aNode, ErrorResult& aErr); void SetStartBefore(nsINode& aNode, ErrorResult& aErr); void SurroundContents(nsINode& aNode, ErrorResult& aErr); - already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true); - already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true); + already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true, + bool aFlushLayout = true); + already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true, + bool aFlushLayout = true); nsINode* GetParentObject() const { return mOwner; } virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE MOZ_FINAL; private: // no copy's or assigns nsRange(const nsRange&); nsRange& operator=(const nsRange&); @@ -255,17 +257,17 @@ public: static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset, uint32_t aEndOffset); static void CollectClientRects(nsLayoutUtils::RectCallback* aCollector, nsRange* aRange, nsINode* aStartParent, int32_t aStartOffset, nsINode* aEndParent, int32_t aEndOffset, - bool aClampToEdge); + bool aClampToEdge, bool aFlushLayout); typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable; protected: void RegisterCommonAncestor(nsINode* aNode); void UnregisterCommonAncestor(nsINode* aNode); nsINode* IsValidBoundary(nsINode* aNode); // CharacterDataChanged set aNotInsertedYet to true to disable an assertion
--- a/content/html/content/src/nsTextEditorState.cpp +++ b/content/html/content/src/nsTextEditorState.cpp @@ -671,17 +671,19 @@ public: NS_DECL_NSIEDITOROBSERVER protected: /** the default destructor. virtual due to the possibility of derivation. */ virtual ~nsTextInputListener(); - nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate); + nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate, + nsISelection* sel = nullptr, + int16_t reason = 0); protected: nsIFrame* mFrame; nsITextControlElement* const mTxtCtrlElement; bool mSelectionWasCollapsed; @@ -775,26 +777,28 @@ nsTextInputListener::NotifySelectionChan WidgetEvent event(true, NS_FORM_SELECTED); presShell->HandleEventWithTarget(&event, mFrame, content, &status); } } } } + UpdateTextInputCommands(NS_LITERAL_STRING("selectionchange"), aSel, aReason); + // if the collapsed state did not change, don't fire notifications if (collapsed == mSelectionWasCollapsed) return NS_OK; - + mSelectionWasCollapsed = collapsed; if (!weakFrame.IsAlive() || !nsContentUtils::IsFocusedContent(mFrame->GetContent())) return NS_OK; - return UpdateTextInputCommands(NS_LITERAL_STRING("select")); + return UpdateTextInputCommands(NS_LITERAL_STRING("select"), aSel, aReason); } // END nsIDOMSelectionListener static void DoCommandCallback(Command aCommand, void* aData) { nsTextControlFrame *frame = static_cast<nsTextControlFrame*>(aData); @@ -925,28 +929,30 @@ nsTextInputListener::EditAction() return NS_OK; } // END nsIEditorObserver nsresult -nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate) +nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate, + nsISelection* sel, + int16_t reason) { nsIContent* content = mFrame->GetContent(); NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); nsCOMPtr<nsIDocument> doc = content->GetDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); nsPIDOMWindow *domWindow = doc->GetWindow(); NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); - return domWindow->UpdateCommands(commandsToUpdate); + return domWindow->UpdateCommands(commandsToUpdate, sel, reason); } // END nsTextInputListener // nsTextEditorState nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement) : mTextCtrlElement(aOwningElement),
--- a/content/media/AudioStream.cpp +++ b/content/media/AudioStream.cpp @@ -1,26 +1,30 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* 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 <stdio.h> #include <math.h> +#include <string.h> #include "prlog.h" #include "prdtoa.h" #include "AudioStream.h" #include "VideoUtils.h" #include "mozilla/Monitor.h" #include "mozilla/Mutex.h" #include <algorithm> #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "soundtouch/SoundTouch.h" #include "Latency.h" +#ifdef XP_MACOSX +#include <sys/sysctl.h> +#endif namespace mozilla { #ifdef LOG #undef LOG #endif #ifdef PR_LOGGING @@ -241,20 +245,20 @@ AudioStream::AudioStream() , mOutRate(0) , mChannels(0) , mOutChannels(0) , mWritten(0) , mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST()) , mLatencyRequest(HighLatency) , mReadPoint(0) , mDumpFile(nullptr) - , mVolume(1.0) , mBytesPerFrame(0) , mState(INITIALIZED) , mNeedsStart(false) + , mShouldDropFrames(false) { // keep a ref in case we shut down later than nsLayoutStatics mLatencyLog = AsyncLatencyLogger::Get(true); } AudioStream::~AudioStream() { LOG(("AudioStream: delete %p, state %d", this, mState)); @@ -493,18 +497,20 @@ AudioStream::Init(int32_t aNumChannels, mDumpFile = OpenDumpFile(this); cubeb_stream_params params; params.rate = aRate; params.channels = mOutChannels; #if defined(__ANDROID__) #if defined(MOZ_B2G) + mAudioChannel = aAudioChannel; params.stream_type = ConvertChannelToCubebType(aAudioChannel); #else + mAudioChannel = dom::AudioChannel::Content; params.stream_type = CUBEB_STREAM_TYPE_MUSIC; #endif if (params.stream_type == CUBEB_STREAM_TYPE_MAX) { return NS_ERROR_INVALID_ARG; } #endif if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { @@ -539,16 +545,72 @@ AudioStream::Init(int32_t aNumChannels, // thread for now (cubeb API issue) { MonitorAutoLock mon(mMonitor); CheckForStart(); } return rv; } +// On certain MacBookPro, the microphone is located near the left speaker. +// We need to pan the sound output to the right speaker if we are using the mic +// and the built-in speaker, or we will have terrible echo. +void AudioStream::PanOutputIfNeeded(bool aMicrophoneActive) +{ +#ifdef XP_MACOSX + cubeb_device* device; + int rv; + char name[128]; + size_t length = sizeof(name); + bool panCenter = false; + + rv = sysctlbyname("hw.model", name, &length, NULL, 0); + if (rv) { + return; + } + + if (!strncmp(name, "MacBookPro", 10)) { + if (cubeb_stream_get_current_device(mCubebStream, &device) == CUBEB_OK) { + // Check if we are currently outputing sound on external speakers. + if (!strcmp(device->output_name, "ispk")) { + // Pan everything to the right speaker. + if (aMicrophoneActive) { + LOG(("%p Panning audio output to the right.", this)); + if (cubeb_stream_set_panning(mCubebStream, 1.0) != CUBEB_OK) { + NS_WARNING("Could not pan audio output to the right."); + } + } else { + panCenter = true; + } + } else { + panCenter = true; + } + if (panCenter) { + LOG(("%p Panning audio output to the center.", this)); + if (cubeb_stream_set_panning(mCubebStream, 0.0) != CUBEB_OK) { + NS_WARNING("Could not pan audio output to the center."); + } + // This a microphone that goes through the headphone plug, reset the + // output to prevent echo building up. + if (strcmp(device->input_name, "emic") == 0) { + Reset(); + } + } + cubeb_stream_device_destroy(mCubebStream, device); + } + } +#endif +} + +void AudioStream::DeviceChangedCallback() { + MonitorAutoLock mon(mMonitor); + PanOutputIfNeeded(mMicrophoneActive); + mShouldDropFrames = true; +} + // This code used to live inside AudioStream::Init(), but on Mac (others?) // it has been known to take 300-800 (or even 8500) ms to execute(!) nsresult AudioStream::OpenCubeb(cubeb_stream_params &aParams, LatencyRequest aLatencyRequest) { { MonitorAutoLock mon(mMonitor); @@ -595,22 +657,27 @@ AudioStream::OpenCubeb(cubeb_stream_para } else { MonitorAutoLock mon(mMonitor); mState = ERRORED; LOG(("AudioStream::OpenCubeb() %p failed to init cubeb", this)); return NS_ERROR_FAILURE; } } + cubeb_stream_register_device_changed_callback(mCubebStream, + AudioStream::DeviceChangedCallback_s); + + mState = INITIALIZED; + if (!mStartTime.IsNull()) { - TimeDuration timeDelta = TimeStamp::Now() - mStartTime; + TimeDuration timeDelta = TimeStamp::Now() - mStartTime; LOG(("AudioStream creation time %sfirst: %u ms", mIsFirst ? "" : "not ", - (uint32_t) timeDelta.ToMilliseconds())); - Telemetry::Accumulate(mIsFirst ? Telemetry::AUDIOSTREAM_FIRST_OPEN_MS : - Telemetry::AUDIOSTREAM_LATER_OPEN_MS, timeDelta.ToMilliseconds()); + (uint32_t) timeDelta.ToMilliseconds())); + Telemetry::Accumulate(mIsFirst ? Telemetry::AUDIOSTREAM_FIRST_OPEN_MS : + Telemetry::AUDIOSTREAM_LATER_OPEN_MS, timeDelta.ToMilliseconds()); } return NS_OK; } void AudioStream::CheckForStart() { @@ -655,25 +722,30 @@ AudioInitTask::Run() return rv; } // aTime is the time in ms the samples were inserted into MediaStreamGraph nsresult AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime) { MonitorAutoLock mon(mMonitor); + + // See if we need to start() the stream, since we must do that from this thread + CheckForStart(); + + if (mShouldDropFrames) { + mBuffer.ContractTo(0); + return NS_OK; + } if (mState == ERRORED) { return NS_ERROR_FAILURE; } NS_ASSERTION(mState == INITIALIZED || mState == STARTED || mState == RUNNING, "Stream write in unexpected state."); - // See if we need to start() the stream, since we must do that from this thread - CheckForStart(); - // Downmix to Stereo. if (mChannels > 2 && mChannels <= 8) { DownmixAudioToStereo(const_cast<AudioDataValue*> (aBuf), mChannels, aFrames); } else if (mChannels > 8) { return NS_ERROR_FAILURE; } @@ -746,19 +818,31 @@ AudioStream::Available() MonitorAutoLock mon(mMonitor); NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated."); return BytesToFrames(mBuffer.Available()); } void AudioStream::SetVolume(double aVolume) { + NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume"); + + if (cubeb_stream_set_volume(mCubebStream, aVolume * GetVolumeScale()) != CUBEB_OK) { + NS_WARNING("Could not change volume on cubeb stream."); + } +} + +void +AudioStream::SetMicrophoneActive(bool aActive) +{ MonitorAutoLock mon(mMonitor); - NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume"); - mVolume = aVolume; + + mMicrophoneActive = aActive; + + PanOutputIfNeeded(mMicrophoneActive); } void AudioStream::Cancel() { MonitorAutoLock mon(mMonitor); mState = ERRORED; mon.NotifyAll(); @@ -789,21 +873,24 @@ AudioStream::Start() void AudioStream::StartUnlocked() { mMonitor.AssertCurrentThreadOwns(); if (!mCubebStream) { mNeedsStart = true; return; } + if (mState == INITIALIZED) { int r; { MonitorAutoUnlock mon(mMonitor); r = cubeb_stream_start(mCubebStream); + + PanOutputIfNeeded(mMicrophoneActive); } mState = r == CUBEB_OK ? STARTED : ERRORED; LOG(("AudioStream: started %p, state %s", this, mState == STARTED ? "STARTED" : "ERRORED")); } } void AudioStream::Pause() @@ -1032,28 +1119,83 @@ AudioStream::GetTimeStretched(void* aBuf processedFrames += receivedFrames; } while (processedFrames < aFrames && !lowOnBufferedData); GetBufferInsertTime(aTimeMs); return processedFrames; } +void +AudioStream::Reset() +{ + mShouldDropFrames = true; + mNeedsStart = true; + + cubeb_stream_params params; + params.rate = mInRate; + params.channels = mOutChannels; +#if defined(__ANDROID__) +#if defined(MOZ_B2G) + params.stream_type = ConvertChannelToCubebType(mAudioChannel); +#else + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + if (params.stream_type == CUBEB_STREAM_TYPE_MAX) { + return; + } +#endif + + if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { + params.format = CUBEB_SAMPLE_S16NE; + } else { + params.format = CUBEB_SAMPLE_FLOAT32NE; + } + mBytesPerFrame = sizeof(AudioDataValue) * mOutChannels; + + // Size mBuffer for one second of audio. This value is arbitrary, and was + // selected based on the observed behaviour of the existing AudioStream + // implementations. + uint32_t bufferLimit = FramesToBytes(mInRate); + NS_ABORT_IF_FALSE(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames"); + mBuffer.Reset(); + mBuffer.SetCapacity(bufferLimit); + + + if (mLatencyRequest == LowLatency) { + // Don't block this thread to initialize a cubeb stream. + // When this is done, it will start callbacks from Cubeb. Those will + // cause us to move from INITIALIZED to RUNNING. Until then, we + // can't access any cubeb functions. + // Use a RefPtr to avoid leaks if Dispatch fails + RefPtr<AudioInitTask> init = new AudioInitTask(this, mLatencyRequest, params); + init->Dispatch(); + return; + } + // High latency - open synchronously + OpenCubeb(params, mLatencyRequest); + + CheckForStart(); +} + long AudioStream::DataCallback(void* aBuffer, long aFrames) { MonitorAutoLock mon(mMonitor); MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown"); uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length()); NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames"); AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer); uint32_t underrunFrames = 0; uint32_t servicedFrames = 0; int64_t insertTime; + mShouldDropFrames = false; + // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN) // Bug 996162 // callback tells us cubeb succeeded initializing if (mState == STARTED) { // For low-latency streams, we want to minimize any built-up data when // we start getting callbacks. // Simple version - contract on first callback only. @@ -1094,19 +1236,16 @@ AudioStream::DataCallback(void* aBuffer, if (mLatencyRequest == LowLatency && !mWritten) { servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime); } else { servicedFrames = GetUnprocessed(output, aFrames, insertTime); } } else { servicedFrames = GetTimeStretched(output, aFrames, insertTime); } - float scaled_volume = float(GetVolumeScale() * mVolume); - - ScaleAudioSamples(output, aFrames * mOutChannels, scaled_volume); NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames"); // Notify any blocked Write() call that more space is available in mBuffer. mon.NotifyAll(); } else { GetBufferInsertTime(insertTime); }
--- a/content/media/AudioStream.h +++ b/content/media/AudioStream.h @@ -153,29 +153,37 @@ public: size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { size_t amount = 0; amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); return amount; } + void Reset() + { + mBuffer = nullptr; + mCapacity = 0; + mStart = 0; + mCount = 0; + } + private: nsAutoArrayPtr<uint8_t> mBuffer; uint32_t mCapacity; uint32_t mStart; uint32_t mCount; }; class AudioInitTask; // Access to a single instance of this class must be synchronized by // callers, or made from a single thread. One exception is that access to -// GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels} -// is thread-safe without external synchronization. +// GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels}, +// SetMicrophoneActive is thread-safe without external synchronization. class AudioStream MOZ_FINAL { virtual ~AudioStream(); public: // Initialize Audio Library. Some Audio backends require initializing the // library before using it. static void InitLibrary(); @@ -207,30 +215,37 @@ public: // (22050Hz, 44100Hz, etc). nsresult Init(int32_t aNumChannels, int32_t aRate, const dom::AudioChannel aAudioStreamChannel, LatencyRequest aLatencyRequest); // Closes the stream. All future use of the stream is an error. void Shutdown(); + void Reset(); + // Write audio data to the audio hardware. aBuf is an array of AudioDataValues // AudioDataValue of length aFrames*mChannels. If aFrames is larger // than the result of Available(), the write will block until sufficient // buffer space is available. aTime is the time in ms associated with the first sample // for latency calculations nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp* aTime = nullptr); // Return the number of audio frames that can be written without blocking. uint32_t Available(); // Set the current volume of the audio playback. This is a value from // 0 (meaning muted) to 1 (meaning full volume). Thread-safe. void SetVolume(double aVolume); + // Informs the AudioStream that a microphone is being used by someone in the + // application. + void SetMicrophoneActive(bool aActive); + void PanOutputIfNeeded(bool aMicrophoneActive); + // Block until buffered audio data has been consumed. void Drain(); // Break any blocking operation and set the stream to shutdown. void Cancel(); // Start the stream. void Start(); @@ -299,18 +314,24 @@ private: return static_cast<AudioStream*>(aThis)->DataCallback(aBuffer, aFrames); } static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState) { static_cast<AudioStream*>(aThis)->StateCallback(aState); } + + static void DeviceChangedCallback_s(void * aThis) { + static_cast<AudioStream*>(aThis)->DeviceChangedCallback(); + } + long DataCallback(void* aBuffer, long aFrames); void StateCallback(cubeb_state aState); + void DeviceChangedCallback(); nsresult EnsureTimeStretcherInitializedUnlocked(); // aTime is the time in ms the samples were inserted into MediaStreamGraph long GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTime); long GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTime); long GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t &aTime); @@ -326,16 +347,19 @@ private: Monitor mMonitor; // Input rate in Hz (characteristic of the media being played) int mInRate; // Output rate in Hz (characteristic of the playback rate) int mOutRate; int mChannels; int mOutChannels; +#if defined(__ANDROID__) + dom::AudioChannel mAudioChannel; +#endif // Number of frames written to the buffers. int64_t mWritten; AudioClock mAudioClock; nsAutoPtr<soundtouch::SoundTouch> mTimeStretcher; nsRefPtr<AsyncLatencyLogger> mLatencyLog; // copy of Latency logger's starting time for offset calculations TimeStamp mStartTime; @@ -356,19 +380,16 @@ private: FILE* mDumpFile; // Temporary audio buffer. Filled by Write() and consumed by // DataCallback(). Once mBuffer is full, Write() blocks until sufficient // space becomes available in mBuffer. mBuffer is sized in bytes, not // frames. CircularByteBuffer mBuffer; - // Software volume level. Applied during the servicing of DataCallback(). - double mVolume; - // Owning reference to a cubeb_stream. cubeb_stream_destroy is called by // nsAutoRef's destructor. nsAutoRef<cubeb_stream> mCubebStream; uint32_t mBytesPerFrame; uint32_t BytesToFrames(uint32_t aBytes) { NS_ASSERTION(aBytes % mBytesPerFrame == 0, @@ -392,16 +413,22 @@ private: DRAINED, // StateCallback has indicated that the drain is complete. ERRORED, // Stream disabled due to an internal error. SHUTDOWN // Shutdown has been called }; StreamState mState; bool mNeedsStart; // needed in case Start() is called before cubeb is open bool mIsFirst; + // True if a microphone is active. + bool mMicrophoneActive; + // When we are in the process of changing the output device, and the callback + // is not going to be called for a little while, simply drop incoming frames. + // This is only on OSX for now, because other systems handle this gracefully. + bool mShouldDropFrames; // This mutex protects the static members below. static StaticMutex sMutex; static cubeb* sCubebContext; // Prefered samplerate, in Hz (characteristic of the // hardware/mixer/platform/API used). static uint32_t sPreferredSampleRate;
--- a/content/media/MediaStreamGraph.cpp +++ b/content/media/MediaStreamGraph.cpp @@ -105,16 +105,17 @@ MediaStreamGraphImpl::RemoveStream(Media MonitorAutoLock lock(mMonitor); for (uint32_t i = 0; i < mStreamUpdates.Length(); ++i) { if (mStreamUpdates[i].mStream == aStream) { mStreamUpdates[i].mStream = nullptr; } } } + // Ensure that mFirstCycleBreaker and mMixer are updated when necessary. SetStreamOrderDirty(); mStreams.RemoveElement(aStream); NS_RELEASE(aStream); // probably destroying it STREAM_LOG(PR_LOG_DEBUG, ("Removing media stream %p from the graph", aStream)); } @@ -563,18 +564,28 @@ MediaStreamGraphImpl::UpdateStreamOrder( if (stream->AsSourceStream() && stream->AsSourceStream()->NeedsMixing()) { shouldMix = true; } } if (!mMixer && shouldMix) { mMixer = new AudioMixer(AudioMixerCallback); + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + for (uint32_t i = 0; i < mStreams[i]->mAudioOutputStreams.Length(); ++i) { + mStreams[i]->mAudioOutputStreams[i].mStream->SetMicrophoneActive(true); + } + } } else if (mMixer && !shouldMix) { mMixer = nullptr; + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + for (uint32_t i = 0; i < mStreams[i]->mAudioOutputStreams.Length(); ++i) { + mStreams[i]->mAudioOutputStreams[i].mStream->SetMicrophoneActive(false); + } + } } // The algorithm for finding cycles is based on Tim Leslie's iterative // implementation [1][2] of Pearce's variant [3] of Tarjan's strongly // connected components (SCC) algorithm. There are variations (a) to // distinguish whether streams in SCCs of size 1 are in a cycle and (b) to // re-run the algorithm over SCCs with breaks at DelayNodes. // @@ -687,58 +698,60 @@ MediaStreamGraphImpl::UpdateStreamOrder( } // |ps| is the root of an SCC involving no other streams on dfsStack, the // complete SCC has been recorded, and streams in this SCC are part of at // least one cycle. MOZ_ASSERT(cycleStackMarker == ps->mCycleMarker); // If there are DelayNodes in this SCC, then they may break the cycles. bool haveDelayNode = false; - auto next = static_cast<ProcessedMediaStream*>(sccStack.getFirst()); + auto next = sccStack.getFirst(); // Streams in this SCC are identified by mCycleMarker <= cycleStackMarker. // (There may be other streams later in sccStack from other incompletely // searched SCCs, involving streams still on dfsStack.) // // DelayNodes in cycles must behave differently from those not in cycles, // so all DelayNodes in the SCC must be identified. - while (next && next->mCycleMarker <= cycleStackMarker) { + while (next && static_cast<ProcessedMediaStream*>(next)-> + mCycleMarker <= cycleStackMarker) { auto ns = next->AsAudioNodeStream(); // Get next before perhaps removing from list below. - next = static_cast<ProcessedMediaStream*>(next->getNext()); + next = next->getNext(); if (ns && ns->Engine()->AsDelayNodeEngine()) { haveDelayNode = true; // DelayNodes break cycles by producing their output in a // preprocessing phase; they do not need to be ordered before their // consumers. Order them at the tail of mStreams so that they can be // handled specially. Do so now, so that DFS ignores them. ns->remove(); ns->mCycleMarker = 0; --mFirstCycleBreaker; mStreams[mFirstCycleBreaker] = ns; } } auto after_scc = next; - while ((next = static_cast<ProcessedMediaStream*>(sccStack.popFirst())) - != after_scc) { + while ((next = sccStack.getFirst()) != after_scc) { + next->remove(); + auto removed = static_cast<ProcessedMediaStream*>(next); if (haveDelayNode) { // Return streams to the DFS stack again (to order and detect cycles // without delayNodes). Any of these streams that are still inputs // for streams on the visited stack must be returned to the front of // the stack to be ordered before their dependents. We know that none // of these streams need input from streams on the visited stack, so // they can all be searched and ordered before the current stack head // is popped. - next->mCycleMarker = NOT_VISITED; - dfsStack.insertFront(next); + removed->mCycleMarker = NOT_VISITED; + dfsStack.insertFront(removed); } else { // Streams in cycles without any DelayNodes must be muted, and so do // not need input and can be ordered now. They must be ordered before // their consumers so that their muted output is available. - next->mCycleMarker = IN_MUTED_CYCLE; - mStreams[orderedStreamCount] = next; + removed->mCycleMarker = IN_MUTED_CYCLE; + mStreams[orderedStreamCount] = removed; ++orderedStreamCount; } } } MOZ_ASSERT(orderedStreamCount == mFirstCycleBreaker); } @@ -945,16 +958,19 @@ MediaStreamGraphImpl::CreateOrDestroyAud // XXX for now, allocate stereo output. But we need to fix this to // match the system's ideal channel configuration. // NOTE: we presume this is either fast or async-under-the-covers audioOutputStream->mStream->Init(2, mSampleRate, aStream->mAudioChannelType, AudioStream::LowLatency); audioOutputStream->mTrackID = tracks->GetID(); + // If there is a mixer, there is a micrphone active. + audioOutputStream->mStream->SetMicrophoneActive(mMixer); + LogLatency(AsyncLatencyLogger::AudioStreamCreate, reinterpret_cast<uint64_t>(aStream), reinterpret_cast<int64_t>(audioOutputStream->mStream.get())); } } } for (int32_t i = audioOutputStreamsFound.Length() - 1; i >= 0; --i) { @@ -2029,40 +2045,41 @@ MediaStream::RemoveAllListenersImpl() listener->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_REMOVED); } mListeners.Clear(); } void MediaStream::DestroyImpl() { - RemoveAllListenersImpl(); - for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) { mConsumers[i]->Disconnect(); } for (uint32_t i = 0; i < mAudioOutputStreams.Length(); ++i) { mAudioOutputStreams[i].mStream->Shutdown(); } mAudioOutputStreams.Clear(); + mGraph = nullptr; } void MediaStream::Destroy() { // Keep this stream alive until we leave this method nsRefPtr<MediaStream> kungFuDeathGrip = this; class Message : public ControlMessage { public: Message(MediaStream* aStream) : ControlMessage(aStream) {} virtual void Run() { + mStream->RemoveAllListenersImpl(); + auto graph = mStream->GraphImpl(); mStream->DestroyImpl(); - mStream->GraphImpl()->RemoveStream(mStream); + graph->RemoveStream(mStream); } virtual void RunDuringShutdown() { Run(); } }; mWrapper = nullptr; GraphImpl()->AppendMessage(new Message(this)); // Message::RunDuringShutdown may have removed this stream from the graph, // but our kungFuDeathGrip above will have kept this stream alive if @@ -2333,29 +2350,28 @@ MediaStream::ApplyTrackDisabling(TrackID if (aRawSegment) { aRawSegment->ReplaceWithDisabled(); } } void SourceMediaStream::DestroyImpl() { - { - MutexAutoLock lock(mMutex); - mDestroyed = true; - } + // Hold mMutex while mGraph is reset so that other threads holding mMutex + // can null-check know that the graph will not destroyed. + MutexAutoLock lock(mMutex); MediaStream::DestroyImpl(); } void SourceMediaStream::SetPullEnabled(bool aEnabled) { MutexAutoLock lock(mMutex); mPullEnabled = aEnabled; - if (mPullEnabled && !mDestroyed) { + if (mPullEnabled && GraphImpl()) { GraphImpl()->EnsureNextIteration(); } } void SourceMediaStream::AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment) { @@ -2365,18 +2381,18 @@ SourceMediaStream::AddTrack(TrackID aID, data->mInputRate = aRate; // We resample all audio input tracks to the sample rate of the audio mixer. data->mOutputRate = aSegment->GetType() == MediaSegment::AUDIO ? GraphImpl()->AudioSampleRate() : aRate; data->mStart = aStart; data->mCommands = TRACK_CREATE; data->mData = aSegment; data->mHaveEnough = false; - if (!mDestroyed) { - GraphImpl()->EnsureNextIteration(); + if (auto graph = GraphImpl()) { + graph->EnsureNextIteration(); } } void SourceMediaStream::ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment) { if (aSegment->GetType() != MediaSegment::AUDIO || aTrackData->mInputRate == GraphImpl()->AudioSampleRate()) { @@ -2408,17 +2424,18 @@ SourceMediaStream::ResampleAudioToGraphS } bool SourceMediaStream::AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment) { MutexAutoLock lock(mMutex); // ::EndAllTrackAndFinished() can end these before the sources notice bool appended = false; - if (!mFinished) { + auto graph = GraphImpl(); + if (!mFinished && graph) { TrackData *track = FindDataForTrack(aID); if (track) { // Data goes into mData, and on the next iteration of the MSG moves // into the track's segment after NotifyQueuedTrackChanges(). This adds // 0-10ms of delay before data gets to direct listeners. // Indirect listeners (via subsequent TrackUnion nodes) are synced to // playout time, and so can be delayed by buffering. @@ -2427,23 +2444,21 @@ SourceMediaStream::AppendToTrack(TrackID ApplyTrackDisabling(aID, aSegment, aRawSegment); ResampleAudioToGraphSampleRate(track, aSegment); // Must notify first, since AppendFrom() will empty out aSegment NotifyDirectConsumers(track, aRawSegment ? aRawSegment : aSegment); track->mData->AppendFrom(aSegment); // note: aSegment is now dead appended = true; + graph->EnsureNextIteration(); } else { aSegment->Clear(); } } - if (!mDestroyed) { - GraphImpl()->EnsureNextIteration(); - } return appended; } void SourceMediaStream::NotifyDirectConsumers(TrackData *aTrack, MediaSegment *aSegment) { // Call with mMutex locked @@ -2530,39 +2545,39 @@ SourceMediaStream::EndTrack(TrackID aID) MutexAutoLock lock(mMutex); // ::EndAllTrackAndFinished() can end these before the sources call this if (!mFinished) { TrackData *track = FindDataForTrack(aID); if (track) { track->mCommands |= TRACK_END; } } - if (!mDestroyed) { - GraphImpl()->EnsureNextIteration(); + if (auto graph = GraphImpl()) { + graph->EnsureNextIteration(); } } void SourceMediaStream::AdvanceKnownTracksTime(StreamTime aKnownTime) { MutexAutoLock lock(mMutex); MOZ_ASSERT(aKnownTime >= mUpdateKnownTracksTime); mUpdateKnownTracksTime = aKnownTime; - if (!mDestroyed) { - GraphImpl()->EnsureNextIteration(); + if (auto graph = GraphImpl()) { + graph->EnsureNextIteration(); } } void SourceMediaStream::FinishWithLockHeld() { mMutex.AssertCurrentThreadOwns(); mUpdateFinished = true; - if (!mDestroyed) { - GraphImpl()->EnsureNextIteration(); + if (auto graph = GraphImpl()) { + graph->EnsureNextIteration(); } } void SourceMediaStream::EndAllTrackAndFinish() { MutexAutoLock lock(mMutex); for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) { @@ -2655,16 +2670,17 @@ MediaInputPort::Destroy() class Message : public ControlMessage { public: Message(MediaInputPort* aPort) : ControlMessage(nullptr), mPort(aPort) {} virtual void Run() { mPort->Disconnect(); --mPort->GraphImpl()->mPortCount; + mPort->SetGraphImpl(nullptr); NS_RELEASE(mPort); } virtual void RunDuringShutdown() { Run(); } MediaInputPort* mPort; }; @@ -2681,17 +2697,17 @@ MediaStreamGraph* MediaInputPort::Graph() { return mGraph; } void MediaInputPort::SetGraphImpl(MediaStreamGraphImpl* aGraph) { - MOZ_ASSERT(!mGraph, "Should only be called once"); + MOZ_ASSERT(!mGraph || !aGraph, "Should only be set once"); mGraph = aGraph; } already_AddRefed<MediaInputPort> ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, uint32_t aFlags, uint16_t aInputNumber, uint16_t aOutputNumber) { // This method creates two references to the MediaInputPort: one for @@ -2754,17 +2770,20 @@ ProcessedMediaStream::SetAutofinish(bool void ProcessedMediaStream::DestroyImpl() { for (int32_t i = mInputs.Length() - 1; i >= 0; --i) { mInputs[i]->Disconnect(); } MediaStream::DestroyImpl(); - GraphImpl()->SetStreamOrderDirty(); + // The stream order is only important if there are connections, in which + // case MediaInputPort::Disconnect() called SetStreamOrderDirty(). + // MediaStreamGraphImpl::RemoveStream() will also call + // SetStreamOrderDirty(), for other reasons. } MediaStreamGraphImpl::MediaStreamGraphImpl(bool aRealtime, TrackRate aSampleRate) : mCurrentTime(0) , mStateComputedTime(0) , mProcessingGraphUpdateIndex(0) , mPortCount(0) , mMonitor("MediaStreamGraphImpl")
--- a/content/media/MediaStreamGraph.h +++ b/content/media/MediaStreamGraph.h @@ -672,17 +672,17 @@ protected: // This state is only used on the main thread. DOMMediaStream* mWrapper; // Main-thread views of state StreamTime mMainThreadCurrentTime; bool mMainThreadFinished; bool mMainThreadDestroyed; - // Our media stream graph + // Our media stream graph. null if destroyed on the graph thread. MediaStreamGraphImpl* mGraph; dom::AudioChannel mAudioChannelType; }; /** * This is a stream into which a decoder can write audio and video. * @@ -692,17 +692,17 @@ protected: class SourceMediaStream : public MediaStream { public: SourceMediaStream(DOMMediaStream* aWrapper) : MediaStream(aWrapper), mLastConsumptionState(MediaStreamListener::NOT_CONSUMED), mMutex("mozilla::media::SourceMediaStream"), mUpdateKnownTracksTime(0), mPullEnabled(false), - mUpdateFinished(false), mDestroyed(false) + mUpdateFinished(false) {} virtual SourceMediaStream* AsSourceStream() { return this; } // Media graph thread only virtual void DestroyImpl(); // Call these on any thread. @@ -722,18 +722,16 @@ public: * Add a new track to the stream starting at the given base time (which * must be greater than or equal to the last time passed to * AdvanceKnownTracksTime). Takes ownership of aSegment. aSegment should * contain data starting after aStart. */ void AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment); - struct TrackData; - void ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment); /** * Append media data to a track. Ownership of aSegment remains with the caller, * but aSegment is emptied. * Returns false if the data was not appended because no such track exists * or the stream was already finished. */ bool AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment = nullptr); /** @@ -790,20 +788,23 @@ public: * Returns amount of time (data) that is currently buffered in the track, * assuming playout via PlayAudio or via a TrackUnion - note that * NotifyQueuedTrackChanges() on a SourceMediaStream will occur without * any "extra" buffering, but NotifyQueued TrackChanges() on a TrackUnion * will be buffered. */ TrackTicks GetBufferedTicks(TrackID aID); + void RegisterForAudioMixing(); + // XXX need a Reset API friend class MediaStreamGraphImpl; +protected: struct ThreadAndRunnable { void Init(nsIEventTarget* aTarget, nsIRunnable* aRunnable) { mTarget = aTarget; mRunnable = aRunnable; } nsCOMPtr<nsIEventTarget> mTarget; @@ -835,20 +836,20 @@ public: uint32_t mCommands; // Each time the track updates are flushed to the media graph thread, // the segment buffer is emptied. nsAutoPtr<MediaSegment> mData; nsTArray<ThreadAndRunnable> mDispatchWhenNotEnough; bool mHaveEnough; }; - void RegisterForAudioMixing(); bool NeedsMixing(); -protected: + void ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment); + TrackData* FindDataForTrack(TrackID aID) { for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) { if (mUpdateTracks[i].mID == aID) { return &mUpdateTracks[i]; } } return nullptr; @@ -870,17 +871,16 @@ protected: // held together. Mutex mMutex; // protected by mMutex StreamTime mUpdateKnownTracksTime; nsTArray<TrackData> mUpdateTracks; nsTArray<nsRefPtr<MediaStreamDirectListener> > mDirectListeners; bool mPullEnabled; bool mUpdateFinished; - bool mDestroyed; bool mNeedsMixing; }; /** * Represents a connection between a ProcessedMediaStream and one of its * input streams. * We make these refcounted so that stream-related messages with MediaInputPort* * pointers can be sent to the main thread safely.
--- a/content/media/fmp4/ffmpeg/FFmpegDataDecoder.cpp +++ b/content/media/fmp4/ffmpeg/FFmpegDataDecoder.cpp @@ -92,16 +92,20 @@ FFmpegDataDecoder<LIBAV_VER>::Init() mCodecContext->thread_safe_callbacks = false; mCodecContext->extradata_size = mExtraData.length(); for (int i = 0; i < FF_INPUT_BUFFER_PADDING_SIZE; i++) { mExtraData.append(0); } mCodecContext->extradata = mExtraData.begin(); + if (codec->capabilities & CODEC_CAP_DR1) { + mCodecContext->flags |= CODEC_FLAG_EMU_EDGE; + } + if (avcodec_open2(mCodecContext, codec, nullptr) < 0) { NS_WARNING("Couldn't initialise ffmpeg decoder"); return NS_ERROR_FAILURE; } if (mCodecContext->codec_type == AVMEDIA_TYPE_AUDIO && mCodecContext->sample_fmt != AV_SAMPLE_FMT_FLT && mCodecContext->sample_fmt != AV_SAMPLE_FMT_FLTP) {
--- a/content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp +++ b/content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp @@ -73,58 +73,62 @@ FFmpegH264Decoder<LIBAV_VER>::DecodeFram if (bytesConsumed < 0) { NS_WARNING("FFmpeg video decoder error."); mCallback->Error(); return; } // If we've decoded a frame then we need to output it if (decoded) { - nsAutoPtr<VideoData> data; - VideoInfo info; info.mDisplay = nsIntSize(mCodecContext->width, mCodecContext->height); info.mStereoMode = StereoMode::MONO; info.mHasVideo = true; - data = VideoData::CreateFromImage( - info, mImageContainer, aSample->byte_offset, mFrame->pkt_pts, - aSample->duration, static_cast<Image*>(mFrame->opaque), - aSample->is_sync_point, -1, - gfx::IntRect(0, 0, mCodecContext->width, mCodecContext->height)); + VideoData::YCbCrBuffer b; + b.mPlanes[0].mData = mFrame->data[0]; + b.mPlanes[0].mStride = mFrame->linesize[0]; + b.mPlanes[0].mHeight = mFrame->height; + b.mPlanes[0].mWidth = mFrame->width; + b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0; + + b.mPlanes[1].mData = mFrame->data[1]; + b.mPlanes[1].mStride = mFrame->linesize[1]; + b.mPlanes[1].mHeight = (mFrame->height + 1) >> 1; + b.mPlanes[1].mWidth = (mFrame->width + 1) >> 1; + b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0; - mCallback->Output(data.forget()); + b.mPlanes[2].mData = mFrame->data[2]; + b.mPlanes[2].mStride = mFrame->linesize[2]; + b.mPlanes[2].mHeight = (mFrame->height + 1) >> 1; + b.mPlanes[2].mWidth = (mFrame->width + 1) >> 1; + b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0; + + VideoData *v = VideoData::Create(info, + mImageContainer, + aSample->byte_offset, + mFrame->pkt_pts, + aSample->duration, + b, + aSample->is_sync_point, + -1, + gfx::IntRect(0, 0, mCodecContext->width, mCodecContext->height)); + if (!v) { + NS_WARNING("image allocation error."); + mCallback->Error(); + return; + } + mCallback->Output(v); } if (mTaskQueue->IsEmpty()) { mCallback->InputExhausted(); } } -static void -PlanarYCbCrDataFromAVFrame(mozilla::layers::PlanarYCbCrData& aData, - AVFrame* aFrame) -{ - aData.mPicX = aData.mPicY = 0; - aData.mPicSize = mozilla::gfx::IntSize(aFrame->width, aFrame->height); - aData.mStereoMode = StereoMode::MONO; - - aData.mYChannel = aFrame->data[0]; - aData.mYStride = aFrame->linesize[0]; - aData.mYSize = aData.mPicSize; - aData.mYSkip = 0; - - aData.mCbChannel = aFrame->data[1]; - aData.mCrChannel = aFrame->data[2]; - aData.mCbCrStride = aFrame->linesize[1]; - aData.mCbSkip = aData.mCrSkip = 0; - aData.mCbCrSize = - mozilla::gfx::IntSize((aFrame->width + 1) / 2, (aFrame->height + 1) / 2); -} - /* static */ int FFmpegH264Decoder<LIBAV_VER>::AllocateBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame) { MOZ_ASSERT(aCodecContext->codec_type == AVMEDIA_TYPE_VIDEO); FFmpegH264Decoder* self = static_cast<FFmpegH264Decoder*>(aCodecContext->opaque); @@ -154,26 +158,27 @@ FFmpegH264Decoder<LIBAV_VER>::ReleaseBuf break; } } int FFmpegH264Decoder<LIBAV_VER>::AllocateYUV420PVideoBuffer( AVCodecContext* aCodecContext, AVFrame* aFrame) { - // Older versions of ffmpeg require that edges be allocated* around* the - // actual image. - int edgeWidth = avcodec_get_edge_width(); + bool needAlign = aCodecContext->codec->capabilities & CODEC_CAP_DR1; + int edgeWidth = needAlign ? avcodec_get_edge_width() : 0; int decodeWidth = aCodecContext->width + edgeWidth * 2; int decodeHeight = aCodecContext->height + edgeWidth * 2; - // Align width and height to possibly speed up decode. - int stride_align[AV_NUM_DATA_POINTERS]; - avcodec_align_dimensions2(aCodecContext, &decodeWidth, &decodeHeight, - stride_align); + if (needAlign) { + // Align width and height to account for CODEC_FLAG_EMU_EDGE. + int stride_align[AV_NUM_DATA_POINTERS]; + avcodec_align_dimensions2(aCodecContext, &decodeWidth, &decodeHeight, + stride_align); + } // Get strides for each plane. av_image_fill_linesizes(aFrame->linesize, aCodecContext->pix_fmt, decodeWidth); // Let FFmpeg set up its YUV plane pointers and tell us how much memory we // need. // Note that we're passing |nullptr| here as the base address as we haven't @@ -210,20 +215,16 @@ FFmpegH264Decoder<LIBAV_VER>::AllocateYU // Unused, but needs to be non-zero to keep ffmpeg happy. aFrame->type = GECKO_FRAME_TYPE; aFrame->extended_data = aFrame->data; aFrame->width = aCodecContext->width; aFrame->height = aCodecContext->height; - mozilla::layers::PlanarYCbCrData data; - PlanarYCbCrDataFromAVFrame(data, aFrame); - ycbcr->SetDataNoCopy(data); - aFrame->opaque = static_cast<void*>(image.forget().take()); return 0; } nsresult FFmpegH264Decoder<LIBAV_VER>::Input(mp4_demuxer::MP4Sample* aSample) {
--- a/content/media/mediasource/MediaSourceDecoder.cpp +++ b/content/media/mediasource/MediaSourceDecoder.cpp @@ -226,16 +226,22 @@ private: MediaDecoderReader* GetVideoReader() { if (mActiveVideoDecoder == -1) { return nullptr; } return mDecoders[mActiveVideoDecoder]->GetReader(); } + void SetMediaSourceDuration(double aDuration) { + MOZ_ASSERT(NS_IsMainThread()); + ErrorResult dummy; + mMediaSource->SetDuration(aDuration, dummy); + } + nsTArray<nsRefPtr<SubBufferDecoder>> mPendingDecoders; nsTArray<nsRefPtr<SubBufferDecoder>> mDecoders; int32_t mActiveVideoDecoder; int32_t mActiveAudioDecoder; dom::MediaSource* mMediaSource; }; @@ -580,18 +586,19 @@ MediaSourceReader::ReadMetadata(MediaInf maxDuration = std::max(maxDuration, mDecoders[i]->GetMediaDuration()); MSE_DEBUG("%p: MSR::ReadMetadata audio decoder=%u maxDuration=%lld", this, i, maxDuration); } } if (maxDuration != -1) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); mDecoder->SetMediaDuration(maxDuration); - ErrorResult dummy; - mMediaSource->SetDuration(maxDuration, dummy); + nsRefPtr<nsIRunnable> task ( + NS_NewRunnableMethodWithArg<double>(this, &MediaSourceReader::SetMediaSourceDuration, maxDuration)); + NS_DispatchToMainThread(task); } *aInfo = mInfo; *aTags = nullptr; // TODO: Handle metadata. return NS_OK; }
--- a/content/media/moz.build +++ b/content/media/moz.build @@ -4,16 +4,17 @@ # 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/. DIRS += [ 'encoder', 'gmp', 'mediasource', 'ogg', + 'systemservices', 'webaudio', 'webvtt' ] TEST_DIRS += ['compiledtest'] if CONFIG['MOZ_RAW']: DIRS += ['raw']
rename from content/media/webrtc/LoadManager.cpp rename to content/media/systemservices/LoadManager.cpp
rename from content/media/webrtc/LoadManagerFactory.cpp rename to content/media/systemservices/LoadManagerFactory.cpp
rename from content/media/webrtc/LoadManagerFactory.h rename to content/media/systemservices/LoadManagerFactory.h
rename from content/media/webrtc/LoadMonitor.cpp rename to content/media/systemservices/LoadMonitor.cpp
new file mode 100644 --- /dev/null +++ b/content/media/systemservices/OSXRunLoopSingleton.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; 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/. */ + +#include "OSXRunLoopSingleton.h" +#include <mozilla/StaticMutex.h> +#include <mozilla/NullPtr.h> + +#include <AudioUnit/AudioUnit.h> +#include <CoreAudio/AudioHardware.h> +#include <CoreAudio/HostTime.h> +#include <CoreFoundation/CoreFoundation.h> + +static bool gRunLoopSet = false; +static mozilla::StaticMutex gMutex; + +void mozilla_set_coreaudio_notification_runloop_if_needed() +{ + mozilla::StaticMutexAutoLock lock(gMutex); + if (gRunLoopSet) { + return; + } + + /* This is needed so that AudioUnit listeners get called on this thread, and + * not the main thread. If we don't do that, they are not called, or a crash + * occur, depending on the OSX version. */ + AudioObjectPropertyAddress runloop_address = { + kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + CFRunLoopRef run_loop = nullptr; + + OSStatus r; + r = AudioObjectSetPropertyData(kAudioObjectSystemObject, + &runloop_address, + 0, NULL, sizeof(CFRunLoopRef), &run_loop); + if (r != noErr) { + NS_WARNING("Could not make global CoreAudio notifications use their own thread."); + } + + gRunLoopSet = true; +}
new file mode 100644 --- /dev/null +++ b/content/media/systemservices/OSXRunLoopSingleton.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef OSXRUNLOOPSINGLETON_H_ +#define OSXRUNLOOPSINGLETON_H_ + +#include <mozilla/Types.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +/* This function tells CoreAudio to use its own thread for device change + * notifications, and can be called from any thread without external + * synchronization. */ +void MOZ_EXPORT +mozilla_set_coreaudio_notification_runloop_if_needed(); + +#if defined(__cplusplus) +} +#endif + +#endif // OSXRUNLOOPSINGLETON_H_
new file mode 100644 --- /dev/null +++ b/content/media/systemservices/OpenSLESProvider.cpp @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 50; 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/. */ + +#include "OpenSLESProvider.h" +#include "prlog.h" +#include "nsDebug.h" +#include "mozilla/NullPtr.h" + +#include <dlfcn.h> +#include <SLES/OpenSLES_Android.h> +#include <SLES/OpenSLES_AndroidConfiguration.h> + +// NSPR_LOG_MODULES=OpenSLESProvider:5 +#undef LOG +#undef LOG_ENABLED +#if defined(PR_LOGGING) +PRLogModuleInfo *gOpenSLESProviderLog; +#define LOG(args) PR_LOG(gOpenSLESProviderLog, PR_LOG_DEBUG, args) +#define LOG_ENABLED() PR_LOG_TEST(gLoadManagerLog, 5) +#else +#define LOG(args) +#define LOG_ENABLED() (false) +#endif + +namespace mozilla { + +OpenSLESProvider::OpenSLESProvider() + : mLock("OpenSLESProvider.mLock"), + mSLEngine(nullptr), + mSLEngineUsers(0), + mIsRealized(false), + mOpenSLESLib(nullptr) +{ +#if defined(PR_LOGGING) + if (!gOpenSLESProviderLog) + gOpenSLESProviderLog = PR_NewLogModule("OpenSLESProvider"); + LOG(("OpenSLESProvider being initialized")); +#endif +} + +OpenSLESProvider::~OpenSLESProvider() +{ + if (mOpenSLESLib) { + LOG(("OpenSLES Engine was not properly Destroyed")); + (void)dlclose(mOpenSLESLib); + } +} + +/* static */ +OpenSLESProvider& OpenSLESProvider::getInstance() +{ + // This doesn't need a Mutex in C++11 or GCC 4.3+, see N2660 and + // https://gcc.gnu.org/projects/cxx0x.html + static OpenSLESProvider instance; + return instance; +} + +/* static */ +SLresult OpenSLESProvider::Get(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + OpenSLESProvider& provider = OpenSLESProvider::getInstance(); + return provider.GetEngine(aObjectm, aOptionCount, aOptions); +} + +SLresult OpenSLESProvider::GetEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + MutexAutoLock lock(mLock); + LOG(("Getting OpenSLES engine")); + // Bug 1042051: Validate options are the same + if (mSLEngine != nullptr) { + *aObjectm = mSLEngine; + mSLEngineUsers++; + LOG(("Returning existing engine, %d users", mSLEngineUsers)); + return SL_RESULT_SUCCESS; + } else { + int res = ConstructEngine(aObjectm, aOptionCount, aOptions); + if (res == SL_RESULT_SUCCESS) { + // Bug 1042051: Store engine options + mSLEngine = *aObjectm; + mSLEngineUsers++; + LOG(("Returning new engine")); + } else { + LOG(("Error getting engine: %d", res)); + } + return res; + } +} + +SLresult OpenSLESProvider::ConstructEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + mLock.AssertCurrentThreadOwns(); + + if (!mOpenSLESLib) { + mOpenSLESLib = dlopen("libOpenSLES.so", RTLD_LAZY); + if (!mOpenSLESLib) { + LOG(("Failed to dlopen OpenSLES library")); + return SL_RESULT_MEMORY_FAILURE; + } + } + + typedef SLresult (*slCreateEngine_t)(SLObjectItf *, + SLuint32, + const SLEngineOption *, + SLuint32, + const SLInterfaceID *, + const SLboolean *); + + slCreateEngine_t f_slCreateEngine = + (slCreateEngine_t)dlsym(mOpenSLESLib, "slCreateEngine"); + int result = f_slCreateEngine(aObjectm, aOptionCount, aOptions, 0, NULL, NULL); + return result; +} + +/* static */ +void OpenSLESProvider::Destroy(SLObjectItf * aObjectm) +{ + OpenSLESProvider& provider = OpenSLESProvider::getInstance(); + provider.DestroyEngine(aObjectm); +} + +void OpenSLESProvider::DestroyEngine(SLObjectItf * aObjectm) +{ + MutexAutoLock lock(mLock); + NS_ASSERTION(mOpenSLESLib, "OpenSLES destroy called but library is not open"); + + mSLEngineUsers--; + LOG(("Freeing engine, %d users left", mSLEngineUsers)); + if (mSLEngineUsers) { + return; + } + + (*(*aObjectm))->Destroy(*aObjectm); + // This assumes SLObjectItf is a pointer, but given the previous line, + // that's a given. + *aObjectm = nullptr; + + (void)dlclose(mOpenSLESLib); + mOpenSLESLib = nullptr; + mIsRealized = false; +} + +/* static */ +SLresult OpenSLESProvider::Realize(SLObjectItf aObjectm) +{ + OpenSLESProvider& provider = OpenSLESProvider::getInstance(); + return provider.RealizeEngine(aObjectm); +} + +SLresult OpenSLESProvider::RealizeEngine(SLObjectItf aObjectm) +{ + MutexAutoLock lock(mLock); + NS_ASSERTION(mOpenSLESLib, "OpenSLES realize called but library is not open"); + NS_ASSERTION(aObjectm != nullptr, "OpenSLES realize engine with empty ObjectItf"); + + if (mIsRealized) { + LOG(("Not realizing already realized engine")); + return SL_RESULT_SUCCESS; + } else { + SLresult res = (*aObjectm)->Realize(aObjectm, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + LOG(("Error realizing OpenSLES engine: %d", res)); + } else { + LOG(("Realized OpenSLES engine")); + mIsRealized = true; + } + return res; + } +} + +} // namespace mozilla + +extern "C" { +SLresult mozilla_get_sles_engine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + return mozilla::OpenSLESProvider::Get(aObjectm, aOptionCount, aOptions); +} + +void mozilla_destroy_sles_engine(SLObjectItf * aObjectm) +{ + mozilla::OpenSLESProvider::Destroy(aObjectm); +} + +SLresult mozilla_realize_sles_engine(SLObjectItf aObjectm) +{ + return mozilla::OpenSLESProvider::Realize(aObjectm); +} + +} +
new file mode 100644 --- /dev/null +++ b/content/media/systemservices/OpenSLESProvider.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 _OPENSLESPROVIDER_H_ +#define _OPENSLESPROVIDER_H_ + +#include <SLES/OpenSLES.h> +#include <mozilla/Types.h> + +#ifdef __cplusplus +extern "C" { +#endif +extern MOZ_EXPORT +SLresult mozilla_get_sles_engine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); +extern MOZ_EXPORT +void mozilla_destroy_sles_engine(SLObjectItf * aObjectm); +/* Realize is always in synchronous mode. */ +extern MOZ_EXPORT +SLresult mozilla_realize_sles_engine(SLObjectItf aObjectm); +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +#include "mozilla/Mutex.h" + +extern PRLogModuleInfo *gOpenSLESProviderLog; + +namespace mozilla { + +class OpenSLESProvider { +public: + static SLresult Get(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); + static void Destroy(SLObjectItf * aObjectm); + static SLresult Realize(SLObjectItf aObjectm); +private: + OpenSLESProvider(); + ~OpenSLESProvider(); + OpenSLESProvider(OpenSLESProvider const&); // NO IMPLEMENTATION + void operator=(OpenSLESProvider const&); // NO IMPLEMENTATION + static OpenSLESProvider& getInstance(); + SLresult GetEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); + SLresult ConstructEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); + SLresult RealizeEngine(SLObjectItf aObjectm); + void DestroyEngine(SLObjectItf * aObjectm); + + // Protect all our internal variables + mozilla::Mutex mLock; + SLObjectItf mSLEngine; + int mSLEngineUsers; + bool mIsRealized; + void *mOpenSLESLib; +}; + +} //namespace +#endif // cplusplus + +#endif /* _OPENSLESPROVIDER_H_ */
new file mode 100644 --- /dev/null +++ b/content/media/systemservices/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG['MOZ_WEBRTC']: + EXPORTS += ['LoadManager.h', + 'LoadManagerFactory.h', + 'LoadMonitor.h', + ] + UNIFIED_SOURCES += ['LoadManager.cpp', + 'LoadManagerFactory.cpp', + 'LoadMonitor.cpp', + ] + LOCAL_INCLUDES += [ + '/media/webrtc/trunk', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gonk'): + EXPORTS += [ + 'OpenSLESProvider.h' + ] + UNIFIED_SOURCES += [ + 'OpenSLESProvider.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += ['OSXRunLoopSingleton.cpp'] + EXPORTS += ['OSXRunLoopSingleton.h'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + CXXFLAGS += [ + '-I%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [ + 'frameworks/wilhelm/include', + 'system/media/wilhelm/include', + ] + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul'
--- a/content/media/test/manifest.js +++ b/content/media/test/manifest.js @@ -3,17 +3,17 @@ // "bogus/duh" in each list. // These are small test files, good for just seeing if something loads. We // really only need one test file per backend here. var gSmallTests = [ { name:"small-shot.ogg", type:"audio/ogg", duration:0.276 }, { name:"small-shot.m4a", type:"audio/mp4", duration:0.29 }, { name:"small-shot.mp3", type:"audio/mpeg", duration:0.27 }, - { name:"small-shot-mp3.mp4", type:"audio/mp4; codecs=mp3", duration:0.34 }, + // { name:"small-shot-mp3.mp4", type:"audio/mp4; codecs=mp3", duration:0.34 }, { name:"r11025_s16_c1.wav", type:"audio/x-wav", duration:1.0 }, { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.266 }, { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 }, { name:"vp9.webm", type:"video/webm", width:320, height:240, duration:4 }, { name:"detodos.opus", type:"audio/ogg; codecs=opus", duration:2.9135 }, { name:"gizmo.mp4", type:"video/mp4", duration:5.56 }, { name:"bogus.duh", type:"bogus/duh" } ];
--- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -91,19 +91,20 @@ AudioContext::AudioContext(nsPIDOMWindow , mIsShutDown(false) { aWindow->AddAudioContext(this); // Note: AudioDestinationNode needs an AudioContext that must already be // bound to the window. mDestination = new AudioDestinationNode(this, aIsOffline, aChannel, aNumberOfChannels, aLength, aSampleRate); - // We skip calling SetIsOnlyNodeForContext during mDestination's constructor, - // because we can only call SetIsOnlyNodeForContext after mDestination has - // been set up. + // We skip calling SetIsOnlyNodeForContext and the creation of the + // audioChannelAgent during mDestination's constructor, because we can only + // call them after mDestination has been set up. + mDestination->CreateAudioChannelAgent(); mDestination->SetIsOnlyNodeForContext(true); } AudioContext::~AudioContext() { nsPIDOMWindow* window = GetOwner(); if (window) { window->RemoveAudioContext(this);
--- a/content/media/webaudio/AudioDestinationNode.cpp +++ b/content/media/webaudio/AudioDestinationNode.cpp @@ -253,18 +253,50 @@ private: bool mLastInputMuted; }; static bool UseAudioChannelService() { return Preferences::GetBool("media.useAudioChannelService"); } +class EventProxyHandler MOZ_FINAL : public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + + explicit EventProxyHandler(nsIDOMEventListener* aNode) + { + MOZ_ASSERT(aNode); + mWeakNode = do_GetWeakReference(aNode); + } + + // nsIDOMEventListener + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE + { + nsCOMPtr<nsIDOMEventListener> listener = do_QueryReferent(mWeakNode); + if (!listener) { + return NS_OK; + } + + auto node = static_cast<AudioDestinationNode*>(listener.get()); + return node->HandleEvent(aEvent); + } + +private: + ~EventProxyHandler() + { } + + nsWeakPtr mWeakNode; +}; + +NS_IMPL_ISUPPORTS(EventProxyHandler, nsIDOMEventListener) + NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode, - mAudioChannelAgent) + mAudioChannelAgent, mEventProxyHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode) NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback) NS_INTERFACE_MAP_END_INHERITING(AudioNode) NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode) NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode) @@ -300,27 +332,16 @@ AudioDestinationNode::AudioDestinationNo mStream->SetAudioChannelType(aChannel); mStream->AddMainThreadListener(this); mStream->AddAudioOutput(&gWebAudioOutputKey); if (aChannel != AudioChannel::Normal) { ErrorResult rv; SetMozAudioChannelType(aChannel, rv); } - - if (!aIsOffline && UseAudioChannelService()) { - nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner()); - if (target) { - target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, - /* useCapture = */ true, - /* wantsUntrusted = */ false); - } - - CreateAudioChannelAgent(); - } } AudioDestinationNode::~AudioDestinationNode() { } size_t AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const @@ -342,17 +363,18 @@ AudioDestinationNode::DestroyMediaStream { if (mAudioChannelAgent && !Context()->IsOffline()) { mAudioChannelAgent->StopPlaying(); mAudioChannelAgent = nullptr; nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner()); NS_ENSURE_TRUE_VOID(target); - target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, + target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + mEventProxyHelper, /* useCapture = */ true); } if (!mStream) return; mStream->RemoveMainThreadListener(this); MediaStreamGraph* graph = mStream->Graph(); @@ -560,16 +582,34 @@ AudioDestinationNode::CheckAudioChannelP &perm); return perm == nsIPermissionManager::ALLOW_ACTION; } void AudioDestinationNode::CreateAudioChannelAgent() { + if (mIsOffline || !UseAudioChannelService()) { + return; + } + + if (!mEventProxyHelper) { + nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner()); + if (target) { + // We use a proxy because otherwise the event listerner would hold a + // reference of the destination node, and by extension, everything + // connected to it. + mEventProxyHelper = new EventProxyHandler(this); + target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + mEventProxyHelper, + /* useCapture = */ true, + /* wantsUntrusted = */ false); + } + } + if (mAudioChannelAgent) { mAudioChannelAgent->StopPlaying(); } mAudioChannelAgent = new AudioChannelAgent(); mAudioChannelAgent->InitWithWeakCallback(GetOwner(), static_cast<int32_t>(mAudioChannel), this);
--- a/content/media/webaudio/AudioDestinationNode.h +++ b/content/media/webaudio/AudioDestinationNode.h @@ -12,16 +12,17 @@ #include "nsIDOMEventListener.h" #include "nsIAudioChannelAgent.h" #include "AudioChannelCommon.h" namespace mozilla { namespace dom { class AudioContext; +class EventProxyHandler; class AudioDestinationNode : public AudioNode , public nsIDOMEventListener , public nsIAudioChannelAgentCallback , public MainThreadMediaStreamListener { public: // This node type knows what MediaStreamGraph to use based on @@ -52,59 +53,62 @@ public: void Mute(); void Unmute(); void StartRendering(); void OfflineShutdown(); - // nsIDOMEventListener + // nsIDOMEventListener - by proxy NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent); AudioChannel MozAudioChannelType() const; void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv); virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE; void FireOfflineCompletionEvent(); // An amount that should be added to the MediaStream's current time to // get the AudioContext.currentTime. double ExtraCurrentTime(); // When aIsOnlyNode is true, this is the only node for the AudioContext. void SetIsOnlyNodeForContext(bool aIsOnlyNode); + void CreateAudioChannelAgent(); + virtual const char* NodeType() const { return "AudioDestinationNode"; } virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE; virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE; void InputMuted(bool aInputMuted); protected: virtual ~AudioDestinationNode(); private: bool CheckAudioChannelPermissions(AudioChannel aValue); - void CreateAudioChannelAgent(); void SetCanPlay(bool aCanPlay); void NotifyStableState(); void ScheduleStableStateNotification(); SelfReference<AudioDestinationNode> mOfflineRenderingRef; uint32_t mFramesToProduce; nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent; + nsRefPtr<EventProxyHandler> mEventProxyHelper; + // Audio Channel Type. AudioChannel mAudioChannel; bool mIsOffline; bool mHasFinished; bool mAudioChannelAgentPlaying; TimeStamp mStartedBlockingDueToBeingOnlyNode; double mExtraCurrentTime;
--- a/content/media/webrtc/moz.build +++ b/content/media/webrtc/moz.build @@ -9,24 +9,18 @@ XPIDL_MODULE = 'content_webrtc' EXPORTS += [ 'MediaEngine.h', 'MediaEngineDefault.h', 'MediaTrackConstraints.h', ] if CONFIG['MOZ_WEBRTC']: EXPORTS += ['AudioOutputObserver.h', - 'LoadManager.h', - 'LoadManagerFactory.h', - 'LoadMonitor.h', 'MediaEngineWebRTC.h'] UNIFIED_SOURCES += [ - 'LoadManager.cpp', - 'LoadManagerFactory.cpp', - 'LoadMonitor.cpp', 'MediaEngineTabVideoSource.cpp', 'MediaEngineWebRTCAudio.cpp', 'MediaEngineWebRTCVideo.cpp', ] # MediaEngineWebRTC.cpp needs to be built separately. SOURCES += [ 'MediaEngineWebRTC.cpp', ]
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -7410,17 +7410,18 @@ nsDocShell::CreateAboutBlankContentViewe mFiredUnloadEvent = false; nsCOMPtr<nsIDocumentLoaderFactory> docFactory = nsContentUtils::FindInternalContentViewer("text/html"); if (docFactory) { nsCOMPtr<nsIPrincipal> principal; if (mSandboxFlags & SANDBOXED_ORIGIN) { - principal = do_CreateInstance("@mozilla.org/nullprincipal;1"); + principal = nsNullPrincipal::CreateWithInheritedAttributes(aPrincipal); + NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); } else { principal = aPrincipal; } // generate (about:blank) document to load docFactory->CreateBlankDocument(mLoadGroup, principal, getter_AddRefs(blankDoc)); if (blankDoc) { // Hack: set the base URI manually, since this document never @@ -11141,20 +11142,18 @@ nsDocShell::AddToSessionHistory(nsIURI * } aChannel->GetOwner(getter_AddRefs(owner)); if (!owner) { nsCOMPtr<nsILoadInfo> loadInfo; aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); if (loadInfo) { // For now keep storing just the principal in the SHEntry. if (loadInfo->GetLoadingSandboxed()) { - owner = do_CreateInstance(NS_NULLPRINCIPAL_CONTRACTID, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } + owner = nsNullPrincipal::CreateWithInheritedAttributes(loadInfo->LoadingPrincipal()); + NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); } else if (loadInfo->GetForceInheritPrincipal()) { owner = loadInfo->LoadingPrincipal(); } } } } //Title is set in nsDocShell::SetTitle()
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -354,17 +354,17 @@ nsDOMWindowUtils::SetDisplayPortForEleme nsPresContext::CSSPixelsToAppUnits(aYPx), nsPresContext::CSSPixelsToAppUnits(aWidthPx), nsPresContext::CSSPixelsToAppUnits(aHeightPx)); content->SetProperty(nsGkAtoms::DisplayPort, new DisplayPortPropertyData(displayport, aPriority), nsINode::DeleteProperty<DisplayPortPropertyData>); - if (gfxPrefs::AsyncPanZoomEnabled()) { + if (nsLayoutUtils::UsesAsyncScrolling()) { nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); if (rootScrollFrame && content == rootScrollFrame->GetContent()) { // We are setting a root displayport for a document. // The pres shell needs a special flag set. presShell->SetIgnoreViewportScrolling(true); } }
--- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -918,17 +918,17 @@ nsFocusManager::WindowHidden(nsIDOMWindo nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell(); nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell(); if (oldFocusedContent && oldFocusedContent->IsInDoc()) { NotifyFocusStateChange(oldFocusedContent, mFocusedWindow->ShouldShowFocusRing(), false); - window->UpdateCommands(NS_LITERAL_STRING("focus")); + window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); if (presShell) { SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, oldFocusedContent->GetCurrentDoc(), oldFocusedContent, 1, false); } } @@ -1261,17 +1261,17 @@ nsFocusManager::SetFocusInner(nsIContent nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); if (presShell) ScrollIntoView(presShell, contentToFocus, aFlags); } // update the commands even when inactive so that the attributes for that // window are up to date. if (allowFrameSwitch) - newWindow->UpdateCommands(NS_LITERAL_STRING("focus")); + newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); if (aFlags & FLAG_RAISE) RaiseWindow(newRootWindow); } } bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor, @@ -1581,17 +1581,17 @@ nsFocusManager::Blur(nsPIDOMWindow* aWin } bool result = true; if (sendBlurEvent) { // if there is an active window, update commands. If there isn't an active // window, then this was a blur caused by the active window being lowered, // so there is no need to update the commands if (mActiveWindow) - window->UpdateCommands(NS_LITERAL_STRING("focus")); + window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, content->GetCurrentDoc(), content, 1, false); } // if we are leaving the document or the window was lowered, make the caret // invisible. if (aIsLeavingDocument || !mActiveWindow) @@ -1802,27 +1802,27 @@ nsFocusManager::Focus(nsPIDOMWindow* aWi IMEStateManager::OnChangeFocus(presContext, aContent, GetFocusMoveActionCause(aFlags)); // as long as this focus wasn't because a window was raised, update the // commands // XXXndeakin P2 someone could adjust the focus during the update if (!aWindowRaised) - aWindow->UpdateCommands(NS_LITERAL_STRING("focus")); + aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, aContent->GetCurrentDoc(), aContent, aFlags & FOCUSMETHOD_MASK, aWindowRaised, isRefocus); } else { IMEStateManager::OnChangeFocus(presContext, nullptr, GetFocusMoveActionCause(aFlags)); if (!aWindowRaised) { - aWindow->UpdateCommands(NS_LITERAL_STRING("focus")); + aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); } } } else { // If the window focus event (fired above when aIsNewDocument) caused // the plugin not to be focusable, update the system focus by focusing // the root widget. if (aAdjustWidgets && objectFrameWidget && @@ -1837,17 +1837,17 @@ nsFocusManager::Focus(nsPIDOMWindow* aWi } } nsPresContext* presContext = presShell->GetPresContext(); IMEStateManager::OnChangeFocus(presContext, nullptr, GetFocusMoveActionCause(aFlags)); if (!aWindowRaised) - aWindow->UpdateCommands(NS_LITERAL_STRING("focus")); + aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); } // update the caret visibility and position to match the newly focused // element. However, don't update the position if this was a focus due to a // mouse click as the selection code would already have moved the caret as // needed. If this is a different document than was focused before, also // update the caret's visibility. If this is the same document, the caret // visibility should be the same as before so there is no need to update it.
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -194,16 +194,18 @@ #include "mozilla/dom/StructuredCloneTags.h" #ifdef MOZ_GAMEPAD #include "mozilla/dom/GamepadService.h" #endif #include "nsRefreshDriver.h" +#include "mozilla/dom/SelectionChangeEvent.h" + #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #include "nsLocation.h" #include "nsHTMLDocument.h" #include "nsWrapperCacheInlines.h" #include "mozilla/DOMEventTargetHelper.h" #include "prrng.h" #include "nsSandboxFlags.h" @@ -268,16 +270,17 @@ bool nsGlobalWindow::sIdleObserversAPIFu static nsIEntropyCollector *gEntropyCollector = nullptr; static int32_t gRefCnt = 0; static int32_t gOpenPopupSpamCount = 0; static PopupControlState gPopupControlState = openAbused; static int32_t gRunningTimeoutDepth = 0; static bool gMouseDown = false; static bool gDragServiceDisabled = false; +static bool gSelectionCaretPrefEnabled = false; static FILE *gDumpFile = nullptr; static uint64_t gNextWindowID = 0; static uint32_t gSerialCounter = 0; static uint32_t gTimeoutsRecentlySet = 0; static TimeStamp gLastRecordedRecentTimeouts; #define STATISTICS_INTERVAL (30 * PR_MSEC_PER_SEC) #ifdef DEBUG_jst @@ -1170,16 +1173,19 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW "dom.min_timeout_value", DEFAULT_MIN_TIMEOUT_VALUE); Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue, "dom.min_background_timeout_value", DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE); Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled, "dom.idle-observers-api.fuzz_time.disabled", false); + Preferences::AddBoolVarCache(&gSelectionCaretPrefEnabled, + "selectioncaret.enabled", + false); } if (gDumpFile == nullptr) { const nsAdoptingCString& fname = Preferences::GetCString("browser.dom.window.dump.file"); if (!fname.IsEmpty()) { // if this fails to open, Dump() knows to just go to stdout // on null. @@ -9243,35 +9249,60 @@ public: return mDispatcher->UpdateCommands(mAction); } nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher; nsString mAction; }; NS_IMETHODIMP -nsGlobalWindow::UpdateCommands(const nsAString& anAction) +nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) { nsPIDOMWindow *rootWindow = nsGlobalWindow::GetPrivateRoot(); if (!rootWindow) return NS_OK; nsCOMPtr<nsIDOMXULDocument> xulDoc = do_QueryInterface(rootWindow->GetExtantDoc()); // See if we contain a XUL document. - if (xulDoc) { + // selectionchange action is only used for mozbrowser, not for XUL. So we bypass + // XUL command dispatch if anAction is "selectionchange". + if (xulDoc && !anAction.EqualsLiteral("selectionchange")) { // Retrieve the command dispatcher and call updateCommands on it. nsCOMPtr<nsIDOMXULCommandDispatcher> xulCommandDispatcher; xulDoc->GetCommandDispatcher(getter_AddRefs(xulCommandDispatcher)); if (xulCommandDispatcher) { nsContentUtils::AddScriptRunner(new CommandDispatcher(xulCommandDispatcher, anAction)); } } + if (gSelectionCaretPrefEnabled && mDoc && anAction.EqualsLiteral("selectionchange")) { + SelectionChangeEventInit init; + init.mBubbles = true; + if (aSel) { + nsCOMPtr<nsIDOMRange> range; + nsresult rv = aSel->GetRangeAt(0, getter_AddRefs(range)); + if (NS_SUCCEEDED(rv) && range) { + nsRefPtr<nsRange> nsrange = static_cast<nsRange*>(range.get()); + init.mBoundingClientRect = nsrange->GetBoundingClientRect(true, false); + range->ToString(init.mSelectedText); + init.mReason = aReason; + } + + nsRefPtr<SelectionChangeEvent> event = + SelectionChangeEvent::Constructor(mDoc, NS_LITERAL_STRING("mozselectionchange"), init); + + event->SetTrusted(true); + event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true; + bool ret; + mDoc->DispatchEvent(event, &ret); + } + } + return NS_OK; } Selection* nsGlobalWindow::GetSelection(ErrorResult& aError) { FORWARD_TO_OUTER_OR_THROW(GetSelection, (aError), aError, nullptr);
new file mode 100644 --- /dev/null +++ b/dom/base/test/iframe_main_bug1022229.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> +<script> + // Uncomment this definition of SimpleTest (and comment out the one below) to + // debug in mozBrowser mode. + /* + var SimpleTest = { ok: function(c, m) { dump(m + ": " + c + "\n"); }, + info: function(m) { dump(m + "\n"); }, + finish: function() { dump("Test done\n");} }; + */ + var SimpleTest = parent.SimpleTest; + + var ok = SimpleTest.ok; + var info = SimpleTest.info; + var finish = SimpleTest.finish.bind(SimpleTest); + + var gotTargetedMessage = false; + window.onmessage = function(evt) { + var message = evt.data; + info("Received message: " + message); + switch (message) { + case 'targeted': + gotTargetedMessage = true; + break; + case 'broadcast': + ok(gotTargetedMessage, "Should have received targeted message"); + finish(); + break; + default: + ok(false, "Unexpected message: " + message); + break; + } + } +</script> +</head> +<body> +<iframe src="iframe_sandbox_bug1022229.html" sandbox="allow-scripts"></iframe> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/base/test/iframe_sandbox_bug1022229.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> +<script> + // First send an origin-restricted message, and then send a non-restricted + // message to end the test promptly even in a failure mode. + parent.postMessage('targeted', 'http://mochi.test:8888'); + setTimeout(function() { parent.postMessage('broadcast', '*'); }, 0); +</script> +</head> +<body> +</body> +</html>
--- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -1,12 +1,14 @@ [DEFAULT] support-files = audio.ogg iframe_bug976673.html + iframe_main_bug1022229.html + iframe_sandbox_bug1022229.html iframe_messageChannel_cloning.html iframe_messageChannel_chrome.html iframe_messageChannel_pingpong.html iframe_messageChannel_post.html file_empty.html iframe_postMessage_solidus.html file_setname.html @@ -16,16 +18,17 @@ support-files = skip-if = buildapp == 'mulet' [test_bug793311.html] [test_bug913761.html] [test_bug976673.html] [test_bug978522.html] [test_bug979109.html] [test_bug989665.html] [test_bug999456.html] +[test_bug1022229.html] [test_clearTimeoutIntervalNoArg.html] [test_consoleEmptyStack.html] [test_constructor-assignment.html] [test_constructor.html] [test_dialogArguments.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s [test_document.all_unqualified.html] [test_domcursor.html]
new file mode 100644 --- /dev/null +++ b/dom/base/test/test_bug1022229.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1022229 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1022229</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for postMessage between sandboxed iframe and non-sandboxed window. + This test is particularly interesting on b2g where we're in a mozBrowser. + + We set the test up with an extra iframe so that we can easily run it in + an artificial mozbrowser for desktop builds. + **/ + SimpleTest.waitForExplicitFinish(); + function go() { + var ifr = document.createElement('iframe'); + + /* Uncomment this chunk to run in a mozBrowser. Make sure to uncomment the + chunk in iframe_main as well. */ + /* + SpecialPowers.Services.prefs.setBoolPref("dom.mozBrowserFramesEnabled", true); + SpecialPowers.Services.prefs.setBoolPref("dom.ipc.browser_frames.oop_by_default", false); + SpecialPowers.addPermission("browser", true, document); + SpecialPowers.wrap(ifr).mozbrowser = true; + */ + + ifr.setAttribute('src', 'iframe_main_bug1022229.html'); + document.body.appendChild(ifr); + } + + </script> +</head> +<body onload="go()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1022229">Mozilla Bug 1022229</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html>
--- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2499,25 +2499,17 @@ ConvertExceptionToPromise(JSContext* cx, return WrapNewBindingObject(cx, promise, rval); } /* static */ void CreateGlobalOptions<nsGlobalWindow>::TraceGlobal(JSTracer* aTrc, JSObject* aObj) { - mozilla::dom::TraceProtoAndIfaceCache(aTrc, aObj); - - // We might be called from a GC during the creation of a global, before we've - // been able to set up the compartment private or the XPCWrappedNativeScope, - // so we need to null-check those. - xpc::CompartmentPrivate* compartmentPrivate = xpc::CompartmentPrivate::Get(aObj); - if (compartmentPrivate && compartmentPrivate->scope) { - compartmentPrivate->scope->TraceSelf(aTrc); - } + xpc::TraceXPCGlobal(aTrc, aObj); } /* static */ bool CreateGlobalOptions<nsGlobalWindow>::PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal) { // Invoking the XPCWrappedNativeScope constructor automatically hooks it
--- a/dom/browser-element/BrowserElementChildPreload.js +++ b/dom/browser-element/BrowserElementChildPreload.js @@ -103,16 +103,23 @@ function getErrorClass(errorCode) { const OBSERVED_EVENTS = [ 'fullscreen-origin-change', 'ask-parent-to-exit-fullscreen', 'ask-parent-to-rollback-fullscreen', 'xpcom-shutdown', 'activity-done' ]; +const COMMAND_MAP = { + 'cut': 'cmd_cut', + 'copy': 'cmd_copy', + 'paste': 'cmd_paste', + 'selectall': 'cmd_selectAll' +}; + /** * The BrowserElementChild implements one half of <iframe mozbrowser>. * (The other half is, unsurprisingly, BrowserElementParent.) * * This script is injected into an <iframe mozbrowser> via * nsIMessageManager::LoadFrameScript(). * * Our job here is to listen for events within this frame and bubble them up to @@ -195,16 +202,20 @@ BrowserElementChild.prototype = { /* useCapture = */ true, /* wantsUntrusted = */ false); addEventListener('DOMMetaRemoved', this._metaChangedHandler.bind(this), /* useCapture = */ true, /* wantsUntrusted = */ false); + addEventListener('mozselectionchange', + this._selectionChangeHandler.bind(this), + /* useCapture = */ false, + /* wantsUntrusted = */ false); // This listens to unload events from our message manager, but /not/ from // the |content| window. That's because the window's unload event doesn't // bubble, and we're not using a capturing listener. If we'd used // useCapture == true, we /would/ hear unload events from the window, which // is not what we want! addEventListener('unload', this._unloadHandler.bind(this), @@ -233,17 +244,18 @@ BrowserElementChild.prototype = { "stop": this._recvStop, "zoom": this._recvZoom, "unblock-modal-prompt": this._recvStopWaiting, "fire-ctx-callback": this._recvFireCtxCallback, "owner-visibility-change": this._recvOwnerVisibilityChange, "exit-fullscreen": this._recvExitFullscreen.bind(this), "activate-next-paint-listener": this._activateNextPaintListener.bind(this), "set-input-method-active": this._recvSetInputMethodActive.bind(this), - "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this) + "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this), + "do-command": this._recvDoCommand } addMessageListener("browser-element-api:call", function(aMessage) { if (aMessage.data.msg_name in mmCalls) { return mmCalls[aMessage.data.msg_name].apply(self, arguments); } }); @@ -347,16 +359,25 @@ BrowserElementChild.prototype = { if (args.promptType == 'prompt' || args.promptType == 'confirm' || args.promptType == 'custom-prompt') { return returnValue; } }, + _isCommandEnabled: function(cmd) { + let command = COMMAND_MAP[cmd]; + if (!command) { + return false; + } + + return docShell.isCommandEnabled(command); + }, + /** * Spin in a nested event loop until we receive a unblock-modal-prompt message for * this window. */ _waitForResult: function(win) { debug("_waitForResult(" + win + ")"); let utils = win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); @@ -466,23 +487,26 @@ BrowserElementChild.prototype = { if (win == content) { sendAsyncMsg('titlechange', { _payload_: e.target.title }); } else { debug("Not top level!"); } }, + _maybeCopyAttribute: function(src, target, attribute) { + if (src.getAttribute(attribute)) { + target[attribute] = src.getAttribute(attribute); + } + }, + _iconChangedHandler: function(e) { debug('Got iconchanged: (' + e.target.href + ')'); let icon = { href: e.target.href }; - if (e.target.getAttribute('sizes')) { - icon.sizes = e.target.getAttribute('sizes'); - } - + this._maybeCopyAttribute(e.target, icon, 'sizes'); sendAsyncMsg('iconchange', icon); }, _openSearchHandler: function(e) { debug('Got opensearch: (' + e.target.href + ')'); if (e.target.type !== "application/opensearchdescription+xml") { return; @@ -506,17 +530,18 @@ BrowserElementChild.prototype = { // Ignore links which don't come from the top-level // <iframe mozbrowser> window. if (win != content) { debug('Not top level!'); return; } let handlers = { - 'icon': this._iconChangedHandler, + 'icon': this._iconChangedHandler.bind(this), + 'apple-touch-icon': this._iconChangedHandler.bind(this), 'search': this._openSearchHandler, 'manifest': this._manifestChangedHandler }; debug('Got linkAdded: (' + e.target.href + ') ' + e.target.rel); e.target.rel.split(' ').forEach(function(x) { let token = x.toLowerCase(); if (handlers[token]) { @@ -585,16 +610,63 @@ BrowserElementChild.prototype = { if (lang) { meta.lang = lang; } sendAsyncMsg('metachange', meta); }, + _selectionChangeHandler: function(e) { + let isMouseUp = e.reason & Ci.nsISelectionListener.MOUSEUP_REASON; + let isSelectAll = e.reason & Ci.nsISelectionListener.SELECTALL_REASON; + // When selectall happened, gecko will first collapse the range then + // select all. So we will receive two selection change events with + // SELECTALL_REASON. We filter first event by check the length of + // selectedText. + if (!(isMouseUp || (isSelectAll && e.selectedText.length > 0))) { + return; + } + + e.stopPropagation(); + let boundingClientRect = e.boundingClientRect; + let zoomFactor = content.screen.width / content.innerWidth; + + let detail = { + rect: { + width: boundingClientRect.width, + height: boundingClientRect.height, + top: boundingClientRect.top, + bottom: boundingClientRect.bottom, + left: boundingClientRect.left, + right: boundingClientRect.right, + }, + commands: { + canSelectAll: this._isCommandEnabled("selectall"), + canCut: this._isCommandEnabled("cut"), + canCopy: this._isCommandEnabled("copy"), + canPaste: this._isCommandEnabled("paste"), + }, + zoomFactor: zoomFactor, + }; + + // Get correct geometry information if we have nested <iframe mozbrowser> + let currentWindow = e.target.defaultView; + while (currentWindow.realFrameElement) { + let currentRect = currentWindow.realFrameElement.getBoundingClientRect(); + detail.rect.top += currentRect.top; + detail.rect.bottom += currentRect.top; + detail.rect.left += currentRect.left; + detail.rect.right += currentRect.left; + currentWindow = currentWindow.realFrameElement.ownerDocument.defaultView; + } + + sendAsyncMsg("selectionchange", detail); + }, + _themeColorChangedHandler: function(eventType, target) { let meta = { name: 'theme-color', content: target.content, type: eventType.replace('DOMMeta', '').toLowerCase() }; sendAsyncMsg('metachange', meta); }, @@ -901,33 +973,27 @@ BrowserElementChild.prototype = { this._ctxHandlers[data.json.menuitem].click(); this._ctxHandlers = {}; } else { debug("Ignored invalid contextmenu invocation"); } }, _buildMenuObj: function(menu, idPrefix) { - function maybeCopyAttribute(src, target, attribute) { - if (src.getAttribute(attribute)) { - target[attribute] = src.getAttribute(attribute); - } - } - var menuObj = {type: 'menu', items: []}; - maybeCopyAttribute(menu, menuObj, 'label'); + this._maybeCopyAttribute(menu, menuObj, 'label'); for (var i = 0, child; child = menu.children[i++];) { if (child.nodeName === 'MENU') { menuObj.items.push(this._buildMenuObj(child, idPrefix + i + '_')); } else if (child.nodeName === 'MENUITEM') { var id = this._ctxCounter + '_' + idPrefix + i; var menuitem = {id: id, type: 'menuitem'}; - maybeCopyAttribute(child, menuitem, 'label'); - maybeCopyAttribute(child, menuitem, 'icon'); + this._maybeCopyAttribute(child, menuitem, 'label'); + this._maybeCopyAttribute(child, menuitem, 'icon'); this._ctxHandlers[id] = child; menuObj.items.push(menuitem); } } return menuObj; }, _recvSetVisible: function(data) { @@ -1031,16 +1097,22 @@ BrowserElementChild.prototype = { let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); webNav.stop(webNav.STOP_NETWORK); }, _recvZoom: function(data) { docShell.contentViewer.fullZoom = data.json.zoom; }, + _recvDoCommand: function(data) { + if (this._isCommandEnabled(data.json.command)) { + docShell.doCommand(COMMAND_MAP[data.json.command]); + } + }, + _recvSetInputMethodActive: function(data) { let msgData = { id: data.json.id }; if (!this._isContentWindowCreated) { if (data.json.args.isActive) { // To activate the input method, we should wait before the content // window is ready. this._pendingSetInputMethodActive.push(data); return;
--- a/dom/browser-element/BrowserElementParent.jsm +++ b/dom/browser-element/BrowserElementParent.jsm @@ -155,16 +155,21 @@ function BrowserElementParent(frameLoade if (!this._window._browserElementParents) { this._window._browserElementParents = new WeakMap(); this._window.addEventListener('visibilitychange', visibilityChangeHandler, /* useCapture = */ false, /* wantsUntrusted = */ false); } + this._frameElement.addEventListener('mozdocommand', + this._doCommandHandler.bind(this), + /* useCapture = */ false, + /* wantsUntrusted = */ false); + this._window._browserElementParents.set(this, null); // Insert ourself into the prompt service. BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this); if (!isPendingFrame) { this._setupMessageListener(); this._registerAppManifest(); } else { @@ -243,17 +248,18 @@ BrowserElementParent.prototype = { "got-screenshot": this._gotDOMRequestResult, "got-can-go-back": this._gotDOMRequestResult, "got-can-go-forward": this._gotDOMRequestResult, "fullscreen-origin-change": this._remoteFullscreenOriginChange, "rollback-fullscreen": this._remoteFrameFullscreenReverted, "exit-fullscreen": this._exitFullscreen, "got-visible": this._gotDOMRequestResult, "visibilitychange": this._childVisibilityChange, - "got-set-input-method-active": this._gotDOMRequestResult + "got-set-input-method-active": this._gotDOMRequestResult, + "selectionchange": this._handleSelectionChange }; this._mm.addMessageListener('browser-element-api:call', function(aMsg) { if (self._isAlive() && (aMsg.data.msg_name in mmCalls)) { return mmCalls[aMsg.data.msg_name].apply(self, arguments); } }); }, @@ -445,16 +451,27 @@ BrowserElementParent.prototype = { if (!evt.defaultPrevented) { // Unblock the inner frame immediately. Otherwise we'll unblock upon // evt.detail.unblock(). sendUnblockMsg(); } }, + _handleSelectionChange: function(data) { + let evt = this._createEvent('selectionchange', data.json, + /* cancelable = */ false); + this._frameElement.dispatchEvent(evt); + }, + + _doCommandHandler: function(e) { + e.stopPropagation(); + this._sendAsyncMsg('do-command', { command: e.detail.cmd }); + }, + _createEvent: function(evtName, detail, cancelable) { // This will have to change if we ever want to send a CustomEvent with null // detail. For now, it's OK. if (detail !== undefined && detail !== null) { detail = Cu.cloneInto(detail, this._window); return new this._window.CustomEvent('mozbrowser' + evtName, { bubbles: true, cancelable: cancelable,
--- a/dom/browser-element/mochitest/browserElementTestHelpers.js +++ b/dom/browser-element/mochitest/browserElementTestHelpers.js @@ -55,16 +55,20 @@ const browserElementTestHelpers = { ['dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2] ); }, setEnabledPref: function(value) { this._setPref('dom.mozBrowserFramesEnabled', value); }, + setSelectionChangeEnabledPref: function(value) { + this._setPref('selectioncaret.enabled', value); + }, + getOOPByDefaultPref: function() { return this._getBoolPref("dom.ipc.browser_frames.oop_by_default"); }, addPermission: function() { SpecialPowers.addPermission("browser", true, document); this.tempPermissions.push(location.href) },
new file mode 100644 --- /dev/null +++ b/dom/browser-element/mochitest/browserElement_CopyPaste.js @@ -0,0 +1,296 @@ +/* Any copyright is dedicated to the public domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that "cut, copy, paste, selectall" and selectionchange event works from inside an <iframe mozbrowser>. +"use strict"; + +SimpleTest.waitForExplicitFinish(); +browserElementTestHelpers.setEnabledPref(true); +browserElementTestHelpers.setSelectionChangeEnabledPref(true); +browserElementTestHelpers.addPermission(); +var gTextarea = null; +var mm; +var iframe; +var state = 0; +var stateMeaning; +var defaultData; +var pasteData; +var focusScript; + +function copyToClipboard(str) { + gTextarea.value = str; + SpecialPowers.wrap(gTextarea).editor.selectAll(); + SpecialPowers.wrap(gTextarea).editor.copy(); +} + +function getScriptForGetContent() { + var script = 'data:,\ + var elt = content.document.getElementById("text"); \ + var txt = ""; \ + if (elt) { \ + if (elt.tagName === "DIV" || elt.tagName === "BODY") { \ + txt = elt.textContent; \ + } else { \ + txt = elt.value; \ + } \ + } \ + sendAsyncMessage("content-text", txt);'; + return script; +} + +function getScriptForSetFocus() { + var script = 'data:,' + focusScript + 'sendAsyncMessage("content-focus")'; + return script; +} + +function runTest() { + iframe = document.createElement('iframe'); + SpecialPowers.wrap(iframe).mozbrowser = true; + document.body.appendChild(iframe); + + gTextarea = document.createElement('textarea'); + document.body.appendChild(gTextarea); + + mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + + iframe.addEventListener("mozbrowserloadend", function onloadend(e) { + iframe.removeEventListener("mozbrowserloadend", onloadend); + dispatchTest(e); + }); +} + +function doCommand(cmd) { + let doc = iframe.ownerDocument; + let event = doc.createEvent('CustomEvent'); + event.initCustomEvent('mozdocommand', true, true, { cmd: cmd }); + SpecialPowers.wrap(iframe).dispatchEvent(event); +} + +function dispatchTest(e) { + iframe.addEventListener("mozbrowserloadend", function onloadend2(e) { + iframe.removeEventListener("mozbrowserloadend", onloadend2); + SimpleTest.executeSoon(function() { testSelectAll(e); }); + }); + + switch (state) { + case 0: // test for textarea + defaultData = "Test for selection change event"; + pasteData = "from parent "; + iframe.src = "data:text/html,<html><body>" + + "<textarea id='text'>" + defaultData + "</textarea>" + + "</body>" + + "</html>"; + stateMeaning = " (test: textarea)"; + focusScript = "var elt=content.document.getElementById('text');elt.focus();elt.select();"; + break; + case 1: // test for input text + defaultData = "Test for selection change event"; + pasteData = "from parent "; + iframe.src = "data:text/html,<html><body>" + + "<input type='text' id='text' value='" + defaultData + "'>" + + "</body>" + + "</html>"; + stateMeaning = " (test: <input type=text>)"; + focusScript = "var elt=content.document.getElementById('text');elt.focus();elt.select();"; + break; + case 2: // test for input password + defaultData = "Test for selection change event"; + pasteData = "from parent "; + iframe.src = "data:text/html,<html><body>" + + "<input type='password' id='text' value='" + defaultData + "'>" + + "</body>" + + "</html>"; + stateMeaning = " (test: <input type=password>)"; + focusScript = "var elt=content.document.getElementById('text');elt.focus();elt.select();"; + break; + case 3: // test for input number + defaultData = "12345"; + pasteData = "67890"; + iframe.src = "data:text/html,<html><body>" + + "<input type='number' id='text' value='" + defaultData + "'>" + + "</body>" + + "</html>"; + stateMeaning = " (test: <input type=number>)"; + focusScript = "var elt=content.document.getElementById('text');elt.focus();elt.select();"; + break; + case 4: // test for div contenteditable + defaultData = "Test for selection change event"; + pasteData = "from parent "; + iframe.src = "data:text/html,<html><body>" + + "<div contenteditable='true' id='text'>" + defaultData + "</div>" + + "</body>" + + "</html>"; + stateMeaning = " (test: content editable div)"; + focusScript = "var elt=content.document.getElementById('text');elt.focus();"; + break; + case 5: // test for normal div + SimpleTest.finish(); + return; + defaultData = "Test for selection change event"; + pasteData = "from parent "; + iframe.src = "data:text/html,<html><body>" + + "<div id='text'>" + defaultData + "</div>" + + "</body>" + + "</html>"; + stateMeaning = " (test: normal div)"; + focusScript = "var elt=content.document.getElementById('text');elt.focus();"; + break; + case 6: // test for normal div with designMode:on + defaultData = "Test for selection change event"; + pasteData = "from parent "; + iframe.src = "data:text/html,<html><body id='text'>" + + defaultData + + "</body>" + + "<script>document.designMode='on';</script>" + + "</html>"; + stateMeaning = " (test: normal div with designMode:on)"; + focusScript = "var elt=content.document.getElementById('text');elt.focus();"; + break; + default: + SimpleTest.finish(); + break; + } +} + +function testSelectAll(e) { + iframe.addEventListener("mozbrowserselectionchange", function selectchangeforselectall(e) { + iframe.removeEventListener("mozbrowserselectionchange", selectchangeforselectall, true); + ok(true, "got mozbrowserselectionchange event." + stateMeaning); + ok(e.detail, "event.detail is not null." + stateMeaning); + ok(e.detail.width != 0, "event.detail.width is not zero" + stateMeaning); + ok(e.detail.height != 0, "event.detail.height is not zero" + stateMeaning); + SimpleTest.executeSoon(function() { testCopy1(e); }); + }, true); + + mm.addMessageListener('content-focus', function messageforfocus(msg) { + mm.removeMessageListener('content-focus', messageforfocus); + // test selectall command, after calling this the selectionchange event should be fired. + doCommand('selectall'); + }); + + mm.loadFrameScript(getScriptForSetFocus(), false); +} + +function testCopy1(e) { + // Right now we're at "selectall" state, so we can test copy commnad by + // calling doCommand + copyToClipboard(""); + let setup = function() { + doCommand("copy"); + }; + + let nextTest = function(success) { + ok(success, "copy command works" + stateMeaning); + SimpleTest.executeSoon(function() { testPaste1(e); }); + }; + + let success = function() { + nextTest(true); + } + + let fail = function() { + nextTest(false); + } + + let compareData = defaultData; + if (state == 2) { + // In password case, we just check length of text at clipboard is equal + // to length of defaultData + compareData = function(clipboardText) { + return clipboardText.length == defaultData.length; + }; + } + + SimpleTest.waitForClipboard(compareData, setup, success, fail); +} + +function testPaste1(e) { + // Next test paste command, first we copy to global clipboard in parent side. + // Then paste it to child side. + copyToClipboard(pasteData); + + doCommand("paste"); + SimpleTest.executeSoon(function() { testPaste2(e); }); +} + +function testPaste2(e) { + mm.addMessageListener('content-text', function messageforpaste(msg) { + mm.removeMessageListener('content-text', messageforpaste); + if (state == 5) { + // normal div cannot paste, so the content remain unchange + ok(SpecialPowers.wrap(msg).json === defaultData, "paste command works" + stateMeaning); + } else if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) { + // Something weird when we doCommand with content editable element in OOP. Mark this case as todo + todo(false, "paste command works" + stateMeaning); + } else { + ok(SpecialPowers.wrap(msg).json === pasteData, "paste command works" + stateMeaning); + } + SimpleTest.executeSoon(function() { testCut1(e); }); + }); + + mm.loadFrameScript(getScriptForGetContent(), false); +} + +function testCut1(e) { + // Clean clipboard first + copyToClipboard(""); + let setup = function() { + doCommand("selectall"); + doCommand("cut"); + }; + + let nextTest = function(success) { + if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) { + // Something weird when we doCommand with content editable element in OOP. + todo(false, "cut function works" + stateMeaning); + } else { + ok(success, "cut function works" + stateMeaning); + } + SimpleTest.executeSoon(function() { testCut2(e); }); + }; + + let success = function() { + nextTest(true); + } + + let fail = function() { + nextTest(false); + } + + let compareData = pasteData; + if (state == 2) { + // In password case, we just check length of text at clipboard is equal + // to length of pasteData + compareData = function(clipboardText) { + return clipboardText.length == pasteData.length; + }; + } else if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) { + // Something weird when we doCommand with content editable element in OOP. + // Always true in this case + compareData = function() { return true; } + } + + SimpleTest.waitForClipboard(compareData, setup, success, fail); +} + +function testCut2(e) { + mm.addMessageListener('content-text', function messageforcut(msg) { + mm.removeMessageListener('content-text', messageforcut); + // normal div cannot cut + if (state == 5) { + ok(SpecialPowers.wrap(msg).json !== "", "cut command works" + stateMeaning); + } else if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) { + // Something weird when we doCommand with content editable element in OOP. Mark this case as todo + todo(false, "cut command works" + stateMeaning); + } else { + ok(SpecialPowers.wrap(msg).json === "", "cut command works" + stateMeaning); + } + + state++; + dispatchTest(e); + }); + + mm.loadFrameScript(getScriptForGetContent(), false); +} + +addEventListener('testready', runTest);
--- a/dom/browser-element/mochitest/browserElement_Iconchange.js +++ b/dom/browser-element/mochitest/browserElement_Iconchange.js @@ -7,19 +7,23 @@ SimpleTest.waitForExplicitFinish(); browserElementTestHelpers.setEnabledPref(true); browserElementTestHelpers.addPermission(); function createHtml(link) { return 'data:text/html,<html><head>' + link + '<body></body></html>'; } -function createLink(name, sizes) { +function createLink(name, sizes, rel) { var s = sizes ? 'sizes="' + sizes + '"' : ''; - return '<link rel="icon" type="image/png" ' + s + ' href="http://example.com/' + name + '.png">'; + if (!rel) { + rel = 'icon'; + } + return '<link rel="' + rel + '" type="image/png" ' + s + + ' href="http://example.com/' + name + '.png">'; } function runTest() { var iframe1 = document.createElement('iframe'); SpecialPowers.wrap(iframe1).mozbrowser = true; document.body.appendChild(iframe1); // iframe2 is a red herring; we modify its favicon but don't listen for @@ -83,21 +87,25 @@ function runTest() { // Make sure icon check is case insensitive SpecialPowers.getBrowserFrameMessageManager(iframe1) .loadFrameScript("data:,content.document.head.insertAdjacentHTML('beforeend', '<link rel=ICON href=http://example.com/ucaseicon.png>')", /* allowDelayedLoad = */ false); } else if (numIconChanges == 6) { is(e.detail.href, 'http://example.com/ucaseicon.png'); - iframe1.src = createHtml(createLink('testsize', '50x50')); + iframe1.src = createHtml(createLink('testsize', '50x50', 'icon')); } else if (numIconChanges == 7) { is(e.detail.href, 'http://example.com/testsize.png'); is(e.detail.sizes, '50x50'); + iframe1.src = createHtml(createLink('testapple1', '100x100', 'apple-touch-icon')); + } else if (numIconChanges == 8) { + is(e.detail.href, 'http://example.com/testapple1.png'); + is(e.detail.sizes, '100x100'); SimpleTest.finish(); } else { ok(false, 'Too many iconchange events.'); } }); iframe3.addEventListener('mozbrowsericonchange', function(e) { ok(false, 'Should not get a iconchange event for iframe3.');
--- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -23,16 +23,17 @@ skip-if = (toolkit == 'gonk' && !debug) skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_BackForward.html] [test_browserElement_oop_BadScreenshot.html] [test_browserElement_oop_BrowserWindowNamespace.html] skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_BrowserWindowResize.html] [test_browserElement_oop_Close.html] [test_browserElement_oop_CookiesNotThirdParty.html] +[test_browserElement_oop_CopyPaste.html] [test_browserElement_oop_DOMRequestError.html] [test_browserElement_oop_DataURI.html] [test_browserElement_oop_DocumentFirstPaint.html] [test_browserElement_oop_Download.html] disabled = bug 1022281 [test_browserElement_oop_ErrorSecurity.html] skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_FirstPaint.html]
--- a/dom/browser-element/mochitest/mochitest.ini +++ b/dom/browser-element/mochitest/mochitest.ini @@ -14,16 +14,17 @@ support-files = browserElement_ThemeColor.js browserElement_BrowserWindowNamespace.js browserElement_BrowserWindowResize.js browserElement_Close.js browserElement_CloseApp.js browserElement_CloseFromOpener.js browserElement_ContextmenuEvents.js browserElement_CookiesNotThirdParty.js + browserElement_CopyPaste.js browserElement_DOMRequestError.js browserElement_DataURI.js browserElement_DocumentFirstPaint.js browserElement_Download.js browserElement_ErrorSecurity.js browserElement_ExposableURI.js browserElement_FirstPaint.js browserElement_ForwardName.js @@ -131,16 +132,17 @@ skip-if = buildapp == 'b2g' [test_browserElement_inproc_BrowserWindowResize.html] [test_browserElement_inproc_Close.html] [test_browserElement_inproc_CloseApp.html] skip-if = toolkit == 'android' || buildapp == 'b2g' # android(FAILS, bug 796982) androidx86(FAILS, bug 796982) [test_browserElement_inproc_CloseFromOpener.html] skip-if = buildapp == 'b2g' [test_browserElement_inproc_ContextmenuEvents.html] [test_browserElement_inproc_CookiesNotThirdParty.html] +[test_browserElement_inproc_CopyPaste.html] [test_browserElement_inproc_DOMRequestError.html] [test_browserElement_inproc_DataURI.html] [test_browserElement_inproc_DocumentFirstPaint.html] [test_browserElement_inproc_Download.html] disabled = bug 1022281 [test_browserElement_inproc_ExposableURI.html] [test_browserElement_inproc_FirstPaint.html] [test_browserElement_inproc_ForwardName.html]
new file mode 100644 --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_inproc_CopyPaste.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=987040 +--> +<head> + <title>Test for Bug 987040</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="browserElementTestHelpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=987040">Mozilla Bug 987040</a> + +<script type="application/javascript;version=1.7" src="browserElement_CopyPaste.js"> +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_oop_CopyPaste.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=987040 +--> +<head> + <title>Test for Bug 987040</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="browserElementTestHelpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=987040">Mozilla Bug 987040</a> + +<script type="application/javascript;version=1.7" src="browserElement_CopyPaste.js"> +</script> +</body> +</html>
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -556,17 +556,17 @@ NS_INTERFACE_MAP_END uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0; DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; CanvasRenderingContext2D::CanvasRenderingContext2D() : mForceSoftware(false) // these are the default values from the Canvas spec - , mWidth(300), mHeight(150) + , mWidth(0), mHeight(0) , mZero(false), mOpaque(false) , mResetLayer(true) , mIPC(false) , mStream(nullptr) , mIsEntireFrameInvalid(false) , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) , mInvalidateCount(0) {
new file mode 100644 --- /dev/null +++ b/dom/canvas/test/webgl-conformance/failing_tests_windows_msbasicrender.txt @@ -0,0 +1,4 @@ +conformance/context/context-attributes-alpha-depth-stencil-antialias.html +conformance/renderbuffers/framebuffer-object-attachment.html +conformance/state/gl-object-get-calls.html +conformance/more/functions/isTests.html
--- a/dom/canvas/test/webgl-conformance/mochitest.ini +++ b/dom/canvas/test/webgl-conformance/mochitest.ini @@ -6,16 +6,17 @@ support-files = failing_tests_android_nvidia.txt failing_tests_android_x86.txt failing_tests_linux.txt failing_tests_linux_mesa.txt failing_tests_linux_nvidia.txt failing_tests_mac.txt failing_tests_mac_mtnlion.txt failing_tests_windows.txt + failing_tests_windows_msbasicrender.txt skipped_tests_android.txt skipped_tests_android_x86.txt skipped_tests_linux_mesa.txt skipped_tests_win_vista.txt skipped_tests_winxp.txt [test_webgl_conformance_test_suite.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' # bug 865443- separate suite - the non_conf* tests pass except for one on armv6 tests
--- a/dom/canvas/test/webgl-conformance/test_webgl_conformance_test_suite.html +++ b/dom/canvas/test/webgl-conformance/test_webgl_conformance_test_suite.html @@ -77,16 +77,17 @@ function start() { var OS_WINDOWS = 'windows'; var OS_MAC = 'mac'; var OS_LINUX = 'linux'; var OS_ANDROID = 'android'; var GLDRIVER_MESA = 'mesa'; var GLDRIVER_NVIDIA = 'nvidia'; var GLDRIVER_X86EMULATOR = 'android x86 emulator'; + var GLDRIVER_MSBASICRENDER = 'Microsoft Basic Render Driver'; var kOS = null; var kOSVersion = null; var kGLDriver = null; if (navigator.platform.indexOf('Win') == 0) { kOS = OS_WINDOWS; @@ -112,18 +113,20 @@ function start() { info('GL renderer: ' + glRenderer); if (glRenderer.contains('Android Emulator')) { kGLDriver = GLDRIVER_X86EMULATOR; } else if (glRenderer.contains('llvmpipe')) { kGLDriver = GLDRIVER_MESA; } else if (glVendor.contains('NVIDIA')) { kGLDriver = GLDRIVER_NVIDIA; + } else if (glRenderer.contains(GLDRIVER_MSBASICRENDER)) { + kGLDriver = GLDRIVER_MSBASICRENDER; } - + if (kOS) { info('OS detected as: ' + kOS); info(' Version: ' + kOSVersion); } else { info('OS not detected.'); info(' `platform`: ' + navigator.platform); info(' `appVersion`: ' + navigator.appVersion); info(' `userAgent`: ' + navigator.userAgent); @@ -484,18 +487,22 @@ function start() { // Windows uses the ANGLE library for rendering. Until everything is perfect, this means a different set of // failing tests. It's easier to do a platform check for Windows than for ANGLE itself. // Moreover, we currently also have different tests failing on Mac and on Linux, // presumably due to differences in the drivers. var failingTestsFilename = null; var skippedTestsFilename = null; switch (kOS) { case OS_WINDOWS: { - failingTestsFilename = 'failing_tests_windows.txt'; - + if (kGLDriver == GLDRIVER_MSBASICRENDER) { + failingTestsFilename = 'failing_tests_windows_msbasicrender.txt'; + } else { + failingTestsFilename = 'failing_tests_windows.txt'; + } + if (kOSVersion >= 6.0) // 6.0 is Vista skippedTestsFilename = 'skipped_tests_win_vista.txt' else // XP skippedTestsFilename = 'skipped_tests_winxp.txt'; break; } case OS_MAC: {
--- a/dom/events/test/test_bug603008.html +++ b/dom/events/test/test_bug603008.html @@ -422,17 +422,17 @@ function testPreventDefault() { { name: "touchend", prevent: true }], [{ name: "touchstart", prevent: false }, { name: "touchmove", prevent: false }, { name: "touchmove", prevent: false, doPrevent: true }, { name: "touchend", prevent: false }], [{ name: "touchstart", prevent: false }, { name: "touchmove", prevent: false }, { name: "touchmove", prevent: false }, - { name: "touchend", prevent: false, doPrevent: true }] + { name: "touchend", prevent: true, doPrevent: true }] ]; var dotest = function(aTest) { if (aTest.doPrevent) { target.addEventListener(aTest.name, preventFunction, false); } if (aTest.name == "touchmove") {
--- a/dom/interfaces/base/nsIDOMWindow.idl +++ b/dom/interfaces/base/nsIDOMWindow.idl @@ -18,17 +18,17 @@ interface nsIVariant; * The nsIDOMWindow interface is the primary interface for a DOM * window object. It represents a single window object that may * contain child windows if the document in the window contains a * HTML frameset document or if the document contains iframe elements. * * @see <http://www.whatwg.org/html/#window> */ -[scriptable, uuid(c3ff0328-6c47-4e64-a22f-ac221959e258)] +[scriptable, uuid(ed7cc4e4-cf5b-42af-9c2e-8df074a01470)] interface nsIDOMWindow : nsISupports { // the current browsing context readonly attribute nsIDOMWindow window; /* [replaceable] self */ readonly attribute nsIDOMWindow self; @@ -421,17 +421,19 @@ interface nsIDOMWindow : nsISupports * nsISupports (nsISupportsPrimitives) types are converted to native * JS types when possible. */ [noscript] nsIDOMWindow openDialog(in DOMString url, in DOMString name, in DOMString options, in nsISupports aExtraArgument); // XXX Should this be in nsIDOMChromeWindow? - void updateCommands(in DOMString action); + void updateCommands(in DOMString action, + [optional] in nsISelection sel, + [optional] in short reason); /* Find in page. * @param str: the search pattern * @param caseSensitive: is the search caseSensitive * @param backwards: should we search backwards * @param wrapAround: should we wrap the search * @param wholeWord: should we search only for whole words * @param searchInFrames: should we search through all frames
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -725,16 +725,18 @@ TabChild::TabChild(nsIContentChild* aMan , mAppPackageFileDescriptorRecved(false) , mLastBackgroundColor(NS_RGB(255, 255, 255)) , mDidFakeShow(false) , mNotified(false) , mTriedBrowserInit(false) , mOrientation(eScreenOrientation_PortraitPrimary) , mUpdateHitRegion(false) , mPendingTouchPreventedResponse(false) + , mTouchEndCancelled(false) + , mEndTouchIsClick(false) , mIgnoreKeyPressEvent(false) , mActiveElementManager(new ActiveElementManager()) , mHasValidInnerSize(false) , mUniqueId(0) { if (!sActiveDurationMsSet) { Preferences::AddIntVarCache(&sActiveDurationMs, "ui.touch_activation.duration_ms", @@ -1811,16 +1813,20 @@ TabChild::RecvHandleDoubleTap(const CSSP bool TabChild::RecvHandleSingleTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid) { if (!mGlobal || !mTabChildGlobal) { return true; } + if (mTouchEndCancelled) { + return true; + } + LayoutDevicePoint currentPoint = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid) * mWidget->GetDefaultScale();; MessageLoop::current()->PostDelayedTask( FROM_HERE, NewRunnableMethod(this, &TabChild::FireSingleTapEvent, currentPoint), sActiveDurationMs); return true; } @@ -1903,17 +1909,17 @@ TabChild::RecvNotifyAPZStateChange(const } case APZStateChange::StartPanning: { mActiveElementManager->HandlePanStart(); break; } case APZStateChange::EndTouch: { - mActiveElementManager->HandleTouchEnd(aArg); + mEndTouchIsClick = aArg; break; } default: // APZStateChange has a 'sentinel' value, and the compiler complains // if an enumerator is not handled and there is no 'default' case. break; } return true; @@ -2122,34 +2128,42 @@ TabChild::RecvRealTouchEvent(const Widge if (aEvent.message == NS_TOUCH_START && localEvent.touches.Length() > 0) { mActiveElementManager->SetTargetElement(localEvent.touches[0]->GetTarget()); } bool isTouchPrevented = nsIPresShell::gPreventMouseEvents || localEvent.mFlags.mMultipleActionsPrevented; switch (aEvent.message) { case NS_TOUCH_START: { + mTouchEndCancelled = false; if (mPendingTouchPreventedResponse) { // We can enter here if we get two TOUCH_STARTs in a row and didn't // respond to the first one. Respond to it now. SendContentReceivedTouch(mPendingTouchPreventedGuid, false); mPendingTouchPreventedResponse = false; } if (isTouchPrevented) { SendContentReceivedTouch(aGuid, isTouchPrevented); } else { mPendingTouchPreventedResponse = true; mPendingTouchPreventedGuid = aGuid; } break; } - case NS_TOUCH_MOVE: case NS_TOUCH_END: - case NS_TOUCH_CANCEL: { + if (isTouchPrevented) { + mTouchEndCancelled = true; + mEndTouchIsClick = false; + } + // fall through + case NS_TOUCH_CANCEL: + mActiveElementManager->HandleTouchEnd(mEndTouchIsClick); + // fall through + case NS_TOUCH_MOVE: { SendPendingTouchPreventedResponse(isTouchPrevented, aGuid); break; } default: NS_WARNING("Unknown touch event type"); }
--- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -589,16 +589,19 @@ private: bool mNotified; bool mTriedBrowserInit; ScreenOrientation mOrientation; bool mUpdateHitRegion; bool mPendingTouchPreventedResponse; ScrollableLayerGuid mPendingTouchPreventedGuid; void FireSingleTapEvent(LayoutDevicePoint aPoint); + bool mTouchEndCancelled; + bool mEndTouchIsClick; + bool mIgnoreKeyPressEvent; nsRefPtr<ActiveElementManager> mActiveElementManager; bool mHasValidInnerSize; uint64_t mUniqueId; DISALLOW_EVIL_CONSTRUCTORS(TabChild); };
--- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -103,32 +103,22 @@ GlobalPCList.prototype = { let cleanupWinId = function(list, winID) { if (list.hasOwnProperty(winID)) { list[winID].forEach(cleanupPcRef); delete list[winID]; } }; - let hasPluginId = function(list, winID, pluginID, name, crashReport) { + let broadcastPluginCrash = function(list, winID, pluginID, name, crashReportID) { if (list.hasOwnProperty(winID)) { list[winID].forEach(function(pcref) { let pc = pcref.get(); if (pc) { - if (pc._pc.pluginCrash(pluginID)) { - // Notify DOM window of the crash - let event = new CustomEvent("PluginCrashed", - { bubbles: false, cancelable: false, - detail: { - pluginName: name, - pluginDumpId: crashReport, - submittedCrashReport: false } - }); - pc._win.dispatchEvent(event); - } + pc._pc.pluginCrash(pluginID, name, crashReportID); } }); } }; if (topic == "inner-window-destroyed") { let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; cleanupWinId(this._list, winID); @@ -162,18 +152,18 @@ GlobalPCList.prototype = { let sep = data.indexOf(' '); let pluginId = data.slice(0, sep); let rest = data.slice(sep+1); // This presumes no spaces in the name! sep = rest.indexOf(' '); let name = rest.slice(0, sep); let crashId = rest.slice(sep+1); for (let winId in this._list) { - hasPluginId(this._list, winId, pluginId, name, crashId); - } + broadcastPluginCrash(this._list, winId, pluginId, name, crashId); + } } }, _registerPeerConnectionLifecycleCallback: function(winID, cb) { this._lifecycleobservers[winID] = cb; }, }; let _globalPCList = new GlobalPCList();
--- a/dom/network/moz.build +++ b/dom/network/moz.build @@ -6,12 +6,12 @@ DIRS += ['interfaces', 'src'] XPCSHELL_TESTS_MANIFESTS += [ 'tests/unit/xpcshell.ini', 'tests/unit_ipc/xpcshell.ini', ] -if CONFIG['tests/MOZ_B2G_RIL']: +if CONFIG['MOZ_B2G_RIL']: XPCSHELL_TESTS_MANIFESTS += ['tests/unit_stats/xpcshell.ini'] MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
--- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -1478,18 +1478,18 @@ bool nsIDocument *doc = GetDocumentFromNPP(npp); NS_ENSURE_TRUE(doc, false); nsGlobalWindow* win = static_cast<nsGlobalWindow*>(doc->GetInnerWindow()); if (NS_WARN_IF(!win || !win->FastGetGlobalJSObject())) { return false; } - AutoSafeJSContext cx; - JSAutoCompartment ac(cx, win->FastGetGlobalJSObject()); + dom::AutoEntryScript aes(win); + JSContext* cx = aes.cx(); JS::Rooted<JSObject*> obj(cx, nsNPObjWrapper::GetNewOrUsed(npp, cx, npobj)); if (!obj) { return false; } obj = JS_ObjectToInnerObject(cx, obj);
--- a/dom/plugins/ipc/PluginScriptableObjectChild.cpp +++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp @@ -430,35 +430,40 @@ PluginScriptableObjectChild::~PluginScri } else { NS_ASSERTION(mType == LocalObject, "Wrong type!"); PluginModuleChild::sBrowserFuncs.releaseobject(mObject); } } } -void +bool PluginScriptableObjectChild::InitializeProxy() { AssertPluginThread(); NS_ASSERTION(mType == Proxy, "Bad type!"); NS_ASSERTION(!mObject, "Calling Initialize more than once!"); NS_ASSERTION(!mInvalidated, "Already invalidated?!"); mInstance = static_cast<PluginInstanceChild*>(Manager()); NS_ASSERTION(mInstance, "Null manager?!"); NPObject* object = CreateProxyObject(); - NS_ASSERTION(object, "Failed to create object!"); + if (!object) { + NS_ERROR("Failed to create object!"); + return false; + } if (!PluginModuleChild::current()->RegisterActorForNPObject(object, this)) { - NS_ERROR("Out of memory?"); + NS_ERROR("RegisterActorForNPObject failed"); + return false; } mObject = object; + return true; } void PluginScriptableObjectChild::InitializeLocal(NPObject* aObject) { AssertPluginThread(); NS_ASSERTION(mType == LocalObject, "Bad type!"); NS_ASSERTION(!mObject, "Calling Initialize more than once!"); @@ -468,17 +473,17 @@ PluginScriptableObjectChild::InitializeL NS_ASSERTION(mInstance, "Null manager?!"); PluginModuleChild::sBrowserFuncs.retainobject(aObject); NS_ASSERTION(!mProtectCount, "Should be zero!"); mProtectCount++; if (!PluginModuleChild::current()->RegisterActorForNPObject(aObject, this)) { - NS_ERROR("Out of memory?"); + NS_ERROR("RegisterActorForNPObject failed"); } mObject = aObject; } NPObject* PluginScriptableObjectChild::CreateProxyObject() { @@ -509,25 +514,21 @@ PluginScriptableObjectChild::CreateProxy bool PluginScriptableObjectChild::ResurrectProxyObject() { NS_ASSERTION(mInstance, "Must have an instance already!"); NS_ASSERTION(!mObject, "Should not have an object already!"); NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); - NPObject* object = CreateProxyObject(); - if (!object) { - NS_WARNING("Failed to create object!"); + if (!InitializeProxy()) { + NS_ERROR("Initialize failed!"); return false; } - InitializeProxy(); - NS_ASSERTION(mObject, "Initialize failed!"); - SendProtect(); return true; } NPObject* PluginScriptableObjectChild::GetObject(bool aCanResurrect) { if (!mObject && aCanResurrect && !ResurrectProxyObject()) {
--- a/dom/plugins/ipc/PluginScriptableObjectChild.h +++ b/dom/plugins/ipc/PluginScriptableObjectChild.h @@ -41,17 +41,17 @@ struct ChildNPObject : NPObject class PluginScriptableObjectChild : public PPluginScriptableObjectChild { friend class PluginInstanceChild; public: PluginScriptableObjectChild(ScriptableObjectType aType); virtual ~PluginScriptableObjectChild(); - void + bool InitializeProxy(); void InitializeLocal(NPObject* aObject); virtual bool AnswerInvalidate() MOZ_OVERRIDE;
--- a/dom/plugins/test/mochitest/test_npruntime_npnevaluate.html +++ b/dom/plugins/test/mochitest/test_npruntime_npnevaluate.html @@ -43,16 +43,17 @@ ["new Array(1, 2, 3, 4)", [1, 2, 3, 4]], ["document.getElementById('display')", document.getElementById("display")], ["encodeURI('a = b')", "a%20=%20b"], ["document.getElementById('testdiv').innerHTML = 'Hello world!'", "Hello world!"], ["function test2() { var x = {a: '1', b: '2'}; return x; } test2();", {a: '1', b: '2'}], + ["(function() { var ret; try { win = window.open(); win.document.writeln('wibble'); ret = 'no error' } catch(e) { ret = e.name; } win.close(); return ret; })()", "no error"], ]; var plugin = document.getElementById("plugin1"); // Test calling NPN_Evaluate from within plugin code. for (var test of tests) { var expected = test[1]; var result = plugin.npnEvaluateTest(test[0]);
deleted file mode 100644 --- a/dom/push/tests/moz.build +++ /dev/null @@ -1,4 +0,0 @@ -# vim: set filetype=python: -# 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/.
deleted file mode 100644 --- a/dom/wappush/interfaces/moz.build +++ /dev/null @@ -1,5 +0,0 @@ -# vim: set filetype=python: -# 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/. -
--- a/dom/wappush/moz.build +++ b/dom/wappush/moz.build @@ -1,9 +1,9 @@ # vim: set filetype=python: # 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/. -DIRS += ['interfaces', 'src'] +DIRS += ['src'] if CONFIG['ENABLE_TESTS']: XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
--- a/dom/webidl/PeerConnectionImpl.webidl +++ b/dom/webidl/PeerConnectionImpl.webidl @@ -55,18 +55,18 @@ interface PeerConnectionImpl { * into the SDP. */ [Throws] void addIceCandidate(DOMString candidate, DOMString mid, unsigned short level); /* Puts the SIPCC engine back to 'kIdle', shuts down threads, deletes state */ void close(); - /* Notify DOM window if this plugin crash is ours */ - boolean pluginCrash(unsigned long pluginId); + /* Notify DOM window if this plugin crash is ours. */ + boolean pluginCrash(unsigned long long pluginId, DOMString name, DOMString pluginDumpID); /* Attributes */ readonly attribute DOMString fingerprint; readonly attribute DOMString localDescription; readonly attribute DOMString remoteDescription; readonly attribute PCImplIceConnectionState iceConnectionState; readonly attribute PCImplIceGatheringState iceGatheringState;
new file mode 100644 --- /dev/null +++ b/dom/webidl/SelectionChangeEvent.webidl @@ -0,0 +1,19 @@ +/* -*- 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 SelectionChangeEventInit : EventInit { + DOMString selectedText = ""; + DOMRectReadOnly? boundingClientRect = null; + short reason = 0; +}; + +[Constructor(DOMString type, optional SelectionChangeEventInit eventInit), + ChromeOnly] +interface SelectionChangeEvent : Event { + readonly attribute DOMString selectedText; + readonly attribute DOMRectReadOnly? boundingClientRect; + readonly attribute short reason; +};
--- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -298,17 +298,19 @@ partial interface Window { [Throws] attribute boolean fullScreen; [Throws, ChromeOnly] void back(); [Throws, ChromeOnly] void forward(); [Throws, ChromeOnly] void home(); // XXX Should this be in nsIDOMChromeWindow? - void updateCommands(DOMString action); + void updateCommands(DOMString action, + optional Selection? sel = null, + optional short reason = 0); /* Find in page. * @param str: the search pattern * @param caseSensitive: is the search caseSensitive * @param backwards: should we search backwards * @param wrapAround: should we wrap the search * @param wholeWord: should we search only for whole words * @param searchInFrames: should we search through all frames
--- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -664,16 +664,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [ 'PopStateEvent.webidl', 'PopupBlockedEvent.webidl', 'ProgressEvent.webidl', 'RecordErrorEvent.webidl', 'RTCDataChannelEvent.webidl', 'RTCPeerConnectionIceEvent.webidl', 'RTCPeerConnectionIdentityErrorEvent.webidl', 'RTCPeerConnectionIdentityEvent.webidl', + 'SelectionChangeEvent.webidl', 'SmartCardEvent.webidl', 'StyleRuleChangeEvent.webidl', 'StyleSheetApplicableStateChangeEvent.webidl', 'StyleSheetChangeEvent.webidl', 'TrackEvent.webidl', 'UserProximityEvent.webidl', 'USSDReceivedEvent.webidl', ]
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -4286,16 +4286,23 @@ WorkerPrivate::IsOnCurrentThread(bool* a void WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) { AssertIsOnWorkerThread(); MOZ_ASSERT(mChildWorkers.IsEmpty()); MOZ_ASSERT(mSyncLoopStack.IsEmpty()); ClearMainEventQueue(aRanOrNot); +#ifdef DEBUG + if (WorkerRan == aRanOrNot) { + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); + MOZ_ASSERT(!NS_HasPendingEvents(currentThread)); + } +#endif if (WorkerPrivate* parent = GetParent()) { nsRefPtr<WorkerFinishedRunnable> runnable = new WorkerFinishedRunnable(parent, this); if (!runnable->Dispatch(nullptr)) { NS_WARNING("Failed to dispatch runnable!"); } } @@ -4522,17 +4529,16 @@ WorkerPrivate::ClearMainEventQueue(Worke nsRefPtr<WorkerRunnable> runnable = mPreStartRunnables[index].forget(); static_cast<nsIRunnable*>(runnable.get())->Run(); } } else { nsIThread* currentThread = NS_GetCurrentThread(); MOZ_ASSERT(currentThread); NS_ProcessPendingEvents(currentThread); - MOZ_ASSERT(!NS_HasPendingEvents(currentThread)); } MOZ_ASSERT(mCancelAllPendingRunnables); mCancelAllPendingRunnables = false; } uint32_t WorkerPrivate::RemainingRunTimeMS() const
--- a/gfx/2d/Point.h +++ b/gfx/2d/Point.h @@ -62,17 +62,17 @@ struct PointTyped : public units { static_assert(IsPixel<units>::value, "'units' must be a coordinate system tag"); typedef BasePoint< Float, PointTyped<units> > Super; MOZ_CONSTEXPR PointTyped() : Super() {} MOZ_CONSTEXPR PointTyped(Float aX, Float aY) : Super(aX, aY) {} - MOZ_CONSTEXPR PointTyped(const IntPointTyped<units>& point) : Super(float(point.x), float(point.y)) {} + MOZ_CONSTEXPR MOZ_IMPLICIT PointTyped(const IntPointTyped<units>& point) : Super(float(point.x), float(point.y)) {} // XXX When all of the code is ported, the following functions to convert to and from // unknown types should be removed. static PointTyped<units> FromUnknownPoint(const PointTyped<UnknownUnits>& aPoint) { return PointTyped<units>(aPoint.x, aPoint.y); }
--- a/gfx/2d/Tools.h +++ b/gfx/2d/Tools.h @@ -98,17 +98,17 @@ struct AlignedArray typedef T value_type; AlignedArray() : mPtr(nullptr) , mStorage(nullptr) { } - MOZ_ALWAYS_INLINE AlignedArray(size_t aCount) + explicit MOZ_ALWAYS_INLINE AlignedArray(size_t aCount) : mStorage(nullptr) , mCount(0) { Realloc(aCount); } MOZ_ALWAYS_INLINE ~AlignedArray() {
--- a/gfx/ipc/GfxMessageUtils.h +++ b/gfx/ipc/GfxMessageUtils.h @@ -1051,43 +1051,47 @@ template <> struct ParamTraits<mozilla::gfx::FilterPrimitiveDescription> { typedef mozilla::gfx::FilterPrimitiveDescription paramType; static void Write(Message* aMsg, const paramType& aParam) { WriteParam(aMsg, aParam.Type()); WriteParam(aMsg, aParam.PrimitiveSubregion()); + WriteParam(aMsg, aParam.FilterSpaceBounds()); WriteParam(aMsg, aParam.IsTainted()); WriteParam(aMsg, aParam.OutputColorSpace()); WriteParam(aMsg, aParam.NumberOfInputs()); for (size_t i = 0; i < aParam.NumberOfInputs(); i++) { WriteParam(aMsg, aParam.InputPrimitiveIndex(i)); WriteParam(aMsg, aParam.InputColorSpace(i)); } WriteParam(aMsg, aParam.Attributes()); } static bool Read(const Message* aMsg, void** aIter, paramType* aResult) { mozilla::gfx::PrimitiveType type; mozilla::gfx::IntRect primitiveSubregion; + mozilla::gfx::IntRect filterSpaceBounds; bool isTainted = false; mozilla::gfx::ColorSpace outputColorSpace; size_t numberOfInputs = 0; if (!ReadParam(aMsg, aIter, &type) || !ReadParam(aMsg, aIter, &primitiveSubregion) || + !ReadParam(aMsg, aIter, &filterSpaceBounds) || !ReadParam(aMsg, aIter, &isTainted) || !ReadParam(aMsg, aIter, &outputColorSpace) || !ReadParam(aMsg, aIter, &numberOfInputs)) { return false; } aResult->SetType(type); aResult->SetPrimitiveSubregion(primitiveSubregion); + aResult->SetFilterSpaceBounds(filterSpaceBounds); aResult->SetIsTainted(isTainted); aResult->SetOutputColorSpace(outputColorSpace); for (size_t i = 0; i < numberOfInputs; i++) { int32_t inputPrimitiveIndex = 0; mozilla::gfx::ColorSpace inputColorSpace; if (!ReadParam(aMsg, aIter, &inputPrimitiveIndex) || !ReadParam(aMsg, aIter, &inputColorSpace)) { @@ -1103,24 +1107,22 @@ struct ParamTraits<mozilla::gfx::FilterP template <> struct ParamTraits<mozilla::gfx::FilterDescription> { typedef mozilla::gfx::FilterDescription paramType; static void Write(Message* aMsg, const paramType& aParam) { - WriteParam(aMsg, aParam.mFilterSpaceBounds); WriteParam(aMsg, aParam.mPrimitives); } static bool Read(const Message* aMsg, void** aIter, paramType* aResult) { - return (ReadParam(aMsg, aIter, &aResult->mFilterSpaceBounds) && - ReadParam(aMsg, aIter, &aResult->mPrimitives)); + return (ReadParam(aMsg, aIter, &aResult->mPrimitives)); } }; typedef mozilla::layers::GeckoContentController::APZStateChange APZStateChange; template <> struct ParamTraits<APZStateChange> : public ContiguousTypedEnumSerializer<
--- a/gfx/layers/CompositorTypes.h +++ b/gfx/layers/CompositorTypes.h @@ -79,19 +79,16 @@ MOZ_BEGIN_ENUM_CLASS(TextureFlags, uint3 // The contents of the texture must be uploaded or copied immediately // during the transaction, because the producer may want to write // to it again. IMMEDIATE_UPLOAD = 1 << 15, // The texture is going to be used as part of a double // buffered pair, and so we can guarantee that the producer/consumer // won't be racing to access its contents. DOUBLE_BUFFERED = 1 << 16, - // We've previously tried a texture and it didn't work for some reason. If there - // is a fallback available, try that. - ALLOC_FALLBACK = 1 << 17, // Data in this texture has not been alpha-premultiplied. NON_PREMULTIPLIED = 1 << 18, // OR union of all valid bits ALL_BITS = (1 << 19) - 1, // the default flags DEFAULT = FRONT MOZ_END_ENUM_CLASS(TextureFlags)
--- a/gfx/layers/client/ContentClient.cpp +++ b/gfx/layers/client/ContentClient.cpp @@ -228,25 +228,16 @@ void ContentClientRemoteBuffer::CreateBackBuffer(const nsIntRect& aBufferRect) { // gfx::BackendType::NONE means fallback to the content backend mTextureClient = CreateTextureClientForDrawing( mSurfaceFormat, mSize, gfx::BackendType::NONE, mTextureInfo.mTextureFlags, TextureAllocationFlags::ALLOC_CLEAR_BUFFER ); - if (!mTextureClient) { - // try with ALLOC_FALLBACK - mTextureClient = CreateTextureClientForDrawing( - mSurfaceFormat, mSize, gfx::BackendType::NONE, - mTextureInfo.mTextureFlags | TextureFlags::ALLOC_FALLBACK, - TextureAllocationFlags::ALLOC_CLEAR_BUFFER - ); - } - if (!mTextureClient || !AddTextureClient(mTextureClient)) { AbortTextureClientCreation(); return; } if (mTextureInfo.mTextureFlags & TextureFlags::COMPONENT_ALPHA) { mTextureClientOnWhite = mTextureClient->CreateSimilar( mTextureInfo.mTextureFlags,
--- a/gfx/layers/client/TextureClient.cpp +++ b/gfx/layers/client/TextureClient.cpp @@ -246,125 +246,112 @@ CreateBufferTextureClient(ISurfaceAlloca return result.forget(); } RefPtr<BufferTextureClient> result = new ShmemTextureClient(aAllocator, aFormat, aMoz2DBackend, aTextureFlags); return result.forget(); } -static +// static TemporaryRef<TextureClient> -CreateTextureClientForDrawing(ISurfaceAllocator* aAllocator, - SurfaceFormat aFormat, - TextureFlags aTextureFlags, - gfx::BackendType aMoz2DBackend, - const gfx::IntSize& aSizeHint) +TextureClient::CreateForDrawing(ISurfaceAllocator* aAllocator, + gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + gfx::BackendType aMoz2DBackend, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { if (aMoz2DBackend == gfx::BackendType::NONE) { aMoz2DBackend = gfxPlatform::GetPlatform()->GetContentBackend(); } - RefPtr<TextureClient> result; + RefPtr<TextureClient> texture; #if defined(MOZ_WIDGET_GONK) || defined(XP_WIN) int32_t maxTextureSize = aAllocator->GetMaxTextureSize(); #endif #ifdef XP_WIN LayersBackend parentBackend = aAllocator->GetCompositorBackendType(); if (parentBackend == LayersBackend::LAYERS_D3D11 && (aMoz2DBackend == gfx::BackendType::DIRECT2D || aMoz2DBackend == gfx::BackendType::DIRECT2D1_1) && gfxWindowsPlatform::GetPlatform()->GetD2DDevice() && - aSizeHint.width <= maxTextureSize && - aSizeHint.height <= maxTextureSize && - !(aTextureFlags & TextureFlags::ALLOC_FALLBACK)) { - result = new TextureClientD3D11(aFormat, aTextureFlags); + aSize.width <= maxTextureSize && + aSize.height <= maxTextureSize) { + texture = new TextureClientD3D11(aFormat, aTextureFlags); } if (parentBackend == LayersBackend::LAYERS_D3D9 && aMoz2DBackend == gfx::BackendType::CAIRO && aAllocator->IsSameProcess() && - aSizeHint.width <= maxTextureSize && - aSizeHint.height <= maxTextureSize && - !(aTextureFlags & TextureFlags::ALLOC_FALLBACK)) { + aSize.width <= maxTextureSize && + aSize.height <= maxTextureSize) { if (gfxWindowsPlatform::GetPlatform()->GetD3D9Device()) { - result = new CairoTextureClientD3D9(aFormat, aTextureFlags); + texture = new CairoTextureClientD3D9(aFormat, aTextureFlags); } } - if (!result && aFormat == SurfaceFormat::B8G8R8X8 && + if (!texture && aFormat == SurfaceFormat::B8G8R8X8 && aAllocator->IsSameProcess()) { - result = new DIBTextureClient(aFormat, aTextureFlags); + texture = new DIBTextureClient(aFormat, aTextureFlags); } #endif #ifdef MOZ_X11 LayersBackend parentBackend = aAllocator->GetCompositorBackendType(); gfxSurfaceType type = gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType(); if (parentBackend == LayersBackend::LAYERS_BASIC && aMoz2DBackend == gfx::BackendType::CAIRO && - type == gfxSurfaceType::Xlib && - !(aTextureFlags & TextureFlags::ALLOC_FALLBACK)) + type == gfxSurfaceType::Xlib) { - result = new TextureClientX11(aAllocator, aFormat, aTextureFlags); + texture = new TextureClientX11(aAllocator, aFormat, aTextureFlags); } #ifdef GL_PROVIDER_GLX if (parentBackend == LayersBackend::LAYERS_OPENGL && type == gfxSurfaceType::Xlib && - !(aTextureFlags & TextureFlags::ALLOC_FALLBACK) && aFormat != SurfaceFormat::A8 && gl::sGLXLibrary.UseTextureFromPixmap()) { - result = new TextureClientX11(aAllocator, aFormat, aTextureFlags); + texture = new TextureClientX11(aAllocator, aFormat, aTextureFlags); } #endif #endif #ifdef MOZ_WIDGET_GONK - if (!DisableGralloc(aFormat, aSizeHint)) { + if (!DisableGralloc(aFormat, aSize)) { // Don't allow Gralloc texture clients to exceed the maximum texture size. // BufferTextureClients have code to handle tiling the surface client-side. - if (aSizeHint.width <= maxTextureSize && aSizeHint.height <= maxTextureSize) { - result = new GrallocTextureClientOGL(aAllocator, aFormat, aMoz2DBackend, + if (aSize.width <= maxTextureSize && aSize.height <= maxTextureSize) { + texture = new GrallocTextureClientOGL(aAllocator, aFormat, aMoz2DBackend, aTextureFlags); } } #endif - // Can't do any better than a buffer texture client. - if (!result) { - result = CreateBufferTextureClient(aAllocator, aFormat, aTextureFlags, aMoz2DBackend); + MOZ_ASSERT(!texture || texture->CanExposeDrawTarget(), "texture cannot expose a DrawTarget?"); + + if (texture && texture->AllocateForSurface(aSize, aAllocFlags)) { + return texture; } - MOZ_ASSERT(!result || result->CanExposeDrawTarget(), "texture cannot expose a DrawTarget?"); - return result; -} + if (texture) { + NS_WARNING("Failed to allocate a TextureClient, falling back to BufferTextureClient."); + } -// static -TemporaryRef<TextureClient> -TextureClient::CreateForDrawing(ISurfaceAllocator* aAllocator, - gfx::SurfaceFormat aFormat, - gfx::IntSize aSize, - gfx::BackendType aMoz2DBackend, - TextureFlags aTextureFlags, - TextureAllocationFlags aAllocFlags) -{ - RefPtr<TextureClient> texture = - CreateTextureClientForDrawing(aAllocator, aFormat, - aTextureFlags, aMoz2DBackend, - aSize); - if (texture) { - if (!texture->AllocateForSurface(aSize, aAllocFlags)) { - return nullptr; - } + // Can't do any better than a buffer texture client. + texture = CreateBufferTextureClient(aAllocator, aFormat, aTextureFlags, aMoz2DBackend); + + if (!texture->AllocateForSurface(aSize, aAllocFlags)) { + return nullptr; } + return texture; } // static TemporaryRef<BufferTextureClient> TextureClient::CreateForRawBufferAccess(ISurfaceAllocator* aAllocator, gfx::SurfaceFormat aFormat, gfx::IntSize aSize,
--- a/gfx/layers/d3d9/TextureD3D9.cpp +++ b/gfx/layers/d3d9/TextureD3D9.cpp @@ -719,16 +719,19 @@ SharedTextureClientD3D9::SharedTextureCl , mHandle(0) , mIsLocked(false) { MOZ_COUNT_CTOR(SharedTextureClientD3D9); } SharedTextureClientD3D9::~SharedTextureClientD3D9() { + if (mTexture && mActor) { + KeepUntilFullDeallocation(new TKeepAlive<IDirect3DTexture9>(mTexture)); + } MOZ_COUNT_DTOR(SharedTextureClientD3D9); } bool SharedTextureClientD3D9::Lock(OpenMode) { MOZ_ASSERT(!mIsLocked); if (!IsValid()) { @@ -913,56 +916,73 @@ DXGITextureHostD3D9::DXGITextureHostD3D9 const SurfaceDescriptorD3D10& aDescriptor) : TextureHost(aFlags) , mHandle(aDescriptor.handle()) , mFormat(aDescriptor.format()) , mSize(aDescriptor.size()) , mIsLocked(false) { MOZ_ASSERT(mHandle); + OpenSharedHandle(); +} + +IDirect3DDevice9* +DXGITextureHostD3D9::GetDevice() +{ + return mCompositor ? mCompositor->device() : nullptr; +} + +void +DXGITextureHostD3D9::OpenSharedHandle() +{ + MOZ_ASSERT(!mTextureSource); + + if (!GetDevice()) { + return; + } + + nsRefPtr<IDirect3DTexture9> texture; + HRESULT hr = GetDevice()->CreateTexture(mSize.width, mSize.height, 1, + D3DUSAGE_RENDERTARGET, + SurfaceFormatToD3D9Format(mFormat), + D3DPOOL_DEFAULT, + getter_AddRefs(texture), + (HANDLE*)&mHandle); + if (FAILED(hr)) { + NS_WARNING("Failed to open shared texture"); + return; + } + + mTextureSource = new DataTextureSourceD3D9(mFormat, mSize, mCompositor, texture); + + return; } NewTextureSource* DXGITextureHostD3D9::GetTextureSources() { MOZ_ASSERT(mIsLocked); MOZ_ASSERT(mTextureSource); return mTextureSource; } bool DXGITextureHostD3D9::Lock() { MOZ_ASSERT(!mIsLocked); - DeviceManagerD3D9* deviceManager = gfxWindowsPlatform::GetPlatform()->GetD3D9DeviceManager(); - if (!deviceManager) { - NS_WARNING("trying to lock a TextureHost without a D3D device"); + + if (!GetDevice()) { return false; } if (!mTextureSource) { - nsRefPtr<IDirect3DTexture9> tex; - HRESULT hr = deviceManager->device()->CreateTexture(mSize.width, - mSize.height, - 1, - D3DUSAGE_RENDERTARGET, - SurfaceFormatToD3D9Format(mFormat), - D3DPOOL_DEFAULT, - getter_AddRefs(tex), - (HANDLE*)&mHandle); - if (FAILED(hr)) { - NS_WARNING("Failed to open shared texture"); - return false; - } - - mTextureSource = new DataTextureSourceD3D9(mFormat, mSize, mCompositor, tex); + OpenSharedHandle(); } - - mIsLocked = true; - return true; + mIsLocked = !!mTextureSource; + return mIsLocked; } void DXGITextureHostD3D9::Unlock() { MOZ_ASSERT(mIsLocked); mIsLocked = false; }
--- a/gfx/layers/d3d9/TextureD3D9.h +++ b/gfx/layers/d3d9/TextureD3D9.h @@ -348,16 +348,19 @@ public: virtual void Unlock() MOZ_OVERRIDE; virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() MOZ_OVERRIDE { return nullptr; // TODO: cf bug 872568 } protected: + void OpenSharedHandle(); + IDirect3DDevice9* GetDevice(); + RefPtr<DataTextureSourceD3D9> mTextureSource; RefPtr<CompositorD3D9> mCompositor; WindowsHandle mHandle; gfx::SurfaceFormat mFormat; gfx::IntSize mSize; bool mIsLocked; };
--- a/gfx/src/FilterSupport.cpp +++ b/gfx/src/FilterSupport.cpp @@ -1049,17 +1049,16 @@ FilterNodeGraphFromDescription(DrawTarge const IntRect& aSourceGraphicRect, SourceSurface* aFillPaint, const IntRect& aFillPaintRect, SourceSurface* aStrokePaint, const IntRect& aStrokePaintRect, nsTArray<RefPtr<SourceSurface>>& aAdditionalImages) { const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives; - const IntRect& filterSpaceBounds = aFilter.mFilterSpaceBounds; Rect resultNeededRect(aResultNeededRect); resultNeededRect.RoundOut(); RefPtr<FilterCachedColorModels> sourceFilters[4]; nsTArray<RefPtr<FilterCachedColorModels> > primitiveFilters; for (size_t i = 0; i < primitives.Length(); ++i) { @@ -1068,20 +1067,19 @@ FilterNodeGraphFromDescription(DrawTarge nsTArray<RefPtr<FilterNode> > inputFilterNodes; nsTArray<IntRect> inputSourceRects; nsTArray<AlphaModel> inputAlphaModels; for (size_t j = 0; j < descr.NumberOfInputs(); j++) { int32_t inputIndex = descr.InputPrimitiveIndex(j); if (inputIndex < 0) { - inputSourceRects.AppendElement(filterSpaceBounds); + inputSourceRects.AppendElement(descr.FilterSpaceBounds()); } else { - inputSourceRects.AppendElement(filterSpaceBounds.Intersect( - primitives[inputIndex].PrimitiveSubregion())); + inputSourceRects.AppendElement(primitives[inputIndex].PrimitiveSubregion()); } RefPtr<FilterCachedColorModels> inputFilter; if (inputIndex >= 0) { MOZ_ASSERT(inputIndex < (int64_t)primitiveFilters.Length(), "out-of-bounds input index!"); inputFilter = primitiveFilters[inputIndex]; MOZ_ASSERT(inputFilter, "Referred to input filter that comes after the current one?"); } else { @@ -1123,18 +1121,18 @@ FilterNodeGraphFromDescription(DrawTarge inputFilterNodes.AppendElement(inputFilter->ForColorModel(inputColorModel)); } RefPtr<FilterNode> primitiveFilterNode = FilterNodeFromPrimitiveDescription(descr, aDT, inputFilterNodes, inputSourceRects, aAdditionalImages); if (primitiveFilterNode) { - IntRect cropRect = filterSpaceBounds.Intersect(descr.PrimitiveSubregion()); - primitiveFilterNode = FilterWrappers::Crop(aDT, primitiveFilterNode, cropRect); + primitiveFilterNode = + FilterWrappers::Crop(aDT, primitiveFilterNode, descr.PrimitiveSubregion()); } ColorModel outputColorModel(descr.OutputColorSpace(), OutputAlphaModelForPrimitive(descr, inputAlphaModels)); RefPtr<FilterCachedColorModels> primitiveFilter = new FilterCachedColorModels(aDT, primitiveFilterNode, outputColorModel); primitiveFilters.AppendElement(primitiveFilter); @@ -1293,19 +1291,17 @@ FilterSupport::ComputeResultChangeRegion nsIntRegion inputChangeRegion = ElementForIndex(inputIndex, resultChangeRegions, aSourceGraphicChange, aFillPaintChange, aStrokePaintChange); inputChangeRegions.AppendElement(inputChangeRegion); } nsIntRegion changeRegion = ResultChangeRegionForPrimitive(descr, inputChangeRegions); - IntRect cropRect = - descr.PrimitiveSubregion().Intersect(aFilter.mFilterSpaceBounds); - changeRegion.And(changeRegion, ThebesIntRect(cropRect)); + changeRegion.And(changeRegion, ThebesIntRect(descr.PrimitiveSubregion())); resultChangeRegions.AppendElement(changeRegion); } return resultChangeRegions[resultChangeRegions.Length() - 1]; } static nsIntRegion PostFilterExtentsForPrimitive(const FilterPrimitiveDescription& aDescription, @@ -1380,35 +1376,33 @@ PostFilterExtentsForPrimitive(const Filt } } /* static */ nsIntRegion FilterSupport::ComputePostFilterExtents(const FilterDescription& aFilter, const nsIntRegion& aSourceGraphicExtents) { const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives; - nsIntRegion filterSpace = ThebesIntRect(aFilter.mFilterSpaceBounds); nsTArray<nsIntRegion> postFilterExtents; for (int32_t i = 0; i < int32_t(primitives.Length()); ++i) { const FilterPrimitiveDescription& descr = primitives[i]; + nsIntRegion filterSpace = ThebesIntRect(descr.FilterSpaceBounds()); nsTArray<nsIntRegion> inputExtents; for (size_t j = 0; j < descr.NumberOfInputs(); j++) { int32_t inputIndex = descr.InputPrimitiveIndex(j); MOZ_ASSERT(inputIndex < i, "bad input index"); nsIntRegion inputExtent = ElementForIndex(inputIndex, postFilterExtents, aSourceGraphicExtents, filterSpace, filterSpace); inputExtents.AppendElement(inputExtent); } nsIntRegion extent = PostFilterExtentsForPrimitive(descr, inputExtents); - IntRect cropRect = - descr.PrimitiveSubregion().Intersect(aFilter.mFilterSpaceBounds); - extent.And(extent, ThebesIntRect(cropRect)); + extent.And(extent, ThebesIntRect(descr.PrimitiveSubregion())); postFilterExtents.AppendElement(extent); } return postFilterExtents[postFilterExtents.Length() - 1]; } static nsIntRegion SourceNeededRegionForPrimitive(const FilterPrimitiveDescription& aDescription, @@ -1534,18 +1528,22 @@ FilterSupport::ComputeSourceNeededRegion &ElementForIndex(inputIndex, primitiveNeededRegions, aSourceGraphicNeededRegion, aFillPaintNeededRegion, aStrokePaintNeededRegion)); inputNeededRegion->Or(*inputNeededRegion, SourceNeededRegionForPrimitive(descr, neededRegion, j)); } } - aSourceGraphicNeededRegion.And(aSourceGraphicNeededRegion, - ThebesIntRect(aFilter.mFilterSpaceBounds)); + // Clip original SourceGraphic to first filter region. + if (primitives.Length() > 0) { + const FilterPrimitiveDescription& firstDescr = primitives[0]; + aSourceGraphicNeededRegion.And(aSourceGraphicNeededRegion, + ThebesIntRect(firstDescr.FilterSpaceBounds())); + } } // FilterPrimitiveDescription FilterPrimitiveDescription::FilterPrimitiveDescription() : mType(PrimitiveType::Empty) , mOutputColorSpace(ColorSpace::SRGB) , mIsTainted(false) @@ -1559,56 +1557,58 @@ FilterPrimitiveDescription::FilterPrimit { } FilterPrimitiveDescription::FilterPrimitiveDescription(const FilterPrimitiveDescription& aOther) : mType(aOther.mType) , mAttributes(aOther.mAttributes) , mInputPrimitives(aOther.mInputPrimitives) , mFilterPrimitiveSubregion(aOther.mFilterPrimitiveSubregion) + , mFilterSpaceBounds(aOther.mFilterSpaceBounds) , mInputColorSpaces(aOther.mInputColorSpaces) , mOutputColorSpace(aOther.mOutputColorSpace) , mIsTainted(aOther.mIsTainted) { } FilterPrimitiveDescription& FilterPrimitiveDescription::operator=(const FilterPrimitiveDescription& aOther) { if (this != &aOther) { mType = aOther.mType; mAttributes = aOther.mAttributes; mInputPrimitives = aOther.mInputPrimitives; mFilterPrimitiveSubregion = aOther.mFilterPrimitiveSubregion; + mFilterSpaceBounds = aOther.mFilterSpaceBounds; mInputColorSpaces = aOther.mInputColorSpaces; mOutputColorSpace = aOther.mOutputColorSpace; mIsTainted = aOther.mIsTainted; } return *this; } bool FilterPrimitiveDescription::operator==(const FilterPrimitiveDescription& aOther) const { return mType == aOther.mType && mFilterPrimitiveSubregion.IsEqualInterior(aOther.mFilterPrimitiveSubregion) && + mFilterSpaceBounds.IsEqualInterior(aOther.mFilterSpaceBounds) && mOutputColorSpace == aOther.mOutputColorSpace && mIsTainted == aOther.mIsTainted && mInputPrimitives == aOther.mInputPrimitives && mInputColorSpaces == aOther.mInputColorSpaces && mAttributes == aOther.mAttributes; } // FilterDescription bool FilterDescription::operator==(const FilterDescription& aOther) const { - return mFilterSpaceBounds.IsEqualInterior(aOther.mFilterSpaceBounds) && - mPrimitives == aOther.mPrimitives; + return mPrimitives == aOther.mPrimitives; } // AttributeMap // A class that wraps different types for easy storage in a hashtable. Only // used by AttributeMap. struct FilterAttribute { FilterAttribute(const FilterAttribute& aOther);
--- a/gfx/src/FilterSupport.h +++ b/gfx/src/FilterSupport.h @@ -295,16 +295,17 @@ public: FilterPrimitiveDescription& operator=(const FilterPrimitiveDescription& aOther); PrimitiveType Type() const { return mType; } void SetType(PrimitiveType aType) { mType = aType; } const AttributeMap& Attributes() const { return mAttributes; } AttributeMap& Attributes() { return mAttributes; } IntRect PrimitiveSubregion() const { return mFilterPrimitiveSubregion; } + IntRect FilterSpaceBounds() const { return mFilterSpaceBounds; } bool IsTainted() const { return mIsTainted; } size_t NumberOfInputs() const { return mInputPrimitives.Length(); } int32_t InputPrimitiveIndex(size_t aInputIndex) const { return aInputIndex < mInputPrimitives.Length() ? mInputPrimitives[aInputIndex] : 0; } @@ -317,16 +318,21 @@ public: ColorSpace OutputColorSpace() const { return mOutputColorSpace; } void SetPrimitiveSubregion(const IntRect& aRect) { mFilterPrimitiveSubregion = aRect; } + void SetFilterSpaceBounds(const IntRect& aRect) + { + mFilterSpaceBounds = aRect; + } + void SetIsTainted(bool aIsTainted) { mIsTainted = aIsTainted; } void SetInputPrimitive(size_t aInputIndex, int32_t aInputPrimitiveIndex) { mInputPrimitives.EnsureLengthAtLeast(aInputIndex + 1); @@ -350,42 +356,40 @@ public: return !(*this == aOther); } private: PrimitiveType mType; AttributeMap mAttributes; nsTArray<int32_t> mInputPrimitives; IntRect mFilterPrimitiveSubregion; + IntRect mFilterSpaceBounds; nsTArray<ColorSpace> mInputColorSpaces; ColorSpace mOutputColorSpace; bool mIsTainted; }; /** * A data structure that contains one or more FilterPrimitiveDescriptions. * Designed to be serializable via IPDL, so it must not contain complex * functionality. */ struct FilterDescription MOZ_FINAL { FilterDescription() {} - FilterDescription(const nsTArray<FilterPrimitiveDescription>& aPrimitives, - const IntRect& aFilterSpaceBounds) + FilterDescription(const nsTArray<FilterPrimitiveDescription>& aPrimitives) : mPrimitives(aPrimitives) - , mFilterSpaceBounds(aFilterSpaceBounds) {} bool operator==(const FilterDescription& aOther) const; bool operator!=(const FilterDescription& aOther) const { return !(*this == aOther); } nsTArray<FilterPrimitiveDescription> mPrimitives; - IntRect mFilterSpaceBounds; }; /** * The methods of this class are not on FilterDescription because * FilterDescription is designed as a simple value holder that can be used * on any thread. */ class FilterSupport {
--- a/gfx/thebes/gfxDrawable.cpp +++ b/gfx/thebes/gfxDrawable.cpp @@ -1,193 +1,75 @@ /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- * 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 "gfxDrawable.h" #include "gfxASurface.h" #include "gfxContext.h" -#include "gfxImageSurface.h" #include "gfxPlatform.h" #include "gfxColor.h" +#include "gfx2DGlue.h" #ifdef MOZ_X11 #include "cairo.h" #include "gfxXlibSurface.h" #endif using namespace mozilla; using namespace mozilla::gfx; -gfxSurfaceDrawable::gfxSurfaceDrawable(gfxASurface* aSurface, - const gfxIntSize aSize, - const gfxMatrix aTransform) - : gfxDrawable(aSize) - , mSurface(aSurface) - , mTransform(aTransform) -{ -} - -gfxSurfaceDrawable::gfxSurfaceDrawable(DrawTarget* aDrawTarget, - const gfxIntSize aSize, - const gfxMatrix aTransform) - : gfxDrawable(aSize) - , mDrawTarget(aDrawTarget) - , mTransform(aTransform) -{ -} - gfxSurfaceDrawable::gfxSurfaceDrawable(SourceSurface* aSurface, const gfxIntSize aSize, const gfxMatrix aTransform) : gfxDrawable(aSize) , mSourceSurface(aSurface) , mTransform(aTransform) { } -static gfxMatrix -DeviceToImageTransform(gfxContext* aContext, - const gfxMatrix& aUserSpaceToImageSpace) -{ - gfxFloat deviceX, deviceY; - nsRefPtr<gfxASurface> currentTarget = - aContext->CurrentSurface(&deviceX, &deviceY); - gfxMatrix currentMatrix = aContext->CurrentMatrix(); - gfxMatrix deviceToUser = currentMatrix; - if (!deviceToUser.Invert()) { - return gfxMatrix(0, 0, 0, 0, 0, 0); // singular - } - deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY)); - return deviceToUser * aUserSpaceToImageSpace; -} - -static void -PreparePatternForUntiledDrawing(gfxPattern* aPattern, - const gfxMatrix& aDeviceToImage, - gfxASurface *currentTarget, - const GraphicsFilter aDefaultFilter) -{ - if (!currentTarget) { - // This happens if we're dealing with an Azure target. - aPattern->SetExtend(gfxPattern::EXTEND_PAD); - aPattern->SetFilter(aDefaultFilter); - return; - } - - // In theory we can handle this using cairo's EXTEND_PAD, - // but implementation limitations mean we have to consult - // the surface type. - switch (currentTarget->GetType()) { - -#ifdef MOZ_X11 - case gfxSurfaceType::Xlib: - { - // See bugs 324698, 422179, and 468496. This is a workaround for - // XRender's RepeatPad not being implemented correctly on old X - // servers. - // - // In this situation, cairo avoids XRender and instead reads back - // to perform EXTEND_PAD with pixman. This is too slow so we - // avoid EXTEND_PAD and set the filter to CAIRO_FILTER_FAST --- - // otherwise, pixman's sampling will sample transparency for the - // outside edges and we'll get blurry edges. - // - // But don't do this for simple downscales because it's horrible. - // Downscaling means that device-space coordinates are - // scaled *up* to find the image pixel coordinates. - // - // Cairo, and hence Gecko, can use RepeatPad on Xorg 1.7. We - // enable EXTEND_PAD provided that we're running on a recent - // enough X server. - if (static_cast<gfxXlibSurface*>(currentTarget)->IsPadSlow()) { - bool isDownscale = - aDeviceToImage._11 >= 1.0 && aDeviceToImage._22 >= 1.0 && - aDeviceToImage._21 == 0.0 && aDeviceToImage._12 == 0.0; - - GraphicsFilter filter = - isDownscale ? aDefaultFilter : (const GraphicsFilter)GraphicsFilter::FILTER_FAST; - aPattern->SetFilter(filter); - - // Use the default EXTEND_NONE - break; - } - // else fall through to EXTEND_PAD and the default filter. - } -#endif - - default: - // turn on EXTEND_PAD. - // This is what we really want for all surface types, if the - // implementation was universally good. - aPattern->SetExtend(gfxPattern::EXTEND_PAD); - aPattern->SetFilter(aDefaultFilter); - break; - } -} - bool gfxSurfaceDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect, bool aRepeat, const GraphicsFilter& aFilter, const gfxMatrix& aTransform) { - nsRefPtr<gfxPattern> pattern; - if (mDrawTarget) { - RefPtr<SourceSurface> source = mDrawTarget->Snapshot(); - pattern = new gfxPattern(source, Matrix()); - } else if (mSourceSurface) { - pattern = new gfxPattern(mSourceSurface, Matrix()); - } else { - pattern = new gfxPattern(mSurface); + ExtendMode extend = ExtendMode::CLAMP; + + if (aRepeat) { + extend = ExtendMode::REPEAT; } - if (aRepeat) { - pattern->SetExtend(gfxPattern::EXTEND_REPEAT); - pattern->SetFilter(aFilter); + + Matrix patternTransform = ToMatrix(aTransform * mTransform); + patternTransform.Invert(); + + SurfacePattern pattern(mSourceSurface, extend, + patternTransform, ToFilter(aFilter)); + + Rect fillRect = ToRect(aFillRect); + DrawTarget* dt = aContext->GetDrawTarget(); + + if (aContext->CurrentOperator() == gfxContext::OPERATOR_CLEAR) { + dt->ClearRect(fillRect); + } else if (aContext->CurrentOperator() == gfxContext::OPERATOR_SOURCE) { + // Emulate cairo operator source which is bound by mask! + dt->ClearRect(fillRect); + dt->FillRect(fillRect, pattern); } else { - GraphicsFilter filter = aFilter; - if (aContext->CurrentMatrix().HasOnlyIntegerTranslation() && - aTransform.HasOnlyIntegerTranslation()) - { - // If we only have integer translation, no special filtering needs to - // happen and we explicitly use FILTER_FAST. This is fast for some - // backends. - filter = GraphicsFilter::FILTER_FAST; - } - nsRefPtr<gfxASurface> currentTarget = aContext->CurrentSurface(); - gfxMatrix deviceSpaceToImageSpace = - DeviceToImageTransform(aContext, aTransform); - PreparePatternForUntiledDrawing(pattern, deviceSpaceToImageSpace, - currentTarget, filter); + CompositionOp op = CompositionOpForOp(aContext->CurrentOperator()); + AntialiasMode aaMode = + aContext->CurrentAntialiasMode() == gfxContext::MODE_ALIASED ? + AntialiasMode::NONE : + AntialiasMode::SUBPIXEL; + dt->FillRect(fillRect, pattern, DrawOptions(1.0f, op, aaMode)); } - pattern->SetMatrix(aTransform * mTransform); - aContext->NewPath(); - aContext->SetPattern(pattern); - aContext->Rectangle(aFillRect); - aContext->Fill(); - // clear the pattern so that the snapshot is released before the - // drawable is destroyed - aContext->SetDeviceColor(gfxRGBA(0.0, 0.0, 0.0, 0.0)); return true; } -already_AddRefed<gfxImageSurface> -gfxSurfaceDrawable::GetAsImageSurface() -{ - if (mDrawTarget || mSourceSurface) { - // TODO: Find a way to implement this. The caller really wants a 'sub-image' of - // the original, without having to do a copy. GetDataSurface() might just copy, - // which isn't useful. - return nullptr; - - } - return mSurface->GetAsImageSurface(); -} - gfxCallbackDrawable::gfxCallbackDrawable(gfxDrawingCallback* aCallback, const gfxIntSize aSize) : gfxDrawable(aSize) , mCallback(aCallback) { } already_AddRefed<gfxSurfaceDrawable>
--- a/gfx/thebes/gfxDrawable.h +++ b/gfx/thebes/gfxDrawable.h @@ -8,17 +8,16 @@ #include "nsAutoPtr.h" #include "gfxRect.h" #include "gfxMatrix.h" #include "GraphicsFilter.h" #include "mozilla/gfx/2D.h" class gfxASurface; -class gfxImageSurface; class gfxContext; class gfxPattern; /** * gfxDrawable * An Interface representing something that has an intrinsic size and can draw * itself repeatedly. */ @@ -35,51 +34,42 @@ public: * pattern prior to rendering it. * @return whether drawing was successful */ virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, bool aRepeat, const GraphicsFilter& aFilter, const gfxMatrix& aTransform = gfxMatrix()) = 0; - virtual already_AddRefed<gfxImageSurface> GetAsImageSurface() { return nullptr; } virtual gfxIntSize Size() { return mSize; } protected: // Protected destructor, to discourage deletion outside of Release(): virtual ~gfxDrawable() {} const gfxIntSize mSize; }; /** * gfxSurfaceDrawable * A convenience implementation of gfxDrawable for surfaces. */ class gfxSurfaceDrawable : public gfxDrawable { public: - gfxSurfaceDrawable(gfxASurface* aSurface, const gfxIntSize aSize, - const gfxMatrix aTransform = gfxMatrix()); - gfxSurfaceDrawable(mozilla::gfx::DrawTarget* aDT, const gfxIntSize aSize, - const gfxMatrix aTransform = gfxMatrix()); gfxSurfaceDrawable(mozilla::gfx::SourceSurface* aSurface, const gfxIntSize aSize, const gfxMatrix aTransform = gfxMatrix()); virtual ~gfxSurfaceDrawable() {} virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, bool aRepeat, const GraphicsFilter& aFilter, const gfxMatrix& aTransform = gfxMatrix()); - virtual already_AddRefed<gfxImageSurface> GetAsImageSurface(); - protected: - nsRefPtr<gfxASurface> mSurface; - mozilla::RefPtr<mozilla::gfx::DrawTarget> mDrawTarget; mozilla::RefPtr<mozilla::gfx::SourceSurface> mSourceSurface; const gfxMatrix mTransform; }; /** * gfxDrawingCallback * A simple drawing functor. */
--- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -3061,27 +3061,28 @@ static AntialiasMode Get2DAAMode(gfxFont // The TextRunDrawParams are set up once per textrun; the FontDrawParams // are dependent on the specific font, so they are set per GlyphRun. struct TextRunDrawParams { RefPtr<DrawTarget> dt; gfxContext *context; gfxFont::Spacing *spacing; gfxTextRunDrawCallbacks *callbacks; - gfxTextContextPaint *contextPaint; + gfxTextContextPaint *runContextPaint; gfxFloat direction; double devPerApp; DrawMode drawMode; bool isRTL; bool paintSVGGlyphs; }; struct FontDrawParams { RefPtr<ScaledFont> scaledFont; RefPtr<GlyphRenderingOptions> renderingOptions; + gfxTextContextPaint *contextPaint; Matrix *passedInvMatrix; Matrix matInv; double synBoldOnePixelOffset; int32_t extraStrikes; DrawOptions drawOptions; bool haveSVGGlyphs; bool haveColorGlyphs; }; @@ -3145,22 +3146,22 @@ private: gfxContext::AzureState state = mRunParams.context->CurrentState(); if ((int(mRunParams.drawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) { FlushStroke(buf, state); } if (int(mRunParams.drawMode) & int(DrawMode::GLYPH_FILL)) { - if (state.pattern || mRunParams.contextPaint) { + if (state.pattern || mFontParams.contextPaint) { Pattern *pat; nsRefPtr<gfxPattern> fillPattern; - if (!mRunParams.contextPaint || - !(fillPattern = mRunParams.contextPaint->GetFillPattern( + if (!mFontParams.contextPaint || + !(fillPattern = mFontParams.contextPaint->GetFillPattern( mRunParams.context->CurrentMatrix()))) { if (state.pattern) { pat = state.pattern->GetPattern(mRunParams.dt, state.patternTransformChanged ? &state.patternTransform : nullptr); } else { pat = nullptr; } @@ -3230,19 +3231,19 @@ private: mNumGlyphs = 0; } void FlushStroke(gfx::GlyphBuffer& aBuf, gfxContext::AzureState& aState) { RefPtr<Path> path = mFontParams.scaledFont->GetPathForGlyphs(aBuf, mRunParams.dt); - if (mRunParams.contextPaint) { + if (mFontParams.contextPaint) { nsRefPtr<gfxPattern> strokePattern = - mRunParams.contextPaint->GetStrokePattern( + mFontParams.contextPaint->GetStrokePattern( mRunParams.context->CurrentMatrix()); if (strokePattern) { mRunParams.dt->Stroke(path, *strokePattern->GetPattern(mRunParams.dt), aState.strokeOptions); } } } @@ -3310,17 +3311,17 @@ gfxFont::DrawOneGlyph(uint32_t aGlyphID, ToDeviceUnits(aPt->y, runParams.devPerApp)); if (fontParams.haveSVGGlyphs) { if (!runParams.paintSVGGlyphs) { return; } DrawMode mode = ForcePaintingDrawMode(runParams.drawMode); if (RenderSVGGlyph(runParams.context, devPt, mode, - aGlyphID, runParams.contextPaint, + aGlyphID, fontParams.contextPaint, runParams.callbacks, *aEmittedGlyphs)) { return; } } if (fontParams.haveColorGlyphs && RenderColorGlyph(runParams.context, fontParams.scaledFont, fontParams.renderingOptions, fontParams.drawOptions, @@ -3414,17 +3415,17 @@ gfxFont::DrawGlyphs(gfxShapedText } } return emittedGlyphs; } void gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, - gfxPoint *aPt, TextRunDrawParams& aRunParams) + gfxPoint *aPt, const TextRunDrawParams& aRunParams) { NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH || !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)), "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); if (aStart >= aEnd) { return; } @@ -3433,27 +3434,28 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint fontParams.scaledFont = GetScaledFont(aRunParams.dt); if (!fontParams.scaledFont) { return; } fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this); fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs(); + fontParams.contextPaint = aRunParams.runContextPaint; nsAutoPtr<gfxTextContextPaint> contextPaint; - if (fontParams.haveSVGGlyphs && !aRunParams.contextPaint) { + if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) { // If no pattern is specified for fill, use the current pattern NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0, "no pattern supplied for stroking text"); nsRefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern(); contextPaint = new SimpleTextContextPaint(fillPattern, nullptr, aRunParams.context->CurrentMatrix()); - aRunParams.contextPaint = contextPaint; + fontParams.contextPaint = contextPaint; } // Synthetic-bold strikes are each offset one device pixel in run direction. // (these values are only needed if IsSyntheticBold() is true) if (IsSyntheticBold()) { double xscale = CalcXScale(aRunParams.context); fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale; if (xscale != 0.0) { @@ -7099,17 +7101,17 @@ gfxTextRun::Draw(gfxContext *aContext, g // to draw, regardless of the font used. TextRunDrawParams params; params.context = aContext; params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); params.isRTL = IsRightToLeft(); params.direction = direction; params.drawMode = aDrawMode; params.callbacks = aCallbacks; - params.contextPaint = aContextPaint; + params.runContextPaint = aContextPaint; params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs; params.dt = aContext->GetDrawTarget(); gfxPoint pt = aPt; // synthetic bolding draws glyphs twice ==> colors with opacity won't draw // correctly unless first drawn without alpha BufferAlphaColor syntheticBoldBuffer(aContext);
--- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -1767,17 +1767,17 @@ public: * Draw a series of glyphs to aContext. The direction of aTextRun must * be honoured. * @param aStart the first character to draw * @param aEnd draw characters up to here * @param aPt the baseline origin; the left end of the baseline * for LTR textruns, the right end for RTL textruns. * On return, this will be updated to the other end of the baseline. * In application units, really! - * @param aParams record with drawing parameters, see TextRunDrawParams. + * @param aRunParams record with drawing parameters, see TextRunDrawParams. * Particular fields of interest include * .spacing spacing to insert before and after characters (for RTL * glyphs, before-spacing is inserted to the right of characters). There * are aEnd - aStart elements in this array, unless it's null to indicate * that there is no spacing. * .drawMode specifies whether the fill or stroke of the glyph should be * drawn, or if it should be drawn into the current path * .contextPaint information about how to construct the fill and @@ -1786,17 +1786,17 @@ public: * .context the Thebes graphics context to which we're drawing * .dt Moz2D DrawTarget to which we're drawing * * Callers guarantee: * -- aStart and aEnd are aligned to cluster and ligature boundaries * -- all glyphs use this font */ void Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, - gfxPoint *aPt, TextRunDrawParams& aParams); + gfxPoint *aPt, const TextRunDrawParams& aRunParams); /** * Measure a run of characters. See gfxTextRun::Metrics. * @param aTight if false, then return the union of the glyph extents * with the font-box for the characters (the rectangle with x=0,width= * the advance width for the character run,y=-(font ascent), and height= * font ascent + font descent). Otherwise, we must return as tight as possible * an approximation to the area actually painted by glyphs.
--- a/gfx/thebes/gfxPoint.h +++ b/gfx/thebes/gfxPoint.h @@ -14,25 +14,25 @@ #include "gfxTypes.h" struct gfxSize : public mozilla::gfx::BaseSize<gfxFloat, gfxSize> { typedef mozilla::gfx::BaseSize<gfxFloat, gfxSize> Super; gfxSize() : Super() {} gfxSize(gfxFloat aWidth, gfxFloat aHeight) : Super(aWidth, aHeight) {} - gfxSize(const nsIntSize& aSize) : Super(aSize.width, aSize.height) {} + MOZ_IMPLICIT gfxSize(const nsIntSize& aSize) : Super(aSize.width, aSize.height) {} }; struct gfxPoint : public mozilla::gfx::BasePoint<gfxFloat, gfxPoint> { typedef mozilla::gfx::BasePoint<gfxFloat, gfxPoint> Super; gfxPoint() : Super() {} gfxPoint(gfxFloat aX, gfxFloat aY) : Super(aX, aY) {} - gfxPoint(const nsIntPoint& aPoint) : Super(aPoint.x, aPoint.y) {} + MOZ_IMPLICIT gfxPoint(const nsIntPoint& aPoint) : Super(aPoint.x, aPoint.y) {} bool WithinEpsilonOf(const gfxPoint& aPoint, gfxFloat aEpsilon) { return fabs(aPoint.x - x) < aEpsilon && fabs(aPoint.y - y) < aEpsilon; } }; inline gfxPoint operator*(const gfxPoint& aPoint, const gfxSize& aSize)
--- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -393,38 +393,32 @@ CreateSamplingRestrictedDrawable(gfxDraw // if 'needed' is empty, nothing will be drawn since aFill // must be entirely outside the clip region, so it doesn't // matter what we do here, but we should avoid trying to // create a zero-size surface. if (needed.IsEmpty()) return nullptr; - nsRefPtr<gfxDrawable> drawable; gfxIntSize size(int32_t(needed.Width()), int32_t(needed.Height())); - nsRefPtr<gfxImageSurface> image = aDrawable->GetAsImageSurface(); - if (image && gfxRect(0, 0, image->GetSize().width, image->GetSize().height).Contains(needed)) { - nsRefPtr<gfxASurface> temp = image->GetSubimage(needed); - drawable = new gfxSurfaceDrawable(temp, size, gfxMatrix().Translate(-needed.TopLeft())); - } else { - RefPtr<DrawTarget> target = - gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(ToIntSize(size), - aFormat); - if (!target) { - return nullptr; - } - - nsRefPtr<gfxContext> tmpCtx = new gfxContext(target); - tmpCtx->SetOperator(OptimalFillOperator()); - aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), true, - GraphicsFilter::FILTER_FAST, gfxMatrix().Translate(needed.TopLeft())); - drawable = new gfxSurfaceDrawable(target, size, gfxMatrix().Translate(-needed.TopLeft())); + RefPtr<DrawTarget> target = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(ToIntSize(size), + aFormat); + if (!target) { + return nullptr; } + nsRefPtr<gfxContext> tmpCtx = new gfxContext(target); + tmpCtx->SetOperator(OptimalFillOperator()); + aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), true, + GraphicsFilter::FILTER_FAST, gfxMatrix().Translate(needed.TopLeft())); + RefPtr<SourceSurface> surface = target->Snapshot(); + + nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size, gfxMatrix().Translate(-needed.TopLeft())); return drawable.forget(); } #endif // !MOZ_GFX_OPTIMIZE_MOBILE // working around cairo/pixman bug (bug 364968) // Our device-space-to-image-space transform may not be acceptable to pixman. struct MOZ_STACK_CLASS AutoCairoPixmanBugWorkaround {
--- a/image/src/SurfaceCache.cpp +++ b/image/src/SurfaceCache.cpp @@ -112,46 +112,46 @@ private: * surface. */ class CachedSurface { ~CachedSurface() {} public: NS_INLINE_DECL_REFCOUNTING(CachedSurface) - CachedSurface(DrawTarget* aTarget, + CachedSurface(SourceSurface* aSurface, const IntSize aTargetSize, const Cost aCost, const ImageKey aImageKey, const SurfaceKey& aSurfaceKey) - : mTarget(aTarget) + : mSurface(aSurface) , mTargetSize(aTargetSize) , mCost(aCost) , mImageKey(aImageKey) , mSurfaceKey(aSurfaceKey) { - MOZ_ASSERT(mTarget, "Must have a valid DrawTarget"); + MOZ_ASSERT(mSurface, "Must have a valid SourceSurface"); MOZ_ASSERT(mImageKey, "Must have a valid image key"); } already_AddRefed<gfxDrawable> Drawable() const { nsRefPtr<gfxDrawable> drawable = - new gfxSurfaceDrawable(mTarget, ThebesIntSize(mTargetSize)); + new gfxSurfaceDrawable(mSurface, ThebesIntSize(mTargetSize)); return drawable.forget(); } ImageKey GetImageKey() const { return mImageKey; } SurfaceKey GetSurfaceKey() const { return mSurfaceKey; } CostEntry GetCostEntry() { return image::CostEntry(this, mCost); } nsExpirationState* GetExpirationState() { return &mExpirationState; } private: nsExpirationState mExpirationState; - nsRefPtr<DrawTarget> mTarget; + nsRefPtr<SourceSurface> mSurface; const IntSize mTargetSize; const Cost mCost; const ImageKey mImageKey; const SurfaceKey mSurfaceKey; }; /* * An ImageSurfaceCache is a per-image surface cache. For correctness we must be @@ -235,31 +235,31 @@ private: UnregisterWeakMemoryReporter(this); } public: void InitMemoryReporter() { RegisterWeakMemoryReporter(this); } - void Insert(DrawTarget* aTarget, + void Insert(SourceSurface* aSurface, IntSize aTargetSize, const Cost aCost, const ImageKey aImageKey, const SurfaceKey& aSurfaceKey) { MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey).take(), "Inserting a duplicate drawable into the SurfaceCache"); // If this is bigger than the maximum cache size, refuse to cache it. if (!CanHold(aCost)) return; nsRefPtr<CachedSurface> surface = - new CachedSurface(aTarget, aTargetSize, aCost, aImageKey, aSurfaceKey); + new CachedSurface(aSurface, aTargetSize, aCost, aImageKey, aSurfaceKey); // Remove elements in order of cost until we can fit this in the cache. while (aCost > mAvailableCost) { MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit"); Remove(mCosts.LastElement().GetSurface()); } // Locate the appropriate per-image cache. If there's not an existing cache @@ -503,25 +503,25 @@ SurfaceCache::Lookup(const ImageKey a { MOZ_ASSERT(sInstance, "Should be initialized"); MOZ_ASSERT(NS_IsMainThread()); return sInstance->Lookup(aImageKey, aSurfaceKey); } /* static */ void -SurfaceCache::Insert(DrawTarget* aTarget, +SurfaceCache::Insert(SourceSurface* aSurface, const ImageKey aImageKey, const SurfaceKey& aSurfaceKey) { MOZ_ASSERT(sInstance, "Should be initialized"); MOZ_ASSERT(NS_IsMainThread()); Cost cost = ComputeCost(aSurfaceKey.Size()); - return sInstance->Insert(aTarget, aSurfaceKey.Size(), cost, aImageKey, + return sInstance->Insert(aSurface, aSurfaceKey.Size(), cost, aImageKey, aSurfaceKey); } /* static */ bool SurfaceCache::CanHold(const IntSize& aSize) { MOZ_ASSERT(sInstance, "Should be initialized"); MOZ_ASSERT(NS_IsMainThread());
--- a/image/src/SurfaceCache.h +++ b/image/src/SurfaceCache.h @@ -130,17 +130,17 @@ struct SurfaceCache * without first calling Lookup to verify that the surface is not already in * the cache. * * @param aTarget The new surface (in the form of a DrawTarget) to insert * into the cache. * @param aImageKey Key data identifying which image the surface belongs to. * @param aSurfaceKey Key data which uniquely identifies the requested surface. */ - static void Insert(gfx::DrawTarget* aTarget, + static void Insert(gfx::SourceSurface* aSurface, const ImageKey aImageKey, const SurfaceKey& aSurfaceKey); /* * Checks if a surface of a given size could possibly be stored in the cache. * If CanHold() returns false, Insert() will always fail to insert the * surface, but the inverse is not true: Insert() may take more information * into account than just image size when deciding whether to cache the
--- a/image/src/VectorImage.cpp +++ b/image/src/VectorImage.cpp @@ -930,28 +930,30 @@ VectorImage::CreateDrawableAndShow(const gfxUtils::DrawPixelSnapped(ctx, svgDrawable, gfxMatrix(), ThebesIntRect(aParams.imageRect), ThebesIntRect(aParams.imageRect), ThebesIntRect(aParams.imageRect), ThebesIntRect(aParams.imageRect), SurfaceFormat::B8G8R8A8, GraphicsFilter::FILTER_NEAREST, aParams.flags); + RefPtr<SourceSurface> surface = target->Snapshot(); + // Attempt to cache the resulting surface. - SurfaceCache::Insert(target, + SurfaceCache::Insert(surface, ImageKey(this), SurfaceKey(aParams.imageRect.Size(), aParams.scale, aParams.svgContext, aParams.animationTime, aParams.flags)); // Draw. Note that if SurfaceCache::Insert failed for whatever reason, // then |target| is all that is keeping the pixel data alive, so we have // to draw before returning from this function. nsRefPtr<gfxDrawable> drawable = - new gfxSurfaceDrawable(target, ThebesIntSize(aParams.imageRect.Size())); + new gfxSurfaceDrawable(surface, ThebesIntSize(aParams.imageRect.Size())); Show(drawable, aParams); } void VectorImage::Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams) { MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now");
--- a/js/public/ProfilingFrameIterator.h +++ b/js/public/ProfilingFrameIterator.h @@ -22,54 +22,50 @@ namespace JS { // This iterator can be used to walk the stack of a thread suspended at an // arbitrary pc. To provide acurate results, profiling must have been enabled // (via EnableRuntimeProfilingStack) before executing the callstack being // unwound. class JS_PUBLIC_API(ProfilingFrameIterator) { js::AsmJSActivation *activation_; - static const unsigned StorageSpace = 5 * sizeof(void*); + static const unsigned StorageSpace = 6 * sizeof(void*); mozilla::AlignedStorage<StorageSpace> storage_; js::AsmJSProfilingFrameIterator &iter() { JS_ASSERT(!done()); return *reinterpret_cast<js::AsmJSProfilingFrameIterator*>(storage_.addr()); } const js::AsmJSProfilingFrameIterator &iter() const { JS_ASSERT(!done()); return *reinterpret_cast<const js::AsmJSProfilingFrameIterator*>(storage_.addr()); } void settle(); public: struct RegisterState { + RegisterState() : pc(nullptr), sp(nullptr), lr(nullptr) {} void *pc; void *sp; -#if defined(JS_CODEGEN_ARM) void *lr; -#endif }; ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state); ~ProfilingFrameIterator(); void operator++(); bool done() const { return !activation_; } - enum Kind { - Function, - AsmJSTrampoline, - CppFunction - }; - Kind kind() const; + // Assuming the stack grows down (we do), the return value: + // - always points into the stack + // - is weakly monotonically increasing (may be equal for successive frames) + // - will compare greater than newer native and psuedo-stack frame addresses + // and less than older native and psuedo-stack frame addresses + void *stackAddress() const; - // Methods available if kind() == Function: - JSAtom *functionDisplayAtom() const; - const char *functionFilename() const; - - // Methods available if kind() != Function - const char *nonFunctionDescription() const; + // Return a label suitable for regexp-matching as performed by + // browser/devtools/profiler/cleopatra/js/parserWorker.js + const char *label() const; }; } // namespace JS #endif /* js_ProfilingFrameIterator_h */
--- a/js/public/ProfilingStack.h +++ b/js/public/ProfilingStack.h @@ -56,30 +56,33 @@ class ProfileEntry // a JS frame is assumed by default. You're not allowed to publicly // change the frame type. Instead, call `setJsFrame` or `setCppFrame`. IS_CPP_ENTRY = 0x01, // Indicate that copying the frame label is not necessary when taking a // sample of the pseudostack. FRAME_LABEL_COPY = 0x02, + // This ProfileEntry was pushed immediately before calling into asm.js. + ASMJS = 0x04, + // Mask for removing all flags except the category information. - CATEGORY_MASK = ~IS_CPP_ENTRY & ~FRAME_LABEL_COPY + CATEGORY_MASK = ~IS_CPP_ENTRY & ~FRAME_LABEL_COPY & ~ASMJS }; MOZ_BEGIN_NESTED_ENUM_CLASS(Category, uint32_t) - OTHER = 0x04, - CSS = 0x08, - JS = 0x10, - GC = 0x20, - CC = 0x40, - NETWORK = 0x80, - GRAPHICS = 0x100, - STORAGE = 0x200, - EVENTS = 0x400, + OTHER = 0x08, + CSS = 0x10, + JS = 0x20, + GC = 0x40, + CC = 0x80, + NETWORK = 0x100, + GRAPHICS = 0x200, + STORAGE = 0x400, + EVENTS = 0x800, FIRST = OTHER, LAST = EVENTS MOZ_END_NESTED_ENUM_CLASS(Category) // All of these methods are marked with the 'volatile' keyword because SPS's // representation of the stack is stored such that all ProfileEntry // instances are volatile. These methods would not be available unless they
--- a/js/src/jit-test/tests/asm.js/testProfiling.js +++ b/js/src/jit-test/tests/asm.js/testProfiling.js @@ -1,122 +1,133 @@ load(libdir + "asm.js"); // Single-step profiling currently only works in the ARM simulator if (!getBuildConfiguration()["arm-simulator"]) quit(); +function assertEqualStacks(got, expect) +{ + // Strip off the " (script/library info)" + got = String(got).replace(/ \([^\)]*\)/g, ""); + + // Shorten FFI/entry trampolines + got = got.replace(/FFI trampoline/g, "<").replace(/entry trampoline/g, ">"); + + assertEq(got, expect); +} + // Test profiling enablement while asm.js is running. var stacks; var ffi = function(enable) { if (enable == +1) enableSPSProfiling(); if (enable == -1) disableSPSProfiling(); enableSingleStepProfiling(); stacks = disableSingleStepProfiling(); } var f = asmLink(asmCompile('global','ffis',USE_ASM + "var ffi=ffis.ffi; function g(i) { i=i|0; ffi(i|0) } function f(i) { i=i|0; g(i|0) } return f"), null, {ffi}); f(0); -assertEq(String(stacks), ""); +assertEqualStacks(stacks, ""); f(+1); -assertEq(String(stacks), ""); +assertEqualStacks(stacks, ""); f(0); -assertEq(String(stacks), "*gf*"); +assertEqualStacks(stacks, "<gf>"); f(-1); -assertEq(String(stacks), "*gf*"); +assertEqualStacks(stacks, "<gf>"); f(0); -assertEq(String(stacks), ""); +assertEqualStacks(stacks, ""); // Enable profiling for the rest of the tests. enableSPSProfiling(); var f = asmLink(asmCompile(USE_ASM + "function f() { return 42 } return f")); enableSingleStepProfiling(); assertEq(f(), 42); var stacks = disableSingleStepProfiling(); -assertEq(String(stacks), ",*,f*,*,"); +assertEqualStacks(stacks, ",>,f>,>,"); var f = asmLink(asmCompile(USE_ASM + "function g(i) { i=i|0; return (i+1)|0 } function f() { return g(42)|0 } return f")); enableSingleStepProfiling(); assertEq(f(), 43); var stacks = disableSingleStepProfiling(); -assertEq(String(stacks), ",*,f*,gf*,f*,*,"); +assertEqualStacks(stacks, ",>,f>,gf>,f>,>,"); var f = asmLink(asmCompile(USE_ASM + "function g1() { return 1 } function g2() { return 2 } function f(i) { i=i|0; return TBL[i&1]()|0 } var TBL=[g1,g2]; return f")); enableSingleStepProfiling(); assertEq(f(0), 1); assertEq(f(1), 2); var stacks = disableSingleStepProfiling(); -assertEq(String(stacks), ",*,f*,g1f*,f*,*,,*,f*,g2f*,f*,*,"); +assertEqualStacks(stacks, ",>,f>,g1f>,f>,>,,>,f>,g2f>,f>,>,"); function testBuiltinD2D(name) { var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d) { d=+d; return +fun(d) } return f"), this); enableSingleStepProfiling(); assertEq(f(.1), eval("Math." + name + "(.1)")); var stacks = disableSingleStepProfiling(); - assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,"); + assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,"); } for (name of ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'ceil', 'floor', 'exp', 'log']) testBuiltinD2D(name); function testBuiltinF2F(name) { var f = asmLink(asmCompile('g', USE_ASM + "var tof=g.Math.fround; var fun=g.Math." + name + "; function f(d) { d=tof(d); return tof(fun(d)) } return f"), this); enableSingleStepProfiling(); assertEq(f(.1), eval("Math.fround(Math." + name + "(Math.fround(.1)))")); var stacks = disableSingleStepProfiling(); - assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,"); + assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,"); } for (name of ['ceil', 'floor']) testBuiltinF2F(name); function testBuiltinDD2D(name) { var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d, e) { d=+d; e=+e; return +fun(d,e) } return f"), this); enableSingleStepProfiling(); assertEq(f(.1, .2), eval("Math." + name + "(.1, .2)")); var stacks = disableSingleStepProfiling(); - assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,"); + assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,"); } for (name of ['atan2', 'pow']) testBuiltinDD2D(name); // FFI tests: setJitCompilerOption("ion.usecount.trigger", 10); setJitCompilerOption("baseline.usecount.trigger", 0); setJitCompilerOption("offthread-compilation.enable", 0); var ffi1 = function() { return 10 } var ffi2 = function() { return 73 } var f = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis.ffi2; function f() { return ((ffi1()|0) + (ffi2()|0))|0 } return f"), null, {ffi1,ffi2}); // Interpreter FFI exit enableSingleStepProfiling(); assertEq(f(), 83); var stacks = disableSingleStepProfiling(); -assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,"); +assertEqualStacks(stacks, ",>,f>,<f>,f>,<f>,f>,>,"); // Ion FFI exit for (var i = 0; i < 20; i++) assertEq(f(), 83); enableSingleStepProfiling(); assertEq(f(), 83); var stacks = disableSingleStepProfiling(); -assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,"); +assertEqualStacks(stacks, ",>,f>,<f>,f>,<f>,f>,>,"); var ffi1 = function() { return 15 } var ffi2 = function() { return f2() + 17 } var {f1,f2} = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis.ffi2; function f2() { return ffi1()|0 } function f1() { return ffi2()|0 } return {f1:f1, f2:f2}"), null, {ffi1, ffi2}); // Interpreter FFI exit enableSingleStepProfiling(); assertEq(f1(), 32); var stacks = disableSingleStepProfiling(); -assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,"); +assertEqualStacks(stacks, ",>,f1>,<f1>,><f1>,f2><f1>,<f2><f1>,f2><f1>,><f1>,<f1>,f1>,>,"); // Ion FFI exit for (var i = 0; i < 20; i++) assertEq(f1(), 32); enableSingleStepProfiling(); assertEq(f1(), 32); var stacks = disableSingleStepProfiling(); -assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,"); +assertEqualStacks(stacks, ",>,f1>,<f1>,><f1>,f2><f1>,<f2><f1>,f2><f1>,><f1>,<f1>,f1>,>,"); // This takes forever to run. // Stack-overflow exit test //var limit = -1; //var maxct = 0; //function ffi(ct) { if (ct == limit) { enableSingleStepProfiling(); print("enabled"); } maxct = ct; } //var f = asmLink(asmCompile('g', 'ffis',USE_ASM + "var ffi=ffis.ffi; var ct=0; function rec(){ ct=(ct+1)|0; ffi(ct|0); rec() } function f() { ct=0; rec() } return f"), null, {ffi}); //// First find the stack limit:
--- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -1013,16 +1013,20 @@ class MOZ_STACK_CLASS ModuleCompiler explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) { u.func = func; } }; private: struct SlowFunction { + SlowFunction(PropertyName *name, unsigned ms, unsigned line, unsigned column) + : name(name), ms(ms), line(line), column(column) + {} + PropertyName *name; unsigned ms; unsigned line; unsigned column; }; typedef HashMap<PropertyName*, MathBuiltin> MathNameMap; typedef HashMap<PropertyName*, Global*> GlobalMap; @@ -1418,37 +1422,35 @@ class MOZ_STACK_CLASS ModuleCompiler } void startFunctionBodies() { module_->startFunctionBodies(); } bool finishGeneratingFunction(Func &func, CodeGenerator &codegen, const AsmJSFunctionLabels &labels) { - if (!module_->addFunctionCodeRange(func.name(), labels)) + uint32_t line, column; + tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column); + + if (!module_->addFunctionCodeRange(func.name(), line, labels)) return false; jit::IonScriptCounts *counts = codegen.extractScriptCounts(); if (counts && !module_->addFunctionCounts(counts)) { js_delete(counts); return false; } if (func.compileTime() >= 250) { - SlowFunction sf; - sf.name = func.name(); - sf.ms = func.compileTime(); - tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &sf.line, &sf.column); + SlowFunction sf(func.name(), func.compileTime(), line, column); if (!slowFunctions_.append(sf)) return false; } #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) - uint32_t line, column; - tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column); unsigned begin = labels.begin.offset(); unsigned end = labels.end.offset(); if (!module_->addProfiledFunction(func.name(), begin, end, line, column)) return false; # ifdef JS_ION_PERF // Per-block profiling info uses significantly more memory so only store // this information if it is actively requested. if (PerfBlockEnabled()) { @@ -5942,23 +5944,29 @@ static const unsigned FramePushedAfterSa #else static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) + NonVolatileRegs.fpus().getPushSizeInBytes(); #endif static bool GenerateEntry(ModuleCompiler &m, unsigned exportIndex) { - PropertyName *funcName = m.module().exportedFunction(exportIndex).name(); - const ModuleCompiler::Func &func = *m.lookupFunction(funcName); - MacroAssembler &masm = m.masm(); Label begin; - GenerateAsmJSEntryPrologue(masm, &begin); + masm.align(CodeAlignment); + masm.bind(&begin); + +#if defined(JS_CODEGEN_ARM) + masm.push(lr); +#elif defined(JS_CODEGEN_MIPS) + masm.push(ra); +#endif + masm.subPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer); + masm.setFramePushed(0); // In constrast to the system ABI, the Ion convention is that all registers // are clobbered by calls. Thus, we must save the caller's non-volatile // registers. masm.PushRegsInMask(NonVolatileRegs); JS_ASSERT(masm.framePushed() == FramePushedAfterSave); // ARM and MIPS have a globally-pinned GlobalReg (x64 uses RIP-relative @@ -5974,31 +5982,33 @@ GenerateEntry(ModuleCompiler &m, unsigne #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) masm.loadPtr(Address(IntArgReg1, AsmJSModule::heapGlobalDataOffset()), HeapReg); #endif // Remember the stack pointer in the current AsmJSActivation. This will be // used by error exit paths to set the stack pointer back to what it was // right after the (C++) caller's non-volatile registers were saved so that // they can be restored. - Register activation = ABIArgGenerator::NonArgReturnVolatileReg0; + Register activation = ABIArgGenerator::NonArgReturnReg0; masm.loadAsmJSActivation(activation); masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfErrorRejoinSP())); // Get 'argv' into a non-arg register and save it on the stack. - Register argv = ABIArgGenerator::NonArgReturnVolatileReg0; - Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1; + Register argv = ABIArgGenerator::NonArgReturnReg0; + Register scratch = ABIArgGenerator::NonArgReturnReg1; #if defined(JS_CODEGEN_X86) masm.loadPtr(Address(StackPointer, sizeof(AsmJSFrame) + masm.framePushed()), argv); #else masm.movePtr(IntArgReg0, argv); #endif masm.Push(argv); // Bump the stack for the call. + PropertyName *funcName = m.module().exportedFunction(exportIndex).name(); + const ModuleCompiler::Func &func = *m.lookupFunction(funcName); unsigned stackDec = StackDecrementForCall(masm, func.sig().args()); masm.reserveStack(stackDec); // Copy parameters out of argv and into the registers/stack-slots specified by // the system ABI. for (ABIArgTypeIter iter(func.sig().args()); !iter.done(); iter++) { unsigned argOffset = iter.index() * sizeof(uint64_t); Address src(argv, argOffset); @@ -6047,18 +6057,19 @@ GenerateEntry(ModuleCompiler &m, unsigne break; } // Restore clobbered non-volatile registers of the caller. masm.PopRegsInMask(NonVolatileRegs); JS_ASSERT(masm.framePushed() == 0); masm.move32(Imm32(true), ReturnReg); - - GenerateAsmJSEntryEpilogue(masm); + masm.addPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer); + masm.ret(); + return m.finishGeneratingEntry(exportIndex, &begin) && !masm.oom(); } static void FillArgumentArray(ModuleCompiler &m, const VarTypeVector &argTypes, unsigned offsetToArgs, unsigned offsetToCallerStackArgs, Register scratch) { @@ -6117,17 +6128,17 @@ GenerateFFIInterpExit(ModuleCompiler &m, unsigned argvBytes = Max<size_t>(1, exit.sig().args().length()) * sizeof(Value); unsigned framePushed = StackDecrementForCall(masm, offsetToArgv + argvBytes); Label begin; GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::FFI, &begin); // Fill the argument array. unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed(); - Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0; + Register scratch = ABIArgGenerator::NonArgReturnReg0; FillArgumentArray(m, exit.sig().args(), offsetToArgv, offsetToCallerStackArgs, scratch); // Prepare the arguments for the call to InvokeFromAsmJS_*. ABIArgMIRTypeIter i(invokeArgTypes); // argument 0: exitIndex if (i->kind() == ABIArg::GPR) masm.mov(ImmWord(exitIndex), i->gpr()); @@ -6236,18 +6247,18 @@ GenerateFFIIonExit(ModuleCompiler &m, co // 1. Descriptor size_t argOffset = offsetToIonArgs; uint32_t descriptor = MakeFrameDescriptor(framePushed, JitFrame_Entry); masm.storePtr(ImmWord(uintptr_t(descriptor)), Address(StackPointer, argOffset)); argOffset += sizeof(size_t); // 2. Callee - Register callee = ABIArgGenerator::NonArgReturnVolatileReg0; // live until call - Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1; // clobbered + Register callee = ABIArgGenerator::NonArgReturnReg0; // live until call + Register scratch = ABIArgGenerator::NonArgReturnReg1; // repeatedly clobbered // 2.1. Get ExitDatum unsigned globalDataOffset = m.module().exitIndexToGlobalDataOffset(exitIndex); #if defined(JS_CODEGEN_X64) m.masm().append(AsmJSGlobalAccess(masm.leaRipRelative(callee), globalDataOffset)); #elif defined(JS_CODEGEN_X86) m.masm().append(AsmJSGlobalAccess(masm.movlWithPatch(Imm32(0), callee), globalDataOffset)); #else @@ -6456,16 +6467,20 @@ GenerateFFIExits(ModuleCompiler &m, cons } // Generate a thunk that updates fp before calling the given builtin so that // both the builtin and the calling function show up in profiler stacks. (This // thunk is dynamically patched in when profiling is enabled.) Since the thunk // pushes an AsmJSFrame on the stack, that means we must rebuild the stack // frame. Fortunately, these are low arity functions and everything is passed in // regs on everything but x86 anyhow. +// +// NB: Since this thunk is being injected at system ABI callsites, it must +// preserve the argument registers (going in) and the return register +// (coming out) and preserve non-volatile registers. static bool GenerateBuiltinThunk(ModuleCompiler &m, AsmJSExit::BuiltinKind builtin) { MacroAssembler &masm = m.masm(); JS_ASSERT(masm.framePushed() == 0); MIRTypeVector argTypes(m.cx()); switch (builtin) { @@ -6505,30 +6520,34 @@ GenerateBuiltinThunk(ModuleCompiler &m, MOZ_ASSUME_UNREACHABLE("Bad builtin"); } uint32_t framePushed = StackDecrementForCall(masm, argTypes); Label begin; GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::Builtin(builtin), &begin); - unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed(); for (ABIArgMIRTypeIter i(argTypes); !i.done(); i++) { if (i->kind() != ABIArg::Stack) continue; +#if !defined(JS_CODEGEN_ARM) + unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed(); Address srcAddr(StackPointer, offsetToCallerStackArgs + i->offsetFromArgBase()); Address dstAddr(StackPointer, i->offsetFromArgBase()); if (i.mirType() == MIRType_Int32 || i.mirType() == MIRType_Float32) { - masm.load32(srcAddr, ABIArgGenerator::NonArgReturnVolatileReg0); - masm.store32(ABIArgGenerator::NonArgReturnVolatileReg0, dstAddr); + masm.load32(srcAddr, ABIArgGenerator::NonArg_VolatileReg); + masm.store32(ABIArgGenerator::NonArg_VolatileReg, dstAddr); } else { JS_ASSERT(i.mirType() == MIRType_Double); masm.loadDouble(srcAddr, ScratchDoubleReg); masm.storeDouble(ScratchDoubleReg, dstAddr); } +#else + MOZ_CRASH("Architecture should have enough registers for all builtin calls"); +#endif } AssertStackAlignment(masm); masm.call(BuiltinToImmKind(builtin)); Label profilingReturn; GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::Builtin(builtin), &profilingReturn); return m.finishGeneratingBuiltinThunk(builtin, &begin, &profilingReturn) && !masm.oom(); @@ -6566,17 +6585,17 @@ GenerateAsyncInterruptExit(ModuleCompile // Be very careful here not to perturb the machine state before saving it // to the stack. In particular, add/sub instructions may set conditions in // the flags register. masm.push(Imm32(0)); // space for resumePC masm.pushFlags(); // after this we are safe to use sub masm.setFramePushed(0); // set to zero so we can use masm.framePushed() below masm.PushRegsInMask(AllRegsExceptSP); // save all GP/FP registers (except SP) - Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0; + Register scratch = ABIArgGenerator::NonArgReturnReg0; // Store resumePC into the reserved space. masm.loadAsmJSActivation(scratch); masm.loadPtr(Address(scratch, AsmJSActivation::offsetOfResumePC()), scratch); masm.storePtr(scratch, Address(StackPointer, masm.framePushed() + sizeof(void*))); // We know that StackPointer is word-aligned, but not necessarily // stack-aligned, so we need to align it dynamically. @@ -6721,17 +6740,17 @@ GenerateThrowStub(ModuleCompiler &m, Lab { MacroAssembler &masm = m.masm(); masm.align(CodeAlignment); masm.bind(throwLabel); // We are about to pop all frames in this AsmJSActivation. Set fp to null to // maintain the invariant that fp is either null or pointing to a valid // frame. - Register activation = ABIArgGenerator::NonArgReturnVolatileReg0; + Register activation = ABIArgGenerator::NonArgReturnReg0; masm.loadAsmJSActivation(activation); masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfFP())); masm.setFramePushed(FramePushedAfterSave); masm.loadPtr(Address(activation, AsmJSActivation::offsetOfErrorRejoinSP()), StackPointer); masm.PopRegsInMask(NonVolatileRegs); JS_ASSERT(masm.framePushed() == 0);
--- a/js/src/jit/AsmJSFrameIterator.cpp +++ b/js/src/jit/AsmJSFrameIterator.cpp @@ -94,22 +94,22 @@ AsmJSFrameIterator::computeLine(uint32_t /*****************************************************************************/ // Prologue/epilogue code generation // These constants reflect statically-determined offsets in the profiling // prologue/epilogue. The offsets are dynamically asserted during code // generation. #if defined(JS_CODEGEN_X64) static const unsigned PushedRetAddr = 0; -static const unsigned PushedFP = 11; -static const unsigned StoredFP = 15; +static const unsigned PushedFP = 10; +static const unsigned StoredFP = 14; #elif defined(JS_CODEGEN_X86) static const unsigned PushedRetAddr = 0; -static const unsigned PushedFP = 9; -static const unsigned StoredFP = 12; +static const unsigned PushedFP = 8; +static const unsigned StoredFP = 11; #elif defined(JS_CODEGEN_ARM) static const unsigned PushedRetAddr = 4; static const unsigned PushedFP = 16; static const unsigned StoredFP = 20; #elif defined(JS_CODEGEN_NONE) static const unsigned PushedRetAddr = 0; static const unsigned PushedFP = 1; static const unsigned StoredFP = 1; @@ -131,78 +131,92 @@ PushRetAddr(MacroAssembler &masm) // Generate a prologue that maintains AsmJSActivation::fp as the virtual frame // pointer so that AsmJSProfilingFrameIterator can walk the stack at any pc in // generated code. static void GenerateProfilingPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, Label *begin) { - Register act = ABIArgGenerator::NonArgReturnVolatileReg0; +#if !defined (JS_CODEGEN_ARM) + Register scratch = ABIArgGenerator::NonArg_VolatileReg; +#else + // Unfortunately, there are no unused non-arg volatile registers on ARM -- + // the MacroAssembler claims both lr and ip -- so we use the second scratch + // register (lr) and be very careful not to call any methods that use it. + Register scratch = lr; + masm.setSecondScratchReg(InvalidReg); +#endif // AsmJSProfilingFrameIterator needs to know the offsets of several key // instructions from 'begin'. To save space, we make these offsets static // constants and assert that they match the actual codegen below. On ARM, // this requires AutoForbidPools to prevent a constant pool from being // randomly inserted between two instructions. { #if defined(JS_CODEGEN_ARM) AutoForbidPools afp(&masm, /* number of instructions in scope = */ 5); #endif DebugOnly<uint32_t> offsetAtBegin = masm.currentOffset(); masm.bind(begin); PushRetAddr(masm); JS_ASSERT(PushedRetAddr == masm.currentOffset() - offsetAtBegin); - masm.loadAsmJSActivation(act); - masm.push(Address(act, AsmJSActivation::offsetOfFP())); + masm.loadAsmJSActivation(scratch); + masm.push(Address(scratch, AsmJSActivation::offsetOfFP())); JS_ASSERT(PushedFP == masm.currentOffset() - offsetAtBegin); - masm.storePtr(StackPointer, Address(act, AsmJSActivation::offsetOfFP())); + masm.storePtr(StackPointer, Address(scratch, AsmJSActivation::offsetOfFP())); JS_ASSERT(StoredFP == masm.currentOffset() - offsetAtBegin); } if (reason != AsmJSExit::None) - masm.store32(Imm32(reason), Address(act, AsmJSActivation::offsetOfExitReason())); + masm.store32_NoSecondScratch(Imm32(reason), Address(scratch, AsmJSActivation::offsetOfExitReason())); + +#if defined(JS_CODEGEN_ARM) + masm.setSecondScratchReg(lr); +#endif if (framePushed) masm.subPtr(Imm32(framePushed), StackPointer); } // Generate the inverse of GenerateProfilingPrologue. static void GenerateProfilingEpilogue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, Label *profilingReturn) { - Register act = ABIArgGenerator::NonArgReturnVolatileReg0; + Register scratch = ABIArgGenerator::NonReturn_VolatileReg0; +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) + Register scratch2 = ABIArgGenerator::NonReturn_VolatileReg1; +#endif if (framePushed) masm.addPtr(Imm32(framePushed), StackPointer); - masm.loadAsmJSActivation(act); + masm.loadAsmJSActivation(scratch); if (reason != AsmJSExit::None) - masm.store32(Imm32(AsmJSExit::None), Address(act, AsmJSActivation::offsetOfExitReason())); + masm.store32(Imm32(AsmJSExit::None), Address(scratch, AsmJSActivation::offsetOfExitReason())); // AsmJSProfilingFrameIterator assumes that there is only a single 'ret' // instruction (whose offset is recorded by profilingReturn) after the store // which sets AsmJSActivation::fp to the caller's fp. Use AutoForbidPools to // ensure that a pool is not inserted before the return (a pool inserts a // jump instruction). { #if defined(JS_CODEGEN_ARM) AutoForbidPools afp(&masm, /* number of instructions in scope = */ 3); #endif -#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) - masm.pop(Operand(act, AsmJSActivation::offsetOfFP())); +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) + masm.pop(scratch2); + masm.storePtr(scratch2, Address(scratch, AsmJSActivation::offsetOfFP())); #else - Register fp = ABIArgGenerator::NonArgReturnVolatileReg1; - masm.pop(fp); - masm.storePtr(fp, Address(act, AsmJSActivation::offsetOfFP())); + masm.pop(Operand(scratch, AsmJSActivation::offsetOfFP())); #endif masm.bind(profilingReturn); masm.ret(); } } // In profiling mode, we need to maintain fp so that we can unwind the stack at // any pc. In non-profiling mode, the only way to observe AsmJSActivation::fp is @@ -310,54 +324,31 @@ js::GenerateAsmJSStackOverflowExit(Macro masm.bind(overflowExit); // If we reach here via the non-profiling prologue, AsmJSActivation::fp has // not been updated. To enable stack unwinding from C++, store to it now. If // we reached here via the profiling prologue, we'll just store the same // value again. Do not update AsmJSFrame::callerFP as it is not necessary in // the non-profiling case (there is no return path from this point) and, in // the profiling case, it is already correct. - Register activation = ABIArgGenerator::NonArgReturnVolatileReg0; + Register activation = ABIArgGenerator::NonArgReturnReg0; masm.loadAsmJSActivation(activation); masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfFP())); // Prepare the stack for calling C++. if (unsigned stackDec = StackDecrementForCall(sizeof(AsmJSFrame), ShadowStackSpace)) masm.subPtr(Imm32(stackDec), StackPointer); // No need to restore the stack; the throw stub pops everything. masm.assertStackAlignment(); masm.call(AsmJSImmPtr(AsmJSImm_ReportOverRecursed)); masm.jump(throwLabel); } void -js::GenerateAsmJSEntryPrologue(MacroAssembler &masm, Label *begin) -{ - // Stack-unwinding stops at the entry prologue, so there is no need to - // update AsmJSActivation::fp. Furthermore, on ARM/MIPS, GlobalReg is not - // yet initialized, so we can't even if we wanted to. - masm.align(CodeAlignment); - masm.bind(begin); - PushRetAddr(masm); - masm.subPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer); - masm.setFramePushed(0); -} - -void -js::GenerateAsmJSEntryEpilogue(MacroAssembler &masm) -{ - // Inverse of GenerateAsmJSEntryPrologue: - JS_ASSERT(masm.framePushed() == 0); - masm.addPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer); - masm.ret(); - masm.setFramePushed(0); -} - -void js::GenerateAsmJSExitPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, Label *begin) { masm.align(CodeAlignment); GenerateProfilingPrologue(masm, framePushed, reason, begin); masm.setFramePushed(framePushed); } @@ -373,16 +364,17 @@ js::GenerateAsmJSExitEpilogue(MacroAssem /*****************************************************************************/ // AsmJSProfilingFrameIterator AsmJSProfilingFrameIterator::AsmJSProfilingFrameIterator(const AsmJSActivation &activation) : module_(&activation.module()), callerFP_(nullptr), callerPC_(nullptr), + stackAddress_(nullptr), exitReason_(AsmJSExit::None), codeRange_(nullptr) { initFromFP(activation); } static inline void AssertMatchesCallSite(const AsmJSModule &module, const AsmJSModule::CodeRange *calleeCodeRange, @@ -431,16 +423,17 @@ AsmJSProfilingFrameIterator::initFromFP( // they properly accumulate self-time) and for this we use the exitReason. exitReason_ = activation.exitReason(); void *pc = ReturnAddressFromFP(fp); const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(pc); JS_ASSERT(codeRange); codeRange_ = codeRange; + stackAddress_ = fp; switch (codeRange->kind()) { case AsmJSModule::CodeRange::Entry: callerPC_ = nullptr; callerFP_ = nullptr; break; case AsmJSModule::CodeRange::Function: fp = CallerFPFromFP(fp); @@ -554,16 +547,17 @@ AsmJSProfilingFrameIterator::AsmJSProfil callerPC_ = ReturnAddressFromFP(fp); callerFP_ = CallerFPFromFP(fp); AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, fp); break; } } codeRange_ = codeRange; + stackAddress_ = state.sp; JS_ASSERT(!done()); } void AsmJSProfilingFrameIterator::operator++() { if (exitReason_ != AsmJSExit::None) { JS_ASSERT(codeRange_); @@ -581,134 +575,96 @@ AsmJSProfilingFrameIterator::operator++( JS_ASSERT(callerPC_); const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(callerPC_); JS_ASSERT(codeRange); codeRange_ = codeRange; switch (codeRange->kind()) { case AsmJSModule::CodeRange::Entry: + JS_ASSERT(callerFP_ == nullptr); + JS_ASSERT(callerPC_ != nullptr); callerPC_ = nullptr; - callerFP_ = nullptr; break; case AsmJSModule::CodeRange::Function: case AsmJSModule::CodeRange::FFI: case AsmJSModule::CodeRange::Interrupt: case AsmJSModule::CodeRange::Inline: case AsmJSModule::CodeRange::Thunk: + stackAddress_ = callerFP_; callerPC_ = ReturnAddressFromFP(callerFP_); AssertMatchesCallSite(*module_, codeRange, callerPC_, CallerFPFromFP(callerFP_), callerFP_); callerFP_ = CallerFPFromFP(callerFP_); break; } JS_ASSERT(!done()); } -AsmJSProfilingFrameIterator::Kind -AsmJSProfilingFrameIterator::kind() const -{ - JS_ASSERT(!done()); - - switch (AsmJSExit::ExtractReasonKind(exitReason_)) { - case AsmJSExit::Reason_None: - break; - case AsmJSExit::Reason_Interrupt: - case AsmJSExit::Reason_FFI: - return JS::ProfilingFrameIterator::AsmJSTrampoline; - case AsmJSExit::Reason_Builtin: - return JS::ProfilingFrameIterator::CppFunction; - } - - auto codeRange = reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_); - switch (codeRange->kind()) { - case AsmJSModule::CodeRange::Function: - return JS::ProfilingFrameIterator::Function; - case AsmJSModule::CodeRange::Entry: - case AsmJSModule::CodeRange::FFI: - case AsmJSModule::CodeRange::Interrupt: - case AsmJSModule::CodeRange::Inline: - return JS::ProfilingFrameIterator::AsmJSTrampoline; - case AsmJSModule::CodeRange::Thunk: - return JS::ProfilingFrameIterator::CppFunction; - } - - MOZ_ASSUME_UNREACHABLE("Bad kind"); -} - -JSAtom * -AsmJSProfilingFrameIterator::functionDisplayAtom() const -{ - JS_ASSERT(kind() == JS::ProfilingFrameIterator::Function); - return reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_)->functionName(*module_); -} - -const char * -AsmJSProfilingFrameIterator::functionFilename() const -{ - JS_ASSERT(kind() == JS::ProfilingFrameIterator::Function); - return module_->scriptSource()->filename(); -} - static const char * BuiltinToName(AsmJSExit::BuiltinKind builtin) { + // Note: this label is regexp-matched by + // browser/devtools/profiler/cleopatra/js/parserWorker.js. + switch (builtin) { - case AsmJSExit::Builtin_ToInt32: return "ToInt32"; + case AsmJSExit::Builtin_ToInt32: return "ToInt32 (in asm.js)"; #if defined(JS_CODEGEN_ARM) - case AsmJSExit::Builtin_IDivMod: return "software idivmod"; - case AsmJSExit::Builtin_UDivMod: return "software uidivmod"; + case AsmJSExit::Builtin_IDivMod: return "software idivmod (in asm.js)"; + case AsmJSExit::Builtin_UDivMod: return "software uidivmod (in asm.js)"; #endif - case AsmJSExit::Builtin_ModD: return "fmod"; - case AsmJSExit::Builtin_SinD: return "Math.sin"; - case AsmJSExit::Builtin_CosD: return "Math.cos"; - case AsmJSExit::Builtin_TanD: return "Math.tan"; - case AsmJSExit::Builtin_ASinD: return "Math.asin"; - case AsmJSExit::Builtin_ACosD: return "Math.acos"; - case AsmJSExit::Builtin_ATanD: return "Math.atan"; + case AsmJSExit::Builtin_ModD: return "fmod (in asm.js)"; + case AsmJSExit::Builtin_SinD: return "Math.sin (in asm.js)"; + case AsmJSExit::Builtin_CosD: return "Math.cos (in asm.js)"; + case AsmJSExit::Builtin_TanD: return "Math.tan (in asm.js)"; + case AsmJSExit::Builtin_ASinD: return "Math.asin (in asm.js)"; + case AsmJSExit::Builtin_ACosD: return "Math.acos (in asm.js)"; + case AsmJSExit::Builtin_ATanD: return "Math.atan (in asm.js)"; case AsmJSExit::Builtin_CeilD: - case AsmJSExit::Builtin_CeilF: return "Math.ceil"; + case AsmJSExit::Builtin_CeilF: return "Math.ceil (in asm.js)"; case AsmJSExit::Builtin_FloorD: - case AsmJSExit::Builtin_FloorF: return "Math.floor"; - case AsmJSExit::Builtin_ExpD: return "Math.exp"; - case AsmJSExit::Builtin_LogD: return "Math.log"; - case AsmJSExit::Builtin_PowD: return "Math.pow"; - case AsmJSExit::Builtin_ATan2D: return "Math.atan2"; + case AsmJSExit::Builtin_FloorF: return "Math.floor (in asm.js)"; + case AsmJSExit::Builtin_ExpD: return "Math.exp (in asm.js)"; + case AsmJSExit::Builtin_LogD: return "Math.log (in asm.js)"; + case AsmJSExit::Builtin_PowD: return "Math.pow (in asm.js)"; + case AsmJSExit::Builtin_ATan2D: return "Math.atan2 (in asm.js)"; case AsmJSExit::Builtin_Limit: break; } MOZ_ASSUME_UNREACHABLE("Bad builtin kind"); } const char * -AsmJSProfilingFrameIterator::nonFunctionDescription() const +AsmJSProfilingFrameIterator::label() const { JS_ASSERT(!done()); - JS_ASSERT(kind() != JS::ProfilingFrameIterator::Function); + + // Note: this label is regexp-matched by + // browser/devtools/profiler/cleopatra/js/parserWorker.js. // Use the same string for both time inside and under so that the two // entries will be coalesced by the profiler. - const char *ffiDescription = "asm.js FFI trampoline"; - const char *interruptDescription = "asm.js slow script interrupt"; + const char *ffiDescription = "FFI trampoline (in asm.js)"; + const char *interruptDescription = "slow script interrupt trampoline (in asm.js)"; switch (AsmJSExit::ExtractReasonKind(exitReason_)) { case AsmJSExit::Reason_None: break; case AsmJSExit::Reason_FFI: return ffiDescription; case AsmJSExit::Reason_Interrupt: return interruptDescription; case AsmJSExit::Reason_Builtin: return BuiltinToName(AsmJSExit::ExtractBuiltinKind(exitReason_)); } auto codeRange = reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_); switch (codeRange->kind()) { - case AsmJSModule::CodeRange::Function: MOZ_ASSUME_UNREACHABLE("non-functions only"); - case AsmJSModule::CodeRange::Entry: return "asm.js entry trampoline"; + case AsmJSModule::CodeRange::Function: return codeRange->functionProfilingLabel(*module_); + case AsmJSModule::CodeRange::Entry: return "entry trampoline (in asm.js)"; case AsmJSModule::CodeRange::FFI: return ffiDescription; case AsmJSModule::CodeRange::Interrupt: return interruptDescription; - case AsmJSModule::CodeRange::Inline: return "asm.js inline stub"; + case AsmJSModule::CodeRange::Inline: return "inline stub (in asm.js)"; case AsmJSModule::CodeRange::Thunk: return BuiltinToName(codeRange->thunkTarget()); } MOZ_ASSUME_UNREACHABLE("Bad exit kind"); }
--- a/js/src/jit/AsmJSFrameIterator.h +++ b/js/src/jit/AsmJSFrameIterator.h @@ -110,61 +110,51 @@ namespace AsmJSExit // Iterates over the frames of a single AsmJSActivation, given an // asynchrously-interrupted thread's state. If the activation's // module is not in profiling mode, the activation is skipped. class AsmJSProfilingFrameIterator { const AsmJSModule *module_; uint8_t *callerFP_; void *callerPC_; + void *stackAddress_; AsmJSExit::Reason exitReason_; // Really, a const AsmJSModule::CodeRange*, but no forward declarations of // nested classes, so use void* to avoid pulling in all of AsmJSModule.h. const void *codeRange_; void initFromFP(const AsmJSActivation &activation); public: AsmJSProfilingFrameIterator() : codeRange_(nullptr) {} AsmJSProfilingFrameIterator(const AsmJSActivation &activation); AsmJSProfilingFrameIterator(const AsmJSActivation &activation, const JS::ProfilingFrameIterator::RegisterState &state); void operator++(); bool done() const { return !codeRange_; } - typedef JS::ProfilingFrameIterator::Kind Kind; - Kind kind() const; - - JSAtom *functionDisplayAtom() const; - const char *functionFilename() const; - unsigned functionLine() const; - - const char *nonFunctionDescription() const; + void *stackAddress() const { JS_ASSERT(!done()); return stackAddress_; } + const char *label() const; }; /******************************************************************************/ // Prologue/epilogue code generation. void GenerateAsmJSFunctionPrologue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSFunctionLabels *labels); void GenerateAsmJSFunctionEpilogue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSFunctionLabels *labels); void GenerateAsmJSStackOverflowExit(jit::MacroAssembler &masm, jit::Label *overflowExit, jit::Label *throwLabel); void -GenerateAsmJSEntryPrologue(jit::MacroAssembler &masm, jit::Label *begin); -void -GenerateAsmJSEntryEpilogue(jit::MacroAssembler &masm); - -void GenerateAsmJSExitPrologue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, jit::Label *begin); void GenerateAsmJSExitEpilogue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, jit::Label *profilingReturn); } // namespace js
--- a/js/src/jit/AsmJSModule.cpp +++ b/js/src/jit/AsmJSModule.cpp @@ -1168,18 +1168,20 @@ AsmJSModule::ExportedFunction::clone(Exc if (!ClonePodVector(cx, argCoercions_, &out->argCoercions_)) return false; out->pod = pod; return true; } -AsmJSModule::CodeRange::CodeRange(uint32_t nameIndex, const AsmJSFunctionLabels &l) +AsmJSModule::CodeRange::CodeRange(uint32_t nameIndex, uint32_t lineNumber, + const AsmJSFunctionLabels &l) : nameIndex_(nameIndex), + lineNumber_(lineNumber), begin_(l.begin.offset()), profilingReturn_(l.profilingReturn.offset()), end_(l.end.offset()) { u.kind_ = Function; setDeltas(l.entry.offset(), l.profilingJump.offset(), l.profilingEpilogue.offset()); JS_ASSERT(l.begin.offset() < l.entry.offset()); @@ -1530,16 +1532,39 @@ AsmJSModule::clone(JSContext *cx, Scoped void AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx) { JS_ASSERT(isDynamicallyLinked()); if (profilingEnabled_ == enabled) return; + // When enabled, generate profiling labels for every name in names_ that is + // the name of some Function CodeRange. This involves malloc() so do it now + // since, once we start sampling, we'll be in a signal-handing context where + // we cannot malloc. + if (enabled) { + profilingLabels_.resize(names_.length()); + const char *filename = scriptSource_->filename(); + JS::AutoCheckCannotGC nogc; + for (size_t i = 0; i < codeRanges_.length(); i++) { + CodeRange &cr = codeRanges_[i]; + if (!cr.isFunction()) + continue; + unsigned lineno = cr.functionLineNumber(); + PropertyName *name = names_[cr.functionNameIndex()].name(); + profilingLabels_[cr.functionNameIndex()].reset( + name->hasLatin1Chars() + ? JS_smprintf("%s (%s:%u)", name->latin1Chars(nogc), filename, lineno) + : JS_smprintf("%hs (%s:%u)", name->twoByteChars(nogc), filename, lineno)); + } + } else { + profilingLabels_.clear(); + } + // Conservatively flush the icache for the entire module. AutoFlushICache afc("AsmJSModule::setProfilingEnabled"); setAutoFlushICacheRange(); // To enable profiling, we need to patch 3 kinds of things: AutoUnprotectCode auc(cx, *this); // Patch all internal (asm.js->asm.js) callsites to call the profiling
--- a/js/src/jit/AsmJSModule.h +++ b/js/src/jit/AsmJSModule.h @@ -330,16 +330,17 @@ class AsmJSModule uint8_t *serialize(uint8_t *cursor) const; const uint8_t *deserialize(ExclusiveContext *cx, const uint8_t *cursor); bool clone(ExclusiveContext *cx, ExportedFunction *out) const; }; class CodeRange { uint32_t nameIndex_; + uint32_t lineNumber_; uint32_t begin_; uint32_t profilingReturn_; uint32_t end_; union { struct { uint8_t kind_; uint8_t beginToEntry_; uint8_t profilingJumpToProfilingReturn_; @@ -353,17 +354,17 @@ class AsmJSModule } u; void setDeltas(uint32_t entry, uint32_t profilingJump, uint32_t profilingEpilogue); public: enum Kind { Function, Entry, FFI, Interrupt, Thunk, Inline }; CodeRange() {} - CodeRange(uint32_t nameIndex, const AsmJSFunctionLabels &l); + CodeRange(uint32_t nameIndex, uint32_t lineNumber, const AsmJSFunctionLabels &l); CodeRange(Kind kind, uint32_t begin, uint32_t end); CodeRange(Kind kind, uint32_t begin, uint32_t profilingReturn, uint32_t end); CodeRange(AsmJSExit::BuiltinKind builtin, uint32_t begin, uint32_t pret, uint32_t end); void updateOffsets(jit::MacroAssembler &masm); Kind kind() const { return Kind(u.kind_); } bool isFunction() const { return kind() == Function; } bool isEntry() const { return kind() == Entry; } @@ -388,20 +389,32 @@ class AsmJSModule uint32_t profilingEpilogue() const { JS_ASSERT(isFunction()); return profilingReturn_ - u.func.profilingEpilogueToProfilingReturn_; } uint32_t profilingReturn() const { JS_ASSERT(isFunction() || isFFI() || isInterrupt() || isThunk()); return profilingReturn_; } + uint32_t functionNameIndex() const { + JS_ASSERT(isFunction()); + return nameIndex_; + } PropertyName *functionName(const AsmJSModule &module) const { JS_ASSERT(isFunction()); return module.names_[nameIndex_].name(); } + const char *functionProfilingLabel(const AsmJSModule &module) const { + JS_ASSERT(isFunction()); + return module.profilingLabels_[nameIndex_].get(); + } + uint32_t functionLineNumber() const { + JS_ASSERT(isFunction()); + return lineNumber_; + } AsmJSExit::BuiltinKind thunkTarget() const { JS_ASSERT(isThunk()); return AsmJSExit::BuiltinKind(u.thunk.target_); } }; class FuncPtrTable { @@ -425,16 +438,18 @@ class AsmJSModule PropertyName *name() const { return name_; } PropertyName *&name() { return name_; } size_t serializedSize() const; uint8_t *serialize(uint8_t *cursor) const; const uint8_t *deserialize(ExclusiveContext *cx, const uint8_t *cursor); bool clone(ExclusiveContext *cx, Name *out) const; }; + typedef mozilla::UniquePtr<char, JS::FreePolicy> ProfilingLabel; + #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) // Function information to add to the VTune JIT profiler following linking. struct ProfiledFunction { PropertyName *name; struct Pod { unsigned startCodeOffset; unsigned endCodeOffset; @@ -598,16 +613,17 @@ class AsmJSModule Vector<Global, 0, SystemAllocPolicy> globals_; Vector<Exit, 0, SystemAllocPolicy> exits_; Vector<ExportedFunction, 0, SystemAllocPolicy> exports_; Vector<jit::CallSite, 0, SystemAllocPolicy> callSites_; Vector<CodeRange, 0, SystemAllocPolicy> codeRanges_; Vector<FuncPtrTable, 0, SystemAllocPolicy> funcPtrTables_; Vector<uint32_t, 0, SystemAllocPolicy> builtinThunkOffsets_; Vector<Name, 0, SystemAllocPolicy> names_; + Vector<ProfilingLabel, 0, SystemAllocPolicy> profilingLabels_; Vector<jit::AsmJSHeapAccess, 0, SystemAllocPolicy> heapAccesses_; Vector<jit::IonScriptCounts*, 0, SystemAllocPolicy> functionCounts_; #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) Vector<ProfiledFunction, 0, SystemAllocPolicy> profiledFunctions_; #endif #if defined(JS_ION_PERF) Vector<ProfiledBlocksFunction, 0, SystemAllocPolicy> perfProfiledBlocksFunctions_; #endif @@ -796,23 +812,25 @@ class AsmJSModule /*************************************************************************/ // These functions are called while parsing/compiling function bodies: void requireHeapLengthToBeAtLeast(uint32_t len) { JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies()); if (len > pod.minHeapLength_) pod.minHeapLength_ = len; } - bool addFunctionCodeRange(PropertyName *name, const AsmJSFunctionLabels &labels) { + bool addFunctionCodeRange(PropertyName *name, uint32_t lineNumber, + const AsmJSFunctionLabels &labels) + { JS_ASSERT(!isFinished()); JS_ASSERT(name->isTenured()); if (names_.length() >= UINT32_MAX) return false; uint32_t nameIndex = names_.length(); - return names_.append(name) && codeRanges_.append(CodeRange(nameIndex, labels)); + return names_.append(name) && codeRanges_.append(CodeRange(nameIndex, lineNumber, labels)); } bool addEntryCodeRange(uint32_t begin, uint32_t end) { return codeRanges_.append(CodeRange(CodeRange::Entry, begin, end)); } bool addFFICodeRange(uint32_t begin, uint32_t pret, uint32_t end) { return codeRanges_.append(CodeRange(CodeRange::FFI, begin, pret, end)); } bool addInterruptCodeRange(uint32_t begin, uint32_t pret, uint32_t end) {
--- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -1285,37 +1285,37 @@ OptimizeMIR(MIRGenerator *mir) } IonSpewPass("BuildSSA"); AssertBasicGraphCoherency(graph); if (mir->shouldCancel("Start")) return false; + if (!mir->compilingAsmJS()) { + AutoTraceLog log(logger, TraceLogger::FoldTests); + FoldTests(graph); + IonSpewPass("Fold Tests"); + AssertBasicGraphCoherency(graph); + + if (mir->shouldCancel("Fold Tests")) + return false; + } + { AutoTraceLog log(logger, TraceLogger::SplitCriticalEdges); if (!SplitCriticalEdges(graph)) return false; IonSpewPass("Split Critical Edges"); AssertGraphCoherency(graph); if (mir->shouldCancel("Split Critical Edges")) return false; } - if (!mir->compilingAsmJS()) { - AutoTraceLog log(logger, TraceLogger::FoldTests); - FoldTests(graph); - IonSpewPass("Fold Tests"); - AssertBasicGraphCoherency(graph); - - if (mir->shouldCancel("Fold Tests")) - return false; - } - { AutoTraceLog log(logger, TraceLogger::RenumberBlocks); if (!RenumberBlocks(graph)) return false; IonSpewPass("Renumber Blocks"); AssertGraphCoherency(graph); if (mir->shouldCancel("Renumber Blocks"))
--- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -199,16 +199,23 @@ MaybeFoldConditionBlock(MIRGraph &graph, if (falseBranch->numPredecessors() != 1 || falseBranch->numSuccessors() != 1) return; MBasicBlock *testBlock = trueBranch->getSuccessor(0); if (testBlock != falseBranch->getSuccessor(0)) return; if (testBlock->numPredecessors() != 2) return; + if (initialBlock->isLoopBackedge() || trueBranch->isLoopBackedge() || falseBranch->isLoopBackedge()) + return; + + // Make sure the test block does not have any outgoing loop backedges. + if (!SplitCriticalEdgesForBlock(graph, testBlock)) + CrashAtUnhandlableOOM("MaybeFoldConditionBlock"); + MPhi *phi; MTest *finalTest; if (!BlockIsSingleTest(testBlock, &phi, &finalTest)) return; if (&testBlock->info() != &initialBlock->info() || &trueBranch->info() != &initialBlock->info() || &falseBranch->info() != &initialBlock->info()) @@ -268,24 +275,16 @@ MaybeFoldConditionBlock(MIRGraph &graph, // Short circuit the initial test to skip any constant branch eliminated above. UpdateTestSuccessors(graph.alloc(), initialBlock, initialTest->input(), trueTarget, falseTarget, testBlock); // Remove testBlock itself. finalTest->ifTrue()->removePredecessor(testBlock); finalTest->ifFalse()->removePredecessor(testBlock); graph.removeBlock(testBlock); - - // Split any new critical edges which were introduced. - if (!SplitCriticalEdgesForBlock(graph, initialBlock) || - (trueTarget == trueBranch && !SplitCriticalEdgesForBlock(graph, trueBranch)) || - (falseTarget == falseBranch && !SplitCriticalEdgesForBlock(graph, falseBranch))) - { - CrashAtUnhandlableOOM("MaybeFoldConditionBlock"); - } } static void MaybeFoldAndOrBlock(MIRGraph &graph, MBasicBlock *initialBlock) { // Optimize the MIR graph to improve the code generated for && and || // operations when they are used in tests. This is very similar to the // above method for folding condition blocks, though the two are @@ -318,16 +317,23 @@ MaybeFoldAndOrBlock(MIRGraph &graph, MBa testBlock = initialTest->ifTrue(); } if (branchBlock->numSuccessors() != 1 || branchBlock->getSuccessor(0) != testBlock) return; if (branchBlock->numPredecessors() != 1 || testBlock->numPredecessors() != 2) return; + if (initialBlock->isLoopBackedge() || branchBlock->isLoopBackedge()) + return; + + // Make sure the test block does not have any outgoing loop backedges. + if (!SplitCriticalEdgesForBlock(graph, testBlock)) + CrashAtUnhandlableOOM("MaybeFoldAndOrBlock"); + MPhi *phi; MTest *finalTest; if (!BlockIsSingleTest(testBlock, &phi, &finalTest)) return; if (&testBlock->info() != &initialBlock->info() || &branchBlock->info() != &initialBlock->info()) return; @@ -361,23 +367,16 @@ MaybeFoldAndOrBlock(MIRGraph &graph, MBa UpdateTestSuccessors(graph.alloc(), branchBlock, branchResult, finalTest->ifTrue(), finalTest->ifFalse(), testBlock); // Remove testBlock itself. finalTest->ifTrue()->removePredecessor(testBlock); finalTest->ifFalse()->removePredecessor(testBlock); graph.removeBlock(testBlock); - - // Split any new critical edges which were introduced. - if (!SplitCriticalEdgesForBlock(graph, initialBlock) || - !SplitCriticalEdgesForBlock(graph, branchBlock)) - { - CrashAtUnhandlableOOM("MaybeFoldConditionBlock"); - } } void jit::FoldTests(MIRGraph &graph) { for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { MaybeFoldConditionBlock(graph, *block); MaybeFoldAndOrBlock(graph, *block);
--- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -1496,16 +1496,20 @@ MBinaryArithInstruction::foldsTo(TempAll return rhs; // x op id => x return this; } void MBinaryArithInstruction::trySpecializeFloat32(TempAllocator &alloc) { + // Do not use Float32 if we can use int32. + if (specialization_ == MIRType_Int32) + return; + MDefinition *left = lhs(); MDefinition *right = rhs(); if (!left->canProduceFloat32() || !right->canProduceFloat32() || !CheckUsesAreFloat32Consumers(this)) { if (left->type() == MIRType_Float32) ConvertDefinitionToDouble<0>(alloc, left, this); @@ -1522,16 +1526,20 @@ bool MAbs::fallible() const { return !implicitTruncate_ && (!range() || !range()->hasInt32Bounds()); } void MAbs::trySpecializeFloat32(TempAllocator &alloc) { + // Do not use Float32 if we can use int32. + if (input()->type() == MIRType_Int32) + return; + if (!input()->canProduceFloat32() || !CheckUsesAreFloat32Consumers(this)) { if (input()->type() == MIRType_Float32) ConvertDefinitionToDouble<0>(alloc, input(), this); return; } setResultType(MIRType_Float32); specialization_ = MIRType_Float32;
--- a/js/src/jit/arm/Assembler-arm.cpp +++ b/js/src/jit/arm/Assembler-arm.cpp @@ -72,18 +72,21 @@ ABIArgGenerator::next(MIRType type) floatRegIndex_+=2; break; default: MOZ_ASSUME_UNREACHABLE("Unexpected argument type"); } return current_; } -const Register ABIArgGenerator::NonArgReturnVolatileReg0 = r4; -const Register ABIArgGenerator::NonArgReturnVolatileReg1 = r5; + +const Register ABIArgGenerator::NonArgReturnReg0 = r4; +const Register ABIArgGenerator::NonArgReturnReg1 = r5; +const Register ABIArgGenerator::NonReturn_VolatileReg0 = r2; +const Register ABIArgGenerator::NonReturn_VolatileReg1 = r3; // Encode a standard register when it is being used as src1, the dest, and an // extra register. These should never be called with an InvalidReg. uint32_t js::jit::RT(Register r) { JS_ASSERT((r.code() & ~0xf) == 0); return r.code() << 12;
--- a/js/src/jit/arm/Assembler-arm.h +++ b/js/src/jit/arm/Assembler-arm.h @@ -58,30 +58,33 @@ static MOZ_CONSTEXPR_VAR Register IntArg static MOZ_CONSTEXPR_VAR Register IntArgReg1 = r1; static MOZ_CONSTEXPR_VAR Register IntArgReg2 = r2; static MOZ_CONSTEXPR_VAR Register IntArgReg3 = r3; static MOZ_CONSTEXPR_VAR Register GlobalReg = r10; static MOZ_CONSTEXPR_VAR Register HeapReg = r11; static MOZ_CONSTEXPR_VAR Register CallTempNonArgRegs[] = { r5, r6, r7, r8 }; static const uint32_t NumCallTempNonArgRegs = mozilla::ArrayLength(CallTempNonArgRegs); + class ABIArgGenerator { unsigned intRegIndex_; unsigned floatRegIndex_; uint32_t stackOffset_; ABIArg current_; public: ABIArgGenerator(); ABIArg next(MIRType argType); ABIArg ¤t() { return current_; } uint32_t stackBytesConsumedSoFar() const { return stackOffset_; } - static const Register NonArgReturnVolatileReg0; - static const Register NonArgReturnVolatileReg1; + static const Register NonArgReturnReg0; + static const Register NonArgReturnReg1; + static const Register NonReturn_VolatileReg0; + static const Register NonReturn_VolatileReg1; }; static MOZ_CONSTEXPR_VAR Register PreBarrierReg = r1; static MOZ_CONSTEXPR_VAR Register InvalidReg = { Registers::invalid_reg }; static MOZ_CONSTEXPR_VAR FloatRegister InvalidFloatReg; static MOZ_CONSTEXPR_VAR Register JSReturnReg_Type = r3;
--- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -2453,16 +2453,23 @@ MacroAssemblerARMCompat::store32(Registe if (dest.offset != 0) { ma_add(base, Imm32(dest.offset), ScratchRegister); base = ScratchRegister; } ma_str(src, DTRAddr(base, DtrRegImmShift(dest.index, LSL, scale))); } void +MacroAssemblerARMCompat::store32_NoSecondScratch(Imm32 src, const Address &address) +{ + move32(src, ScratchRegister); + storePtr(ScratchRegister, address); +} + +void MacroAssemblerARMCompat::storePtr(ImmWord imm, const Address &address) { movePtr(imm, ScratchRegister); storePtr(ScratchRegister, address); } void MacroAssemblerARMCompat::storePtr(ImmPtr imm, const Address &address)
--- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1353,16 +1353,18 @@ class MacroAssemblerARMCompat : public M void store16(Imm32 imm, const BaseIndex &address); void store32(Register src, AbsoluteAddress address); void store32(Register src, const Address &address); void store32(Register src, const BaseIndex &address); void store32(Imm32 src, const Address &address); void store32(Imm32 src, const BaseIndex &address); + void store32_NoSecondScratch(Imm32 src, const Address &address); + void storePtr(ImmWord imm, const Address &address); void storePtr(ImmPtr imm, const Address &address); void storePtr(ImmGCPtr imm, const Address &address); void storePtr(Register src, const Address &address); void storePtr(Register src, const BaseIndex &address); void storePtr(Register src, AbsoluteAddress dest); void storeDouble(FloatRegister src, Address addr) { ma_vstr(src, Operand(addr));
--- a/js/src/jit/mips/Assembler-mips.cpp +++ b/js/src/jit/mips/Assembler-mips.cpp @@ -59,18 +59,21 @@ ABIArgGenerator::next(MIRType type) } break; default: MOZ_ASSUME_UNREACHABLE("Unexpected argument type"); } return current_; } -const Register ABIArgGenerator::NonArgReturnVolatileReg0 = t0; -const Register ABIArgGenerator::NonArgReturnVolatileReg1 = t1; +const Register ABIArgGenerator::NonArgReturnReg0 = t0; +const Register ABIArgGenerator::NonArgReturnReg1 = t1; +const Register ABIArgGenerator::NonArg_VolatileReg = v0; +const Register ABIArgGenerator::NonReturn_VolatileReg0 = a0; +const Register ABIArgGenerator::NonReturn_VolatileReg1 = a1; // Encode a standard register when it is being used as rd, the rs, and // an extra register(rt). These should never be called with an InvalidReg. uint32_t js::jit::RS(Register r) { MOZ_ASSERT((r.code() & ~RegMask) == 0); return r.code() << RSShift;
--- a/js/src/jit/mips/Assembler-mips.h +++ b/js/src/jit/mips/Assembler-mips.h @@ -89,18 +89,21 @@ class ABIArgGenerator uint32_t stackBytesConsumedSoFar() const { if (usedArgSlots_ <= 4) return ShadowStackSpace; return usedArgSlots_ * sizeof(intptr_t); } - static const Register NonArgReturnVolatileReg0; - static const Register NonArgReturnVolatileReg1; + static const Register NonArgReturnReg0; + static const Register NonArgReturnReg1; + static const Register NonArg_VolatileReg; + static const Register NonReturn_VolatileReg0; + static const Register NonReturn_VolatileReg1; }; static MOZ_CONSTEXPR_VAR Register PreBarrierReg = a1; static MOZ_CONSTEXPR_VAR Register InvalidReg = { Registers::invalid_reg }; static MOZ_CONSTEXPR_VAR FloatRegister InvalidFloatReg = { FloatRegisters::invalid_freg }; static MOZ_CONSTEXPR_VAR Register JSReturnReg_Type = a3;
--- a/js/src/jit/shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/shared/MacroAssembler-x86-shared.h @@ -378,16 +378,20 @@ class MacroAssemblerX86Shared : public A } void load32(const Operand &src, Register dest) { movl(src, dest); } template <typename S, typename T> void store32(const S &src, const T &dest) { movl(src, Operand(dest)); } + template <typename S, typename T> + void store32_NoSecondScratch(const S &src, const T &dest) { + store32(src, dest); + } void loadDouble(const Address &src, FloatRegister dest) { movsd(src, dest); } void loadDouble(const BaseIndex &src, FloatRegister dest) { movsd(src, dest); } void loadDouble(const Operand &src, FloatRegister dest) { switch (src.kind()) {
--- a/js/src/jit/x64/Assembler-x64.cpp +++ b/js/src/jit/x64/Assembler-x64.cpp @@ -70,19 +70,21 @@ ABIArgGenerator::next(MIRType type) default: MOZ_ASSUME_UNREACHABLE("Unexpected argument type"); } return current_; #endif } // Avoid r11, which is the MacroAssembler's ScratchReg. -const Register ABIArgGenerator::NonArgReturnVolatileReg0 = r10; -const Register ABIArgGenerator::NonArgReturnVolatileReg1 = r12; +const Register ABIArgGenerator::NonArgReturnReg0 = r10; +const Register ABIArgGenerator::NonArgReturnReg1 = r12; const Register ABIArgGenerator::NonVolatileReg = r13; +const Register ABIArgGenerator::NonArg_VolatileReg = rax; +const Register ABIArgGenerator::NonReturn_VolatileReg0 = rcx; void Assembler::writeRelocation(JmpSrc src, Relocation::Kind reloc) { if (!jumpRelocations_.length()) { // The jump relocation table starts with a fixed-width integer pointing // to the start of the extended jump table. But, we don't know the // actual extended jump table offset yet, so write a 0 which we'll
--- a/js/src/jit/x64/Assembler-x64.h +++ b/js/src/jit/x64/Assembler-x64.h @@ -165,19 +165,21 @@ class ABIArgGenerator public: ABIArgGenerator(); ABIArg next(MIRType argType); ABIArg ¤t() { return current_; } uint32_t stackBytesConsumedSoFar() const { return stackOffset_; } // Note: these registers are all guaranteed to be different - static const Register NonArgReturnVolatileReg0; - static const Register NonArgReturnVolatileReg1; + static const Register NonArgReturnReg0; + static const Register NonArgReturnReg1; static const Register NonVolatileReg; + static const Register NonArg_VolatileReg; + static const Register NonReturn_VolatileReg0; }; static MOZ_CONSTEXPR_VAR Register OsrFrameReg = IntArgReg3; static MOZ_CONSTEXPR_VAR Register PreBarrierReg = rdx; // GCC stack is aligned on 16 bytes, but we don't maintain the invariant in // jitted code.
--- a/js/src/jit/x86/Assembler-x86.cpp +++ b/js/src/jit/x86/Assembler-x86.cpp @@ -30,19 +30,21 @@ ABIArgGenerator::next(MIRType type) stackOffset_ += sizeof(uint64_t); break; default: MOZ_ASSUME_UNREACHABLE("Unexpected argument type"); } return current_; } -const Register ABIArgGenerator::NonArgReturnVolatileReg0 = ecx; -const Register ABIArgGenerator::NonArgReturnVolatileReg1 = edx; +const Register ABIArgGenerator::NonArgReturnReg0 = ecx; +const Register ABIArgGenerator::NonArgReturnReg1 = edx; const Register ABIArgGenerator::NonVolatileReg = ebx; +const Register ABIArgGenerator::NonArg_VolatileReg = eax; +const Register ABIArgGenerator::NonReturn_VolatileReg0 = ecx; void Assembler::executableCopy(uint8_t *buffer) { AssemblerX86Shared::executableCopy(buffer); for (size_t i = 0; i < jumps_.length(); i++) { RelativePatch &rp = jumps_[i];
--- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -76,19 +76,21 @@ class ABIArgGenerator public: ABIArgGenerator(); ABIArg next(MIRType argType); ABIArg ¤t() { return current_; } uint32_t stackBytesConsumedSoFar() const { return stackOffset_; } // Note: these registers are all guaranteed to be different - static const Register NonArgReturnVolatileReg0; - static const Register NonArgReturnVolatileReg1; + static const Register NonArgReturnReg0; + static const Register NonArgReturnReg1; static const Register NonVolatileReg; + static const Register NonArg_VolatileReg; + static const Register NonReturn_VolatileReg0; }; static MOZ_CONSTEXPR_VAR Register OsrFrameReg = edx; static MOZ_CONSTEXPR_VAR Register PreBarrierReg = edx; // Registers used in the GenerateFFIIonExit Enable Activation block. static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegCallee = ecx; static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegE0 = edi;
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4262,38 +4262,24 @@ SingleStepCallback(void *arg, jit::Simul { JSRuntime *rt = reinterpret_cast<JSRuntime*>(arg); JS::ProfilingFrameIterator::RegisterState state; state.pc = pc; state.sp = (void*)sim->get_register(jit::Simulator::sp); state.lr = (void*)sim->get_register(jit::Simulator::lr); + DebugOnly<void*> lastStackAddress = nullptr; StackChars stack; for (JS::ProfilingFrameIterator i(rt, state); !i.done(); ++i) { - switch (i.kind()) { - case JS::ProfilingFrameIterator::Function: { - JS::AutoCheckCannotGC nogc; - JSAtom *atom = i.functionDisplayAtom(); - if (atom->hasLatin1Chars()) - stack.append(atom->latin1Chars(nogc), atom->length()); - else - stack.append(atom->twoByteChars(nogc), atom->length()); - break; - } - case JS::ProfilingFrameIterator::AsmJSTrampoline: { - stack.append('*'); - break; - } - case JS::ProfilingFrameIterator::CppFunction: { - const char *desc = i.nonFunctionDescription(); - stack.append(desc, strlen(desc)); - break; - } - } + JS_ASSERT(i.stackAddress() != nullptr); + JS_ASSERT(lastStackAddress <= i.stackAddress()); + lastStackAddress = i.stackAddress(); + const char *label = i.label(); + stack.append(label, strlen(label)); } // Only append the stack if it differs from the last stack. if (stacks.empty() || stacks.back().length() != stack.length() || !PodEqual(stacks.back().begin(), stack.begin(), stack.length())) { stacks.append(Move(stack));
--- a/js/src/vm/SPSProfiler.cpp +++ b/js/src/vm/SPSProfiler.cpp @@ -196,28 +196,28 @@ SPSProfiler::exit(JSScript *script, JSFu JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0); stack_[*size_].setLabel(nullptr); stack_[*size_].setPC(nullptr); } #endif } void -SPSProfiler::enterNative(const char *string, void *sp) +SPSProfiler::enterAsmJS(const char *string, void *sp) { /* these operations cannot be re-ordered, so volatile-ize operations */ volatile ProfileEntry *stack = stack_; volatile uint32_t *size = size_; uint32_t current = *size; JS_ASSERT(enabled()); if (current < max_) { stack[current].setLabel(string); stack[current].setCppFrame(sp, 0); - JS_ASSERT(stack[current].flags() == js::ProfileEntry::IS_CPP_ENTRY); + stack[current].setFlag(ProfileEntry::ASMJS); } *size = current + 1; } void SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc, bool copy) { JS_ASSERT_IF(sp != nullptr, script == nullptr && pc == nullptr); @@ -318,17 +318,17 @@ SPSEntryMarker::SPSEntryMarker(JSRuntime : profiler(&rt->spsProfiler) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (!profiler->installed()) { profiler = nullptr; return; } size_before = *profiler->size_; - profiler->push("js::RunScript", nullptr, script, script->code(), /* copy = */ false); + profiler->push("js::RunScript", this, nullptr, nullptr, /* copy = */ false); } SPSEntryMarker::~SPSEntryMarker() { if (profiler != nullptr) { profiler->pop(); JS_ASSERT(size_before == *profiler->size_); }
--- a/js/src/vm/SPSProfiler.h +++ b/js/src/vm/SPSProfiler.h @@ -174,19 +174,19 @@ class SPSProfiler void updatePC(JSScript *script, jsbytecode *pc) { if (enabled() && *size_ - 1 < max_) { JS_ASSERT(*size_ > 0); JS_ASSERT(stack_[*size_ - 1].script() == script); stack_[*size_ - 1].setPC(pc); } } - /* Enter a C++ function. */ - void enterNative(const char *string, void *sp); - void exitNative() { pop(); } + /* Enter asm.js code */ + void enterAsmJS(const char *string, void *sp); + void exitAsmJS() { pop(); } jsbytecode *ipToPC(JSScript *script, size_t ip) { return nullptr; } void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max); void setEventMarker(void (*fn)(const char *)); const char *profileString(JSScript *script, JSFunction *maybeFun); void onScriptFinalized(JSScript *script);
--- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1691,34 +1691,34 @@ AsmJSActivation::AsmJSActivation(JSConte exitReason_(AsmJSExit::None) { if (cx->runtime()->spsProfiler.enabled()) { // Use a profiler string that matches jsMatch regex in // browser/devtools/profiler/cleopatra/js/parserWorker.js. // (For now use a single static string to avoid further slowing down // calls into asm.js.) profiler_ = &cx->runtime()->spsProfiler; - profiler_->enterNative("asm.js code :0", this); + profiler_->enterAsmJS("asm.js code :0", this); } prevAsmJSForModule_ = module.activation(); module.activation() = this; prevAsmJS_ = cx->mainThread().asmJSActivationStack_; JSRuntime::AutoLockForInterrupt lock(cx->runtime()); cx->mainThread().asmJSActivationStack_ = this; (void) errorRejoinSP_; // squelch GCC warning } AsmJSActivation::~AsmJSActivation() { if (profiler_) - profiler_->exitNative(); + profiler_->exitAsmJS(); JS_ASSERT(fp_ == nullptr); JS_ASSERT(module_.activation() == this); module_.activation() = prevAsmJSForModule_; JSContext *cx = cx_->asJSContext(); JS_ASSERT(cx->mainThread().asmJSActivationStack_ == this); @@ -1827,51 +1827,28 @@ JS::ProfilingFrameIterator::settle() return; new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_); } #else MOZ_CRASH("Shouldn't have any frames"); #endif } -JS::ProfilingFrameIterator::Kind -JS::ProfilingFrameIterator::kind() const +void * +JS::ProfilingFrameIterator::stackAddress() const { #ifdef JS_ION - return iter().kind(); -#else - MOZ_CRASH("Shouldn't have any frames"); -#endif -} - -JSAtom * -JS::ProfilingFrameIterator::functionDisplayAtom() const -{ -#ifdef JS_ION - JS_ASSERT(kind() == Function); - return iter().functionDisplayAtom(); + return iter().stackAddress(); #else MOZ_CRASH("Shouldn't have any frames"); #endif } const char * -JS::ProfilingFrameIterator::functionFilename() const +JS::ProfilingFrameIterator::label() const { #ifdef JS_ION - JS_ASSERT(kind() == Function); - return iter().functionFilename(); + return iter().label(); #else MOZ_CRASH("Shouldn't have any frames"); #endif } -const char * -JS::ProfilingFrameIterator::nonFunctionDescription() const -{ -#ifdef JS_ION - JS_ASSERT(kind() != Function); - return iter().nonFunctionDescription(); -#else - MOZ_CRASH("Shouldn't have any frames"); -#endif -} -
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp +++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp @@ -605,16 +605,18 @@ XPCWrappedNativeScope::SweepAllWrappedNa // static void XPCWrappedNativeScope::KillDyingScopes() { XPCWrappedNativeScope* cur = gDyingScopes; while (cur) { XPCWrappedNativeScope* next = cur->mNext; + if (cur->mGlobalJSObject) + CompartmentPrivate::Get(cur->mGlobalJSObject)->scope = nullptr; delete cur; cur = next; } gDyingScopes = nullptr; } struct ShutdownData {
--- a/js/xpconnect/src/nsXPConnect.cpp +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -293,20 +293,27 @@ xpc_TryUnmarkWrappedGrayObject(nsISuppor template<typename T> static inline T UnexpectedFailure(T rv) { NS_ERROR("This is not supposed to fail!"); return rv; } void -TraceXPCGlobal(JSTracer *trc, JSObject *obj) +xpc::TraceXPCGlobal(JSTracer *trc, JSObject *obj) { if (js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL) mozilla::dom::TraceProtoAndIfaceCache(trc, obj); + + // We might be called from a GC during the creation of a global, before we've + // been able to set up the compartment private or the XPCWrappedNativeScope, + // so we need to null-check those. + xpc::CompartmentPrivate* compartmentPrivate = xpc::CompartmentPrivate::Get(obj); + if (compartmentPrivate && compartmentPrivate->scope) + compartmentPrivate->scope->TraceInside(trc); } namespace xpc { JSObject* CreateGlobalObject(JSContext *cx, const JSClass *clasp, nsIPrincipal *principal, JS::CompartmentOptions& aOptions) {