зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to b2g-inbound
This commit is contained in:
Коммит
6c6b839f32
|
@ -223,7 +223,7 @@ let OutputGenerator = {
|
|||
},
|
||||
|
||||
_getOutputName: function _getOutputName(aName) {
|
||||
return aName.replace(' ', '');
|
||||
return aName.replace(/\s/g, '');
|
||||
},
|
||||
|
||||
roleRuleMap: {
|
||||
|
|
|
@ -848,7 +848,8 @@ PivotContext.prototype = {
|
|||
hints.push(hint);
|
||||
} else if (aAccessible.actionCount > 0) {
|
||||
hints.push({
|
||||
string: Utils.AccRetrieval.getStringRole(aAccessible.role) + '-hint'
|
||||
string: Utils.AccRetrieval.getStringRole(
|
||||
aAccessible.role).replace(/\s/g, '') + '-hint'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -43,6 +43,9 @@
|
|||
accOrElmOrID: 'nested_link3',
|
||||
expectedHints: [{string: 'link-hint'}, {string: 'pushbutton-hint'},
|
||||
"Double tap and hold to activate"]
|
||||
}, {
|
||||
accOrElmOrID: 'menuitemradio',
|
||||
expectedHints: [{string: 'radiomenuitem-hint'}]
|
||||
}];
|
||||
|
||||
// Test hints.
|
||||
|
@ -80,6 +83,7 @@
|
|||
<a href="#" id="link_with_hint_override" aria-moz-hint="Tap and hold to get to menu">I am a special link</a>
|
||||
<button id="button_with_default_hint">Toggle</button>
|
||||
<button id="button_with_hint_override" aria-moz-hint="Tap and hold to activate">Special</button>
|
||||
<span id="menuitemradio" role="menuitemradio">Item 1</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "iojs"
|
||||
- "0.10"
|
||||
|
||||
notifications:
|
||||
|
|
|
@ -17,12 +17,14 @@ If you have code that you'd like to contribute the Jetpack project, follow these
|
|||
3. Make your changes, per the Overview
|
||||
4. Write a test ([intro][test intro], [API][test API])
|
||||
5. Submit pull request with changes and a title in a form of `Bug XXX - description`
|
||||
6. Copy the pull request link from GitHub and paste it in as an attachment to the bug
|
||||
7. Each pull request should idealy contain only one commit, so squash the commits if necessary.
|
||||
8. Flag the attachment for code review from one of the Jetpack reviewers listed below.
|
||||
6. Make sure that [Travis CI](https://travis-ci.org/mozilla/addon-sdk/branches) tests are passing for your branch.
|
||||
7. Copy the pull request link from GitHub and paste it in as an attachment to the bug
|
||||
8. Each pull request should idealy contain only one commit, so squash the commits if necessary.
|
||||
9. Flag the attachment for code review from one of the Jetpack reviewers listed below.
|
||||
This step is optional, but could speed things up.
|
||||
9. Address any nits (ie style changes), or other issues mentioned in the review.
|
||||
10. Finally, once review is approved, a team member will do the merging
|
||||
10. Address any nits (ie style changes), or other issues mentioned in the review.
|
||||
|
||||
Finally, once review is approved, a team member will do the merging
|
||||
|
||||
## Good First Bugs
|
||||
|
||||
|
|
|
@ -81,14 +81,12 @@ function startup(reason, options) Startup.onceInitialized.then(() => {
|
|||
// Exports data to a pseudo module so that api-utils/l10n/core
|
||||
// can get access to it
|
||||
definePseudo(options.loader, '@l10n/data', data ? data : null);
|
||||
return ready;
|
||||
}).then(function() {
|
||||
run(options);
|
||||
return ready.then(() => run(options, !!data));
|
||||
}).then(null, console.exception);
|
||||
return void 0; // otherwise we raise a warning, see bug 910304
|
||||
});
|
||||
|
||||
function run(options) {
|
||||
function run(options, hasL10n) {
|
||||
try {
|
||||
// Try initializing HTML localization before running main module. Just print
|
||||
// an exception in case of error, instead of preventing addon to be run.
|
||||
|
@ -96,7 +94,7 @@ function run(options) {
|
|||
// Do not enable HTML localization while running test as it is hard to
|
||||
// disable. Because unit tests are evaluated in a another Loader who
|
||||
// doesn't have access to this current loader.
|
||||
if (options.main !== 'sdk/test/runner') {
|
||||
if (hasL10n && options.main !== 'sdk/test/runner') {
|
||||
require('../l10n/html').enable();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,7 +340,7 @@ function getPotentialLeaks() {
|
|||
let item = {
|
||||
path: matches[1],
|
||||
principal: details[1],
|
||||
location: details[2] ? details[2].replace("\\", "/", "g") : undefined,
|
||||
location: details[2] ? details[2].replace(/\\/g, "/") : undefined,
|
||||
source: details[3] ? details[3].split(" -> ").reverse() : undefined,
|
||||
toString: function() this.location
|
||||
};
|
||||
|
@ -364,8 +364,8 @@ function getPotentialLeaks() {
|
|||
|
||||
let item = {
|
||||
path: matches[1],
|
||||
location: details[1].replace("\\", "/", "g"),
|
||||
source: [details[1].replace("\\", "/", "g")],
|
||||
location: details[1].replace(/\\/g, "/"),
|
||||
source: [details[1].replace(/\\/g, "/")],
|
||||
toString: function() this.location
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ const { emit } = require("../event/core");
|
|||
const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
|
||||
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
||||
const { Class } = require("../core/heritage");
|
||||
const { Cu } = require("chrome");
|
||||
|
||||
// Event emitter objects used to register listeners and emit events on them
|
||||
// when they occur.
|
||||
|
@ -42,6 +43,9 @@ const Observer = Class({
|
|||
* Keyboard event being emitted.
|
||||
*/
|
||||
handleEvent(event) {
|
||||
// Ignore events from windows in the child process as they can't be top-level
|
||||
if (Cu.isCrossProcessWrapper(event.target))
|
||||
return;
|
||||
emit(this, event.type, event.target, event);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -703,7 +703,8 @@ Loader.main = main;
|
|||
const Module = iced(function Module(id, uri) {
|
||||
return create(null, {
|
||||
id: { enumerable: true, value: id },
|
||||
exports: { enumerable: true, writable: true, value: create(null) },
|
||||
exports: { enumerable: true, writable: true, value: create(null),
|
||||
configurable: true },
|
||||
uri: { value: uri }
|
||||
});
|
||||
});
|
||||
|
@ -784,6 +785,7 @@ function Loader(options) {
|
|||
}
|
||||
}, modules);
|
||||
|
||||
const builtinModuleExports = modules;
|
||||
modules = keys(modules).reduce(function(result, id) {
|
||||
// We resolve `uri` from `id` since modules are cached by `uri`.
|
||||
let uri = resolveURI(id, mapping);
|
||||
|
@ -792,7 +794,16 @@ function Loader(options) {
|
|||
if (isNative && !uri)
|
||||
uri = id;
|
||||
let module = Module(id, uri);
|
||||
module.exports = freeze(modules[id]);
|
||||
|
||||
// Lazily expose built-in modules in order to
|
||||
// allow them to be loaded lazily.
|
||||
Object.defineProperty(module, "exports", {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return builtinModuleExports[id];
|
||||
}
|
||||
});
|
||||
|
||||
result[uri] = freeze(module);
|
||||
return result;
|
||||
}, {});
|
||||
|
|
|
@ -60,6 +60,7 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
|
|||
'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
|
||||
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
|
||||
'browser.selfsupport.url': 'http://localhost/repair-dummy',
|
||||
'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ const { fromIterator } = require('sdk/util/array');
|
|||
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
|
||||
'menu_socialSidebar',
|
||||
'menu_historySidebar',
|
||||
'menu_bookmarksSidebar'
|
||||
'menu_bookmarksSidebar',
|
||||
'menu_readingListSidebar'
|
||||
];
|
||||
|
||||
function isSidebarShowing(window) {
|
||||
|
|
|
@ -129,8 +129,7 @@ exports.testDestroyEdgeCaseBugWithPrivateWindow = function(assert, done) {
|
|||
assert.pass('onShow works for Sidebar');
|
||||
loader.unload();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
exports.useFoo= function () {
|
||||
return require('foo');
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
"browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.reportURL": "http://localhost/safebrowsing-dummy/report",
|
||||
"browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
|
||||
"browser.selfsupport.url": "http://localhost/repair-dummy",
|
||||
"browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.trackingprotection.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",
|
||||
|
|
|
@ -16,7 +16,8 @@ const { fromIterator } = require('sdk/util/array');
|
|||
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
|
||||
'menu_socialSidebar',
|
||||
'menu_historySidebar',
|
||||
'menu_bookmarksSidebar'
|
||||
'menu_bookmarksSidebar',
|
||||
'menu_readingListSidebar'
|
||||
];
|
||||
|
||||
function isSidebarShowing(window) {
|
||||
|
|
|
@ -506,4 +506,22 @@ exports["test Cu.import in b2g style"] = (assert) => {
|
|||
"Loader.Loader is a funciton");
|
||||
};
|
||||
|
||||
exports['test lazy globals'] = function (assert) {
|
||||
let uri = root + '/fixtures/loader/lazy/';
|
||||
let gotFoo = false;
|
||||
let foo = {};
|
||||
let modules = {
|
||||
get foo() {
|
||||
gotFoo = true;
|
||||
return foo;
|
||||
}
|
||||
};
|
||||
let loader = Loader({ paths: { '': uri }, modules: modules});
|
||||
assert.ok(!gotFoo, "foo hasn't been accessed during loader instanciation");
|
||||
let program = main(loader, 'main');
|
||||
assert.ok(!gotFoo, "foo hasn't been accessed during module loading");
|
||||
assert.equal(program.useFoo(), foo, "foo mock works");
|
||||
assert.ok(gotFoo, "foo has been accessed only when we first try to use it");
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
|
|
@ -29,6 +29,17 @@ exports.testGetAndSet = function(assert) {
|
|||
"preferences-service should read from " +
|
||||
"application-wide preferences service");
|
||||
|
||||
// test getting a pref that does not exist,
|
||||
// and where we provide no default
|
||||
assert.equal(
|
||||
prefs.get("test_dne_get_pref", "default"),
|
||||
"default",
|
||||
"default was used for a pref that does not exist");
|
||||
assert.equal(
|
||||
prefs.get("test_dne_get_pref"),
|
||||
undefined,
|
||||
"undefined was returned for a pref that does not exist with no default");
|
||||
|
||||
prefs.set("test_set_get_pref.integer", 1);
|
||||
assert.equal(prefs.get("test_set_get_pref.integer"), 1,
|
||||
"set/get integer preference should work");
|
||||
|
|
|
@ -96,9 +96,12 @@ exports.testSidebarBasicLifeCycle = function*(assert) {
|
|||
sidebar.destroy();
|
||||
sidebar.destroy();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
let id = mi.getAttribute('id');
|
||||
|
||||
if (BUILTIN_SIDEBAR_MENUITEMS.indexOf(id) < 0) {
|
||||
assert.fail('the menuitem "' + id + '" is not a built-in sidebar');
|
||||
}
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
|
||||
|
@ -360,8 +363,7 @@ exports.testSidebarUnload = function*(assert) {
|
|||
|
||||
loader.unload();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
|
@ -598,9 +600,12 @@ exports.testDestroyEdgeCaseBug = function*(assert) {
|
|||
yield sidebar.show();
|
||||
loader.unload();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
let id = mi.getAttribute('id');
|
||||
|
||||
if (BUILTIN_SIDEBAR_MENUITEMS.indexOf(id) < 0) {
|
||||
assert.fail('the menuitem "' + id + '" is not a built-in sidebar');
|
||||
}
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
|
||||
|
@ -762,6 +767,9 @@ exports.testURLSetterToSameValueReloadsSidebar = function*(assert) {
|
|||
document = window.document;
|
||||
assert.pass('new window was opened');
|
||||
|
||||
yield focus(window);
|
||||
assert.pass('new window was focused');
|
||||
|
||||
yield sidebar1.show();
|
||||
|
||||
assert.equal(isShowing(sidebar1), true, 'the sidebar is showing');
|
||||
|
@ -1490,23 +1498,29 @@ exports.testShowHideRawWindowArg = function*(assert) {
|
|||
});
|
||||
|
||||
let mainWindow = getMostRecentBrowserWindow();
|
||||
let newWindow = yield open().then(focus);
|
||||
let newWindow = yield windowPromise(mainWindow.OpenBrowserWindow(), 'load');
|
||||
assert.pass("Created the new window");
|
||||
|
||||
yield focus(newWindow);
|
||||
assert.pass("Focused the new window");
|
||||
|
||||
yield focus(mainWindow);
|
||||
assert.pass("Focused the old window");
|
||||
|
||||
yield sidebar.show(newWindow);
|
||||
|
||||
assert.pass('the sidebar was shown');
|
||||
assert.ok(!isSidebarShowing(mainWindow), 'sidebar is not showing in main window');
|
||||
assert.ok(isSidebarShowing(newWindow), 'sidebar is showing in new window');
|
||||
assert.equal(isSidebarShowing(mainWindow), false, 'sidebar is not showing in main window');
|
||||
assert.equal(isSidebarShowing(newWindow), true, 'sidebar is showing in new window');
|
||||
|
||||
assert.ok(isFocused(mainWindow), 'main window is still focused');
|
||||
|
||||
yield sidebar.hide(newWindow);
|
||||
|
||||
assert.ok(isFocused(mainWindow), 'main window is still focused');
|
||||
assert.ok(!isSidebarShowing(mainWindow), 'sidebar is not showing in main window');
|
||||
assert.ok(!isSidebarShowing(newWindow), 'sidebar is not showing in new window');
|
||||
assert.equal(isFocused(mainWindow), true, 'main window is still focused');
|
||||
assert.equal(isSidebarShowing(mainWindow), false, 'sidebar is not showing in main window');
|
||||
assert.equal(isSidebarShowing(newWindow), false, 'sidebar is not showing in new window');
|
||||
|
||||
sidebar.destroy();
|
||||
}
|
||||
|
||||
|
@ -1547,6 +1561,7 @@ before(exports, (name, assert) => {
|
|||
});
|
||||
|
||||
after(exports, function*(name, assert) {
|
||||
assert.pass("Cleaning new windows and tabs");
|
||||
yield cleanUI();
|
||||
assert.equal(isSidebarShowing(), false, 'no sidebar is showing');
|
||||
});
|
||||
|
|
|
@ -5,10 +5,7 @@
|
|||
|
||||
const { Cu } = require('chrome');
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
|
||||
function gc() {
|
||||
return new Promise(resolve => Cu.schedulePreciseGC(resolve));
|
||||
};
|
||||
const { gc } = require("sdk/test/memory");
|
||||
|
||||
exports['test adding item'] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
|
@ -121,31 +118,31 @@ exports['test adding non object or null item'] = function(assert) {
|
|||
assert.throws(() => {
|
||||
add(items, 'foo');
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, 0);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, undefined);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, null);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, true);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
};
|
||||
|
||||
|
@ -158,31 +155,31 @@ exports['test adding to non object or null item'] = function(assert) {
|
|||
assert.throws(() => {
|
||||
add('foo', item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(0, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(undefined, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(null, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(true, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
};
|
||||
|
||||
|
|
|
@ -1336,7 +1336,7 @@ pref("browser.devedition.theme.showCustomizeButton", false);
|
|||
|
||||
// Developer edition promo preferences
|
||||
pref("devtools.devedition.promo.shown", false);
|
||||
pref("devtools.devedition.promo.url", "https://mozilla.org/firefox/developer");
|
||||
pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer/?utm_source=firefox-dev-tools&utm_medium=firefox-browser&utm_content=betadoorhanger");
|
||||
|
||||
// Only potentially show in beta release
|
||||
#if MOZ_UPDATE_CHANNEL == beta
|
||||
|
@ -1438,6 +1438,9 @@ pref("devtools.timeline.enabled", true);
|
|||
#else
|
||||
pref("devtools.timeline.enabled", false);
|
||||
#endif
|
||||
|
||||
// TODO remove `devtools.timeline.hiddenMarkers.` branches when performance
|
||||
// tool lands (bug 1075567)
|
||||
pref("devtools.timeline.hiddenMarkers", "[]");
|
||||
|
||||
// Enable perftools via build command
|
||||
|
@ -1455,6 +1458,7 @@ pref("devtools.profiler.ui.show-platform-data", false);
|
|||
pref("devtools.profiler.ui.show-idle-blocks", true);
|
||||
|
||||
// The default Performance UI settings
|
||||
pref("devtools.performance.timeline.hidden-markers", "[]");
|
||||
pref("devtools.performance.ui.invert-call-tree", true);
|
||||
pref("devtools.performance.ui.invert-flame-graph", false);
|
||||
pref("devtools.performance.ui.flatten-tree-recursion", true);
|
||||
|
|
|
@ -688,7 +688,6 @@ let SessionStoreInternal = {
|
|||
Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
|
||||
}
|
||||
|
||||
delete browser.__SS_restore_data;
|
||||
delete browser.__SS_data;
|
||||
|
||||
SessionStoreInternal._resetLocalTabRestoringState(tab);
|
||||
|
@ -2683,26 +2682,16 @@ let SessionStoreInternal = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Restores the specified tab. If the tab can't be restored (eg, no history or
|
||||
* calling gotoIndex fails), then state changes will be rolled back.
|
||||
* This method will check if gTabsProgressListener is attached to the tab's
|
||||
* window, ensuring that we don't get caught without one.
|
||||
* This method removes the session history listener right before starting to
|
||||
* attempt a load. This will prevent cases of "stuck" listeners.
|
||||
* If this method returns false, then it is up to the caller to decide what to
|
||||
* do. In the common case (restoreNextTab), we will want to then attempt to
|
||||
* restore the next tab. In the other case (selecting the tab, reloading the
|
||||
* tab), the caller doesn't actually want to do anything if no page is loaded.
|
||||
* Kicks off restoring the given tab.
|
||||
*
|
||||
* @param aTab
|
||||
* the tab to restore
|
||||
*
|
||||
* @returns true/false indicating whether or not a load actually happened
|
||||
* @param aLoadArguments
|
||||
* optional load arguments used for loadURI()
|
||||
*/
|
||||
restoreTabContent: function (aTab, aLoadArguments = null) {
|
||||
let window = aTab.ownerDocument.defaultView;
|
||||
let browser = aTab.linkedBrowser;
|
||||
let tabData = browser.__SS_data;
|
||||
|
||||
// Make sure that this tab is removed from the priority queue.
|
||||
TabRestoreQueue.remove(aTab);
|
||||
|
@ -2715,17 +2704,6 @@ let SessionStoreInternal = {
|
|||
browser.removeAttribute("pending");
|
||||
aTab.removeAttribute("pending");
|
||||
|
||||
let activeIndex = tabData.index - 1;
|
||||
|
||||
// Attach data that will be restored on "load" event, after tab is restored.
|
||||
if (tabData.entries.length) {
|
||||
// restore those aspects of the currently active documents which are not
|
||||
// preserved in the plain history entries (mainly scroll state and text data)
|
||||
browser.__SS_restore_data = tabData.entries[activeIndex] || {};
|
||||
} else {
|
||||
browser.__SS_restore_data = {};
|
||||
}
|
||||
|
||||
browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
|
||||
{loadArguments: aLoadArguments});
|
||||
},
|
||||
|
|
|
@ -162,7 +162,9 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, {
|
|||
* The select listener for this container.
|
||||
*/
|
||||
_onSelect: function({ detail: snapshotItem }) {
|
||||
if (!snapshotItem) {
|
||||
// Check to ensure the attachment has an actor, like
|
||||
// an in-progress recording.
|
||||
if (!snapshotItem || !snapshotItem.attachment.actor) {
|
||||
return;
|
||||
}
|
||||
let { calls, thumbnails, screenshot } = snapshotItem.attachment;
|
||||
|
|
|
@ -4,6 +4,7 @@ support-files =
|
|||
doc_raf-begin.html
|
||||
doc_settimeout.html
|
||||
doc_no-canvas.html
|
||||
doc_raf-no-canvas.html
|
||||
doc_simple-canvas.html
|
||||
doc_simple-canvas-bitmasks.html
|
||||
doc_simple-canvas-deep-stack.html
|
||||
|
@ -44,7 +45,9 @@ skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in
|
|||
[browser_canvas-frontend-reload-02.js]
|
||||
[browser_canvas-frontend-slider-01.js]
|
||||
[browser_canvas-frontend-slider-02.js]
|
||||
[browser_canvas-frontend-snapshot-select.js]
|
||||
[browser_canvas-frontend-snapshot-select-01.js]
|
||||
[browser_canvas-frontend-snapshot-select-02.js]
|
||||
[browser_canvas-frontend-stepping.js]
|
||||
[browser_canvas-frontend-stop-01.js]
|
||||
[browser_canvas-frontend-stop-02.js]
|
||||
[browser_canvas-frontend-stop-03.js]
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if selecting snapshots in the frontend displays the appropriate data
|
||||
* respective to their recorded animation frame.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
|
||||
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
|
||||
|
||||
yield reload(target);
|
||||
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
let snapshotTarget = SnapshotsListView.getItemAtIndex(0).target;
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
|
||||
|
||||
ok(true, "clicking in-progress snapshot does not fail");
|
||||
|
||||
let finished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
yield finished;
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that a recording that has a rAF cycle, but no draw calls, fails
|
||||
* after timeout.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, panel } = yield initCanvasDebuggerFrontend(RAF_NO_CANVAS_URL);
|
||||
let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
|
||||
|
||||
yield reload(target);
|
||||
|
||||
let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
|
||||
yield recordingStarted;
|
||||
|
||||
is($("#empty-notice").hidden, true, "Empty notice not shown");
|
||||
is($("#waiting-notice").hidden, false, "Waiting notice shown");
|
||||
|
||||
let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
|
||||
|
||||
yield promise.all([recordingFinished, recordingCancelled]);
|
||||
|
||||
ok(true, "Recording stopped and was considered failed.");
|
||||
|
||||
is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
|
||||
is($("#empty-notice").hidden, false, "Empty notice shown");
|
||||
is($("#waiting-notice").hidden, true, "Waiting notice not shown");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Canvas inspector test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
function render () { window.requestAnimationFrame(render); }
|
||||
window.requestAnimationFrame(render);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -30,6 +30,7 @@ const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-u
|
|||
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/canvasdebugger/test/";
|
||||
const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html";
|
||||
const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html";
|
||||
const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html";
|
||||
const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
|
||||
const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
|
||||
const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
min-width: 100%;
|
||||
}
|
||||
|
||||
body.dragging .tag-line {
|
||||
cursor: grabbing;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
#root-wrapper:after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
@ -63,6 +68,40 @@
|
|||
padding-left: 1001em;
|
||||
}
|
||||
|
||||
/* Normally this element takes space in the layout even if it's position: relative
|
||||
* by adding height: 0 we let surrounding elements to fill the blank space */
|
||||
.child.dragging {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Indicates a tag-line in the markup-view as being an active drop target by
|
||||
* drawing a horizontal line where the dragged element would be inserted if
|
||||
* dropped here */
|
||||
.tag-line.drop-target::before, .tag-line.drag-target::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-line.drag-target::before {
|
||||
border-top: 2px dashed var(--theme-contrast-background);
|
||||
}
|
||||
|
||||
.tag-line.drop-target::before {
|
||||
border-top: 2px dashed var(--theme-content-color1);
|
||||
}
|
||||
|
||||
/* In case the indicator is put on the closing .tag-line, the indentation level
|
||||
* will become misleading, so we push it forward to match the indentation level */
|
||||
ul.children + .tag-line::before {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.tag-line {
|
||||
min-height: 1.4em;
|
||||
line-height: 1.4em;
|
||||
|
|
|
@ -14,6 +14,10 @@ const COLLAPSE_ATTRIBUTE_LENGTH = 120;
|
|||
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
|
||||
const COLLAPSE_DATA_URL_LENGTH = 60;
|
||||
const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
|
||||
const GRAB_DELAY = 400;
|
||||
const DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE = 50;
|
||||
const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 5;
|
||||
const DRAG_DROP_MAX_AUTOSCROLL_SPEED = 15;
|
||||
|
||||
const {UndoStack} = require("devtools/shared/undo");
|
||||
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
|
||||
|
@ -62,6 +66,7 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
|||
this._inspector = aInspector;
|
||||
this.walker = this._inspector.walker;
|
||||
this._frame = aFrame;
|
||||
this.win = this._frame.contentWindow;
|
||||
this.doc = this._frame.contentDocument;
|
||||
this._elt = this.doc.querySelector("#root");
|
||||
this.htmlEditor = new HTMLEditor(this.doc);
|
||||
|
@ -94,6 +99,9 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
|||
|
||||
this._onMouseClick = this._onMouseClick.bind(this);
|
||||
|
||||
this._onMouseUp = this._onMouseUp.bind(this);
|
||||
this.doc.body.addEventListener("mouseup", this._onMouseUp);
|
||||
|
||||
this._boundOnNewSelection = this._onNewSelection.bind(this);
|
||||
this._inspector.selection.on("new-node-front", this._boundOnNewSelection);
|
||||
this._onNewSelection();
|
||||
|
@ -156,7 +164,49 @@ MarkupView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
isDragging: false,
|
||||
|
||||
_onMouseMove: function(event) {
|
||||
if (this.isDragging) {
|
||||
event.preventDefault();
|
||||
this._dragStartEl = event.target;
|
||||
|
||||
let docEl = this.doc.documentElement;
|
||||
|
||||
if (this._scrollInterval) {
|
||||
this.win.clearInterval(this._scrollInterval);
|
||||
}
|
||||
|
||||
// Auto-scroll when the mouse approaches top/bottom edge
|
||||
let distanceFromBottom = docEl.clientHeight - event.pageY + this.win.scrollY,
|
||||
distanceFromTop = event.pageY - this.win.scrollY;
|
||||
|
||||
if (distanceFromBottom <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) {
|
||||
// Map our distance from 0-50 to 5-15 range so the speed is kept
|
||||
// in a range not too fast, not too slow
|
||||
let speed = map(distanceFromBottom, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE,
|
||||
DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED);
|
||||
// Here, we use minus because the value of speed - 15 is always negative
|
||||
// and it makes the speed relative to the distance between mouse and edge
|
||||
// the closer to the edge, the faster
|
||||
this._scrollInterval = this.win.setInterval(() => {
|
||||
docEl.scrollTop -= speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (distanceFromTop <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) {
|
||||
// refer to bottom edge's comments for more info
|
||||
let speed = map(distanceFromTop, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE,
|
||||
DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED);
|
||||
|
||||
this._scrollInterval = this.win.setInterval(() => {
|
||||
docEl.scrollTop += speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
let target = event.target;
|
||||
|
||||
// Search target for a markupContainer reference, if not found, walk up
|
||||
|
@ -198,6 +248,18 @@ MarkupView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_onMouseUp: function() {
|
||||
if (this._lastDropTarget) {
|
||||
this.indicateDropTarget(null);
|
||||
}
|
||||
if (this._lastDragTarget) {
|
||||
this.indicateDragTarget(null);
|
||||
}
|
||||
if (this._scrollInterval) {
|
||||
this.win.clearInterval(this._scrollInterval);
|
||||
}
|
||||
},
|
||||
|
||||
_hoveredNode: null,
|
||||
|
||||
/**
|
||||
|
@ -218,6 +280,11 @@ MarkupView.prototype = {
|
|||
},
|
||||
|
||||
_onMouseLeave: function() {
|
||||
if (this._scrollInterval) {
|
||||
this.win.clearInterval(this._scrollInterval);
|
||||
}
|
||||
if (this.isDragging) return;
|
||||
|
||||
this._hideBoxModel(true);
|
||||
if (this._hoveredNode) {
|
||||
this.getContainer(this._hoveredNode).hovered = false;
|
||||
|
@ -790,6 +857,10 @@ MarkupView.prototype = {
|
|||
*/
|
||||
_expandContainer: function(aContainer) {
|
||||
return this._updateChildren(aContainer, {expand: true}).then(() => {
|
||||
if (this._destroyer) {
|
||||
console.warn("Could not expand the node, the markup-view was destroyed");
|
||||
return;
|
||||
}
|
||||
aContainer.expanded = true;
|
||||
});
|
||||
},
|
||||
|
@ -1361,6 +1432,12 @@ MarkupView.prototype = {
|
|||
this.tooltip.destroy();
|
||||
this.tooltip = null;
|
||||
|
||||
this.win = null;
|
||||
this.doc = null;
|
||||
|
||||
this._lastDropTarget = null;
|
||||
this._lastDragTarget = null;
|
||||
|
||||
return this._destroyer;
|
||||
},
|
||||
|
||||
|
@ -1448,6 +1525,80 @@ MarkupView.prototype = {
|
|||
this._updatePreview();
|
||||
this._previewBar.classList.remove("hide");
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an element as it's only argument and marks the element
|
||||
* as the drop target
|
||||
*/
|
||||
indicateDropTarget: function(el) {
|
||||
if (this._lastDropTarget) {
|
||||
this._lastDropTarget.classList.remove("drop-target");
|
||||
}
|
||||
|
||||
if (!el) return;
|
||||
|
||||
let target = el.classList.contains("tag-line") ?
|
||||
el : el.querySelector(".tag-line") || el.closest(".tag-line");
|
||||
if (!target) return;
|
||||
|
||||
target.classList.add("drop-target");
|
||||
this._lastDropTarget = target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an element to mark it as indicator of dragging target's initial place
|
||||
*/
|
||||
indicateDragTarget: function(el) {
|
||||
if (this._lastDragTarget) {
|
||||
this._lastDragTarget.classList.remove("drag-target");
|
||||
}
|
||||
|
||||
if (!el) return;
|
||||
|
||||
let target = el.classList.contains("tag-line") ?
|
||||
el : el.querySelector(".tag-line") || el.closest(".tag-line");
|
||||
|
||||
if (!target) return;
|
||||
|
||||
target.classList.add("drag-target");
|
||||
this._lastDragTarget = target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to get the nodes required to modify the markup after dragging the element (parent/nextSibling)
|
||||
*/
|
||||
get dropTargetNodes() {
|
||||
let target = this._lastDropTarget;
|
||||
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let parent, nextSibling;
|
||||
|
||||
if (this._lastDropTarget.previousElementSibling &&
|
||||
this._lastDropTarget.previousElementSibling.nodeName.toLowerCase() === "ul") {
|
||||
parent = target.parentNode.container.node;
|
||||
nextSibling = null;
|
||||
} else {
|
||||
parent = target.parentNode.container.node.parentNode();
|
||||
nextSibling = target.parentNode.container.node;
|
||||
}
|
||||
|
||||
if (nextSibling && nextSibling.isBeforePseudoElement) {
|
||||
nextSibling = target.parentNode.parentNode.children[1].container.node;
|
||||
}
|
||||
if (nextSibling && nextSibling.isAfterPseudoElement) {
|
||||
parent = target.parentNode.container.node.parentNode();
|
||||
nextSibling = null;
|
||||
}
|
||||
|
||||
if (parent.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {parent, nextSibling};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1481,6 +1632,7 @@ MarkupContainer.prototype = {
|
|||
this.markup = markupView;
|
||||
this.node = node;
|
||||
this.undo = this.markup.undo;
|
||||
this.win = this.markup._frame.contentWindow;
|
||||
|
||||
// The template will fill the following properties
|
||||
this.elt = null;
|
||||
|
@ -1491,15 +1643,16 @@ MarkupContainer.prototype = {
|
|||
this.markup.template(templateID, this);
|
||||
this.elt.container = this;
|
||||
|
||||
// Binding event listeners
|
||||
this._onMouseDown = this._onMouseDown.bind(this);
|
||||
this.elt.addEventListener("mousedown", this._onMouseDown, false);
|
||||
|
||||
this._onToggle = this._onToggle.bind(this);
|
||||
this._onMouseUp = this._onMouseUp.bind(this);
|
||||
this._onMouseMove = this._onMouseMove.bind(this);
|
||||
|
||||
// Expanding/collapsing the node on dblclick of the whole tag-line element
|
||||
// Binding event listeners
|
||||
this.elt.addEventListener("mousedown", this._onMouseDown, false);
|
||||
this.markup.doc.body.addEventListener("mouseup", this._onMouseUp, true);
|
||||
this.markup.doc.body.addEventListener("mousemove", this._onMouseMove, true);
|
||||
this.elt.addEventListener("dblclick", this._onToggle, false);
|
||||
|
||||
if (this.expander) {
|
||||
this.expander.addEventListener("click", this._onToggle, false);
|
||||
}
|
||||
|
@ -1618,24 +1771,109 @@ MarkupContainer.prototype = {
|
|||
return this.elt.parentNode ? this.elt.parentNode.container : null;
|
||||
},
|
||||
|
||||
_isMouseDown: false,
|
||||
_isDragging: false,
|
||||
_dragStartY: 0,
|
||||
|
||||
set isDragging(isDragging) {
|
||||
this._isDragging = isDragging;
|
||||
this.markup.isDragging = isDragging;
|
||||
|
||||
if (isDragging) {
|
||||
this.elt.classList.add("dragging");
|
||||
this.markup.doc.body.classList.add("dragging");
|
||||
} else {
|
||||
this.elt.classList.remove("dragging");
|
||||
this.markup.doc.body.classList.remove("dragging");
|
||||
}
|
||||
},
|
||||
|
||||
get isDragging() {
|
||||
return this._isDragging;
|
||||
},
|
||||
|
||||
_onMouseDown: function(event) {
|
||||
let target = event.target;
|
||||
|
||||
// Target may be a resource link (generated by the output-parser)
|
||||
// The "show more nodes" button (already has its onclick).
|
||||
if (target.nodeName === "button") {
|
||||
return;
|
||||
}
|
||||
|
||||
// output-parser generated links handling.
|
||||
if (target.nodeName === "a") {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let browserWin = this.markup._inspector.target
|
||||
.tab.ownerDocument.defaultView;
|
||||
browserWin.openUILinkIn(target.href, "tab");
|
||||
return;
|
||||
}
|
||||
// Or it may be the "show more nodes" button (which already has its onclick)
|
||||
// Else, it's the container itself
|
||||
else if (target.nodeName !== "button") {
|
||||
this.hovered = false;
|
||||
this.markup.navigate(this);
|
||||
event.stopPropagation();
|
||||
|
||||
// target is the MarkupContainer itself.
|
||||
this._isMouseDown = true;
|
||||
this.hovered = false;
|
||||
this.markup.navigate(this);
|
||||
event.stopPropagation();
|
||||
|
||||
// Start dragging the container after a delay.
|
||||
this.markup._dragStartEl = target;
|
||||
this.win.setTimeout(() => {
|
||||
// Make sure the mouse is still down and on target.
|
||||
if (!this._isMouseDown || this.markup._dragStartEl !== target ||
|
||||
this.node.isPseudoElement || this.node.isAnonymous ||
|
||||
!this.win.getSelection().isCollapsed) {
|
||||
return;
|
||||
}
|
||||
this.isDragging = true;
|
||||
|
||||
this._dragStartY = event.pageY;
|
||||
this.markup.indicateDropTarget(this.elt);
|
||||
|
||||
// If this is the last child, use the closing <div.tag-line> of parent as indicator
|
||||
this.markup.indicateDragTarget(this.elt.nextElementSibling ||
|
||||
this.markup.getContainer(this.node.parentNode()).closeTagLine);
|
||||
}, GRAB_DELAY);
|
||||
},
|
||||
|
||||
/**
|
||||
* On mouse up, stop dragging.
|
||||
*/
|
||||
_onMouseUp: function(event) {
|
||||
this._isMouseDown = false;
|
||||
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragging = false;
|
||||
this.elt.style.removeProperty("top");
|
||||
|
||||
let dropTargetNodes = this.markup.dropTargetNodes;
|
||||
|
||||
if(!dropTargetNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markup.walker.insertBefore(this.node, dropTargetNodes.parent,
|
||||
dropTargetNodes.nextSibling);
|
||||
},
|
||||
|
||||
/**
|
||||
* On mouse move, move the dragged element if any and indicate the drop target.
|
||||
*/
|
||||
_onMouseMove: function(event) {
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
let diff = event.pageY - this._dragStartY;
|
||||
this.elt.style.top = diff + "px";
|
||||
|
||||
let el = this.markup.doc.elementFromPoint(event.pageX - this.win.scrollX,
|
||||
event.pageY - this.win.scrollY);
|
||||
|
||||
this.markup.indicateDropTarget(el);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1644,7 +1882,7 @@ MarkupContainer.prototype = {
|
|||
*/
|
||||
flashMutation: function() {
|
||||
if (!this.selected) {
|
||||
let contentWin = this.markup._frame.contentWindow;
|
||||
let contentWin = this.win;
|
||||
this.flashed = true;
|
||||
if (this._flashMutationTimer) {
|
||||
contentWin.clearTimeout(this._flashMutationTimer);
|
||||
|
@ -1777,6 +2015,10 @@ MarkupContainer.prototype = {
|
|||
// Remove event listeners
|
||||
this.elt.removeEventListener("mousedown", this._onMouseDown, false);
|
||||
this.elt.removeEventListener("dblclick", this._onToggle, false);
|
||||
this.markup.doc.body.removeEventListener("mouseup", this._onMouseUp, true);
|
||||
this.markup.doc.body.removeEventListener("mousemove", this._onMouseMove, true);
|
||||
|
||||
this.win = null;
|
||||
|
||||
if (this.expander) {
|
||||
this.expander.removeEventListener("click", this._onToggle, false);
|
||||
|
@ -2515,6 +2757,17 @@ function parseAttributeValues(attr, doc) {
|
|||
return attributes.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a number from one range to another.
|
||||
*/
|
||||
function map(value, oldMin, oldMax, newMin, newMax) {
|
||||
let ratio = oldMax - oldMin;
|
||||
if (ratio == 0) {
|
||||
return value;
|
||||
}
|
||||
return newMin + (newMax - newMin) * ((value - oldMin) / ratio);
|
||||
}
|
||||
|
||||
loader.lazyGetter(MarkupView.prototype, "strings", () => Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/inspector.properties"
|
||||
));
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
subsuite = devtools
|
||||
support-files =
|
||||
doc_markup_anonymous.html
|
||||
doc_markup_dragdrop.html
|
||||
doc_markup_dragdrop_autoscroll.html
|
||||
doc_markup_edit.html
|
||||
doc_markup_events.html
|
||||
doc_markup_events_jquery.html
|
||||
|
@ -37,6 +39,11 @@ skip-if = e10s # scratchpad.xul is not loading in e10s window
|
|||
[browser_markupview_anonymous_04.js]
|
||||
[browser_markupview_copy_image_data.js]
|
||||
[browser_markupview_css_completion_style_attribute.js]
|
||||
[browser_markupview_dragdrop_autoscroll.js]
|
||||
[browser_markupview_dragdrop_invalidNodes.js]
|
||||
[browser_markupview_dragdrop_isDragging.js]
|
||||
[browser_markupview_dragdrop_reorder.js]
|
||||
[browser_markupview_dragdrop_textSelection.js]
|
||||
[browser_markupview_events.js]
|
||||
skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
|
||||
[browser_markupview_events-overflow.js]
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test: Dragging nodes near top/bottom edges of inspector
|
||||
// should auto-scroll
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop_autoscroll.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
let markup = inspector.markup;
|
||||
|
||||
let container = yield getContainerForSelector("#first", inspector);
|
||||
let rect = container.elt.getBoundingClientRect();
|
||||
|
||||
info("Simulating mouseDown on #first");
|
||||
container._onMouseDown({
|
||||
target: container.tagLine,
|
||||
pageX: 10,
|
||||
pageY: rect.top,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
|
||||
let clientHeight = markup.doc.documentElement.clientHeight;
|
||||
info("Simulating mouseMove on #first with pageY: " + clientHeight);
|
||||
|
||||
let ev = {
|
||||
target: container.tagLine,
|
||||
pageX: 10,
|
||||
pageY: clientHeight,
|
||||
preventDefault: function() {}
|
||||
};
|
||||
|
||||
info("Listening on scroll event");
|
||||
let scroll = onScroll(markup.win);
|
||||
|
||||
markup._onMouseMove(ev);
|
||||
|
||||
yield scroll;
|
||||
|
||||
container._onMouseUp(ev);
|
||||
markup._onMouseUp(ev);
|
||||
|
||||
ok("Scroll event fired");
|
||||
});
|
||||
|
||||
function onScroll(win) {
|
||||
return new Promise((resolve, reject) => {
|
||||
win.onscroll = function(e) {
|
||||
resolve(e);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test: pseudo-elements and anonymous nodes should not be draggable
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
|
||||
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Expanding #test");
|
||||
let parentFront = yield getNodeFront("#test", inspector);
|
||||
yield inspector.markup.expandNode(parentFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
let parentContainer = yield getContainerForNodeFront(parentFront, inspector);
|
||||
let beforePseudo = parentContainer.elt.children[1].firstChild.container;
|
||||
|
||||
parentContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Simulating mouseDown on #test::before");
|
||||
beforePseudo._onMouseDown({
|
||||
target: beforePseudo.tagLine,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
info("Waiting " + (GRAB_DELAY + 1) + "ms")
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
is(beforePseudo.isDragging, false, "[pseudo-element] isDragging is false after GRAB_DELAY has passed");
|
||||
|
||||
let inputFront = yield getNodeFront("#anonymousParent", inspector);
|
||||
|
||||
yield inspector.markup.expandNode(inputFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
let inputContainer = yield getContainerForNodeFront(inputFront, inspector);
|
||||
let anonymousDiv = inputContainer.elt.children[1].firstChild.container;
|
||||
|
||||
inputContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Simulating mouseDown on input#anonymousParent div");
|
||||
anonymousDiv._onMouseDown({
|
||||
target: anonymousDiv.tagLine,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
info("Waiting " + (GRAB_DELAY + 1) + "ms")
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
is(anonymousDiv.isDragging, false, "[anonymous element] isDragging is false after GRAB_DELAY has passed");
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test drag mode's delay, it shouldn't enable dragging before
|
||||
// GRAB_DELAY = 400 has passed
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
let el = yield getContainerForSelector("#test", inspector);
|
||||
let rect = el.tagLine.getBoundingClientRect();
|
||||
|
||||
info("Simulating mouseDown on #test");
|
||||
el._onMouseDown({
|
||||
target: el.tagLine,
|
||||
pageX: rect.x,
|
||||
pageY: rect.y,
|
||||
stopPropagation: function() {}
|
||||
});
|
||||
|
||||
is(el.isDragging, false, "isDragging should not be set to true immedietly");
|
||||
|
||||
info("Waiting " + (GRAB_DELAY + 1) + "ms");
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
ok(el.isDragging, "isDragging true after GRAB_DELAY has passed");
|
||||
|
||||
info("Simulating mouseUp on #test");
|
||||
el._onMouseUp({
|
||||
target: el.tagLine,
|
||||
pageX: rect.x,
|
||||
pageY: rect.y
|
||||
});
|
||||
|
||||
is(el.isDragging, false, "isDragging false after mouseUp");
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test different kinds of drag and drop node re-ordering
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Expanding #test");
|
||||
let parentFront = yield getNodeFront("#test", inspector);
|
||||
let parent = yield getNode("#test");
|
||||
let parentContainer = yield getContainerForNodeFront(parentFront, inspector);
|
||||
|
||||
yield inspector.markup.expandNode(parentFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
parentContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Testing switching elements inside their parent");
|
||||
yield moveElementDown("#firstChild", "#middleChild", inspector);
|
||||
|
||||
is(parent.children[0].id, "middleChild", "#firstChild is now the second child of #test");
|
||||
is(parent.children[1].id, "firstChild", "#middleChild is now the first child of #test");
|
||||
|
||||
info("Testing switching elements with a last child");
|
||||
yield moveElementDown("#firstChild", "#lastChild", inspector);
|
||||
|
||||
is(parent.children[1].id, "lastChild", "#lastChild is now the second child of #test");
|
||||
is(parent.children[2].id, "firstChild", "#firstChild is now the last child of #test");
|
||||
|
||||
info("Testing appending element to a parent");
|
||||
yield moveElementDown("#before", "#test", inspector);
|
||||
|
||||
is(parent.children.length, 4, "New element appended to #test");
|
||||
is(parent.children[0].id, "before", "New element is appended at the right place (currently first child)");
|
||||
|
||||
info("Testing moving element to after it's parent");
|
||||
yield moveElementDown("#firstChild", "#test", inspector);
|
||||
|
||||
is(parent.children.length, 3, "#firstChild is no longer #test's child");
|
||||
is(parent.nextElementSibling.id, "firstChild", "#firstChild is now #test's nextElementSibling");
|
||||
});
|
||||
|
||||
function* dragContainer(selector, targetOffset, inspector) {
|
||||
info("Dragging the markup-container for node " + selector);
|
||||
|
||||
let container = yield getContainerForSelector(selector, inspector);
|
||||
|
||||
let updated = inspector.once("inspector-updated");
|
||||
|
||||
let rect = {
|
||||
x: container.tagLine.offsetLeft,
|
||||
y: container.tagLine.offsetTop
|
||||
};
|
||||
|
||||
info("Simulating mouseDown on " + selector);
|
||||
container._onMouseDown({
|
||||
target: container.tagLine,
|
||||
pageX: rect.x,
|
||||
pageY: rect.y,
|
||||
stopPropagation: function() {}
|
||||
});
|
||||
|
||||
let targetX = rect.x + targetOffset.x,
|
||||
targetY = rect.y + targetOffset.y;
|
||||
|
||||
setTimeout(() => {
|
||||
info("Simulating mouseMove on " + selector +
|
||||
" with pageX: " + targetX + " pageY: " + targetY);
|
||||
container._onMouseMove({
|
||||
target: container.tagLine,
|
||||
pageX: targetX,
|
||||
pageY: targetY
|
||||
});
|
||||
|
||||
info("Simulating mouseUp on " + selector +
|
||||
" with pageX: " + targetX + " pageY: " + targetY);
|
||||
container._onMouseUp({
|
||||
target: container.tagLine,
|
||||
pageX: targetX,
|
||||
pageY: targetY
|
||||
});
|
||||
|
||||
container.markup._onMouseUp();
|
||||
}, GRAB_DELAY+1);
|
||||
|
||||
return updated;
|
||||
};
|
||||
|
||||
function* moveElementDown(selector, next, inspector) {
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let uiUpdate = inspector.once("inspector-updated");
|
||||
|
||||
let el = yield getContainerForSelector(next, inspector);
|
||||
let height = el.tagLine.getBoundingClientRect().height;
|
||||
|
||||
info("Switching " + selector + ' with ' + next);
|
||||
|
||||
yield dragContainer(selector, {x: 0, y: Math.round(height) + 2}, inspector);
|
||||
|
||||
yield onMutated;
|
||||
yield uiUpdate;
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test: Nodes should not be draggable if there is a text selected
|
||||
// (trying to move selected text around shouldn't trigger node drag and drop)
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
let markup = inspector.markup;
|
||||
|
||||
info("Expanding span#before");
|
||||
let spanFront = yield getNodeFront("#before", inspector);
|
||||
let spanContainer = yield getContainerForNodeFront(spanFront, inspector);
|
||||
let span = yield getNode("#before");
|
||||
|
||||
yield inspector.markup.expandNode(spanFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
spanContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Selecting #before's text content");
|
||||
|
||||
let textContent = spanContainer.elt.children[1].firstChild.container;
|
||||
|
||||
let selectRange = markup.doc.createRange();
|
||||
selectRange.selectNode(textContent.editor.elt.querySelector('[tabindex]'));
|
||||
markup.doc.getSelection().addRange(selectRange);
|
||||
|
||||
info("Simulating mouseDown on #before");
|
||||
|
||||
spanContainer._onMouseDown({
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
target: spanContainer.tagLine,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
|
||||
is(spanContainer.isDragging, false, "isDragging should be false if there is a text selected");
|
||||
});
|
|
@ -31,15 +31,3 @@ add_task(function*() {
|
|||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
||||
|
||||
// The expand all operation of the markup-view calls itself recursively and
|
||||
// there's not one event we can wait for to know when it's done
|
||||
function* waitForMultipleChildrenUpdates(inspector) {
|
||||
// As long as child updates are queued up while we wait for an update already
|
||||
// wait again
|
||||
if (inspector.markup._queuedChildUpdates &&
|
||||
inspector.markup._queuedChildUpdates.size) {
|
||||
yield waitForChildrenUpdated(inspector);
|
||||
return yield waitForMultipleChildrenUpdates(inspector);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,15 +33,3 @@ add_task(function*() {
|
|||
"Container for node " + nodeFront.tagName + " is expanded");
|
||||
}
|
||||
});
|
||||
|
||||
// The expand all operation of the markup-view calls itself recursively and
|
||||
// there's not one event we can wait for to know when it's done
|
||||
function* waitForMultipleChildrenUpdates(inspector) {
|
||||
// As long as child updates are queued up while we wait for an update already
|
||||
// wait again
|
||||
if (inspector.markup._queuedChildUpdates &&
|
||||
inspector.markup._queuedChildUpdates.size) {
|
||||
yield waitForChildrenUpdated(inspector);
|
||||
return yield waitForMultipleChildrenUpdates(inspector);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=858038
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 858038</title>
|
||||
<style>
|
||||
#test::before {
|
||||
content: 'This should not be draggable';
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858038">Mozilla Bug 858038</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<input id="anonymousParent" />
|
||||
|
||||
<span id="before">Before</span>
|
||||
<pre id="test">
|
||||
<span id="firstChild">First</span>
|
||||
<span id="middleChild">Middle</span>
|
||||
<span id="lastChild">Last</span>
|
||||
</pre>
|
||||
<span id="after">After</span>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=858038
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 858038 - Autoscroll</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="first"></div>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858038">Mozilla Bug 858038</a>
|
||||
<p id="display">Test</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Make sure the markup-view has enough nodes shown by default that it has a scrollbar -->
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
|
@ -561,3 +561,16 @@ function checkFocusedAttribute(attrName, editMode) {
|
|||
editMode ? "input": "span",
|
||||
editMode ? attrName + " is in edit mode" : attrName + " is not in edit mode");
|
||||
}
|
||||
|
||||
// The expand all operation of the markup-view calls itself recursively and
|
||||
// there's not one event we can wait for to know when it's done
|
||||
// so use this helper function to wait until all recursive children updates are done.
|
||||
function* waitForMultipleChildrenUpdates(inspector) {
|
||||
// As long as child updates are queued up while we wait for an update already
|
||||
// wait again
|
||||
if (inspector.markup._queuedChildUpdates &&
|
||||
inspector.markup._queuedChildUpdates.size) {
|
||||
yield waitForChildrenUpdated(inspector);
|
||||
return yield waitForMultipleChildrenUpdates(inspector);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,6 @@ function test() {
|
|||
function testPrefs() {
|
||||
let { Prefs } = aMonitor.panelWin;
|
||||
|
||||
is(Prefs._root, "devtools.netmonitor",
|
||||
"The preferences object should have a correct root path.");
|
||||
|
||||
is(Prefs.networkDetailsWidth,
|
||||
Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
|
||||
"Getting a pref should work correctly.");
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
/**
|
||||
* Utility functions for managing recording models and their internal data,
|
||||
* such as filtering profile samples or offsetting timestamps.
|
||||
|
@ -130,3 +132,50 @@ exports.RecordingUtils.getSamplesFromAllocations = function(allocations) {
|
|||
gSamplesFromAllocationCache.set(allocations, samples);
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current timeline blueprint without the hidden markers.
|
||||
*
|
||||
* @param blueprint
|
||||
* The default timeline blueprint.
|
||||
* @param array hiddenMarkers
|
||||
* A list of hidden markers' names.
|
||||
* @return object
|
||||
* The filtered timeline blueprint.
|
||||
*/
|
||||
exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarkers }) {
|
||||
let filteredBlueprint = Cu.cloneInto(blueprint, {});
|
||||
let maybeRemovedGroups = new Set();
|
||||
let removedGroups = new Set();
|
||||
|
||||
// 1. Remove hidden markers from the blueprint.
|
||||
|
||||
for (let hiddenMarkerName of hiddenMarkers) {
|
||||
maybeRemovedGroups.add(filteredBlueprint[hiddenMarkerName].group);
|
||||
delete filteredBlueprint[hiddenMarkerName];
|
||||
}
|
||||
|
||||
// 2. Get a list of all the groups that will be removed.
|
||||
|
||||
for (let maybeRemovedGroup of maybeRemovedGroups) {
|
||||
let markerNames = Object.keys(filteredBlueprint);
|
||||
let isGroupRemoved = markerNames.every(e => filteredBlueprint[e].group != maybeRemovedGroup);
|
||||
if (isGroupRemoved) {
|
||||
removedGroups.add(maybeRemovedGroup);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Offset groups so that their indices are consecutive.
|
||||
|
||||
for (let removedGroup of removedGroups) {
|
||||
let markerNames = Object.keys(filteredBlueprint);
|
||||
for (let markerName of markerNames) {
|
||||
let markerDetails = filteredBlueprint[markerName];
|
||||
if (markerDetails.group > removedGroup) {
|
||||
markerDetails.group--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredBlueprint;
|
||||
};
|
||||
|
|
|
@ -57,10 +57,12 @@ const BRANCH_NAME = "devtools.performance.ui.";
|
|||
|
||||
// Events emitted by various objects in the panel.
|
||||
const EVENTS = {
|
||||
// Fired by the OptionsView when a preference changes.
|
||||
// Fired by the PerformanceController and OptionsView when a pref changes.
|
||||
PREF_CHANGED: "Performance:PrefChanged",
|
||||
|
||||
// Emitted by the PerformanceView when the state (display mode) changes.
|
||||
// Emitted by the PerformanceView when the state (display mode) changes,
|
||||
// for example when switching between "empty", "recording" or "recorded".
|
||||
// This causes certain panels to be hidden or visible.
|
||||
UI_STATE_CHANGED: "Performance:UI:StateChanged",
|
||||
|
||||
// Emitted by the PerformanceView on clear button click
|
||||
|
@ -176,6 +178,16 @@ let PerformanceController = {
|
|||
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
|
||||
// All boolean prefs should be handled via the OptionsView in the
|
||||
// ToolbarView, so that they may be accessible via the "gear" menu.
|
||||
// Every other pref should be registered here.
|
||||
this._nonBooleanPrefs = new ViewHelpers.Prefs("devtools.performance", {
|
||||
"hidden-markers": ["Json", "timeline.hidden-markers"]
|
||||
});
|
||||
|
||||
this._nonBooleanPrefs.registerObserver();
|
||||
this._nonBooleanPrefs.on("pref-changed", this._onPrefChanged);
|
||||
|
||||
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
|
@ -195,6 +207,9 @@ let PerformanceController = {
|
|||
* Remove events handled by the PerformanceController
|
||||
*/
|
||||
destroy: function() {
|
||||
this._nonBooleanPrefs.unregisterObserver();
|
||||
this._nonBooleanPrefs.off("pref-changed", this._onPrefChanged);
|
||||
|
||||
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
|
@ -211,11 +226,32 @@ let PerformanceController = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get a preference setting from `prefName` via the underlying
|
||||
* Get a boolean preference setting from `prefName` via the underlying
|
||||
* OptionsView in the ToolbarView.
|
||||
*
|
||||
* @param string prefName
|
||||
* @return boolean
|
||||
*/
|
||||
getOption: function (prefName) {
|
||||
return ToolbarView.optionsView.getPref(prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a preference setting from `prefName`.
|
||||
* @param string prefName
|
||||
* @return object
|
||||
*/
|
||||
getPref: function (prefName) {
|
||||
return ToolbarView.optionsView.getPref(prefName);
|
||||
return this._nonBooleanPrefs[prefName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a preference setting from `prefName`.
|
||||
* @param string prefName
|
||||
* @param object prefValue
|
||||
*/
|
||||
setPref: function (prefName, prefValue) {
|
||||
this._nonBooleanPrefs[prefName] = prefValue;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -223,9 +259,9 @@ let PerformanceController = {
|
|||
* when the front has started to record.
|
||||
*/
|
||||
startRecording: Task.async(function *() {
|
||||
let withMemory = this.getPref("enable-memory");
|
||||
let withTicks = this.getPref("enable-framerate");
|
||||
let withAllocations = this.getPref("enable-memory");
|
||||
let withMemory = this.getOption("enable-memory");
|
||||
let withTicks = this.getOption("enable-framerate");
|
||||
let withAllocations = this.getOption("enable-memory");
|
||||
|
||||
let recording = this._createRecording({ withMemory, withTicks, withAllocations });
|
||||
|
||||
|
@ -241,7 +277,7 @@ let PerformanceController = {
|
|||
* when the front has stopped recording.
|
||||
*/
|
||||
stopRecording: Task.async(function *() {
|
||||
let recording = this._getLatestRecording();
|
||||
let recording = this.getLatestRecording();
|
||||
|
||||
this.emit(EVENTS.RECORDING_WILL_STOP, recording);
|
||||
yield recording.stopRecording();
|
||||
|
@ -267,7 +303,7 @@ let PerformanceController = {
|
|||
* Emits `EVENTS.RECORDINGS_CLEARED` when complete so other components can clean up.
|
||||
*/
|
||||
clearRecordings: Task.async(function* () {
|
||||
let latest = this._getLatestRecording();
|
||||
let latest = this.getLatestRecording();
|
||||
|
||||
if (latest && latest.isRecording()) {
|
||||
yield this.stopRecording();
|
||||
|
@ -335,13 +371,23 @@ let PerformanceController = {
|
|||
* Get most recently added recording that was triggered manually (via UI).
|
||||
* @return RecordingModel
|
||||
*/
|
||||
_getLatestRecording: function () {
|
||||
getLatestRecording: function () {
|
||||
for (let i = this._recordings.length - 1; i >= 0; i--) {
|
||||
return this._recordings[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current timeline blueprint without the hidden markers.
|
||||
* @return object
|
||||
*/
|
||||
getTimelineBlueprint: function() {
|
||||
let blueprint = TIMELINE_BLUEPRINT;
|
||||
let hiddenMarkers = this.getPref("hidden-markers");
|
||||
return RecordingUtils.getFilteredBlueprint({ blueprint, hiddenMarkers });
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired whenever the PerformanceFront emits markers, memory or ticks.
|
||||
*/
|
||||
|
@ -362,8 +408,8 @@ let PerformanceController = {
|
|||
* Fired when the ToolbarView fires a PREF_CHANGED event.
|
||||
* with the value.
|
||||
*/
|
||||
_onPrefChanged: function (_, prefName, value) {
|
||||
this.emit(EVENTS.PREF_CHANGED, prefName, value);
|
||||
_onPrefChanged: function (_, prefName, prefValue) {
|
||||
this.emit(EVENTS.PREF_CHANGED, prefName, prefValue);
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceController]"
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<script type="application/javascript" src="performance/views/recordings.js"/>
|
||||
|
||||
<popupset id="performance-options-popupset">
|
||||
<menupopup id="performance-filter-menupopup"/>
|
||||
<menupopup id="performance-options-menupopup">
|
||||
<menuitem id="option-show-platform-data"
|
||||
type="checkbox"
|
||||
|
@ -85,6 +86,12 @@
|
|||
|
||||
<vbox id="performance-pane" flex="1">
|
||||
<toolbar id="performance-toolbar" class="devtools-toolbar">
|
||||
<hbox id="performance-toolbar-control-other" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="filter-button"
|
||||
popup="performance-filter-menupopup"
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
|
||||
</hbox>
|
||||
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
|
@ -112,7 +119,7 @@
|
|||
<toolbarbutton id="performance-options-button"
|
||||
class="devtools-toolbarbutton devtools-option-toolbarbutton"
|
||||
popup="performance-options-menupopup"
|
||||
tooltiptext="&profilerUI.options.tooltiptext;"/>
|
||||
tooltiptext="&profilerUI.options.gear.tooltiptext;"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
|
|
|
@ -97,3 +97,4 @@ support-files =
|
|||
[browser_profiler_tree-view-06.js]
|
||||
[browser_profiler_tree-view-07.js]
|
||||
[browser_timeline_blueprint.js]
|
||||
[browser_timeline_filters.js]
|
||||
|
|
|
@ -5,28 +5,29 @@
|
|||
* Tests markers filtering mechanism.
|
||||
*/
|
||||
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
|
||||
let { $, $$, TimelineController, TimelineView } = panel.panelWin;
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { $, $$, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
let { MARKERS_GRAPH_ROW_HEIGHT } = panel.panelWin;
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
yield startRecording(panel);
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
yield waitUntil(() => {
|
||||
// Wait until we get 3 different markers.
|
||||
let markers = TimelineController.getMarkers();
|
||||
let markers = PerformanceController.getCurrentRecording().getMarkers();
|
||||
return markers.some(m => m.name == "Styles") &&
|
||||
markers.some(m => m.name == "Reflow") &&
|
||||
markers.some(m => m.name == "Paint");
|
||||
});
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
yield stopRecording(panel);
|
||||
|
||||
let overview = TimelineView.markersOverview;
|
||||
let waterfall = TimelineView.waterfall;
|
||||
let overview = OverviewView.markersOverview;
|
||||
let waterfall = WaterfallView.waterfall;
|
||||
|
||||
// Select everything
|
||||
overview.setSelection({ start: 0, end: overview.width })
|
||||
OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE })
|
||||
|
||||
$("#filter-button").click();
|
||||
|
||||
|
@ -70,8 +71,7 @@ add_task(function*() {
|
|||
|
||||
yield waitUntil(() => !waterfall._outstandingMarkers.length);
|
||||
|
||||
// A row is 11px. See markers-overview.js
|
||||
is(overview.fixedHeight, heightBefore - 11, "Overview is smaller");
|
||||
is(overview.fixedHeight, heightBefore - MARKERS_GRAPH_ROW_HEIGHT, "Overview is smaller");
|
||||
ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (4)");
|
||||
ok(!$(".waterfall-marker-bar[type=Reflow]"), "No 'Reflow' marker (4)");
|
||||
ok(!$(".waterfall-marker-bar[type=Paint]"), "No 'Paint' marker (4)");
|
||||
|
@ -89,5 +89,6 @@ add_task(function*() {
|
|||
|
||||
is(overview.fixedHeight, originalHeight, "Overview restored");
|
||||
|
||||
$(".waterfall-marker-bar[type=Styles]");
|
||||
});
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -5,13 +5,16 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Profiler test page</title>
|
||||
<title>Performance test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var x = 1;
|
||||
function test() {
|
||||
var a = "Hello world!";
|
||||
document.body.style.borderTop = x + "px solid red";
|
||||
x = 1^x;
|
||||
document.body.innerHeight; // flush pending reflows
|
||||
}
|
||||
|
||||
// Prevent this script from being garbage collected.
|
||||
|
|
|
@ -59,10 +59,16 @@ let DetailsSubview = {
|
|||
|
||||
/**
|
||||
* An array of preferences under `devtools.performance.ui.` that the view should
|
||||
* rerender upon change.
|
||||
* rerender and callback `this._onRerenderPrefChanged` upon change.
|
||||
*/
|
||||
rerenderPrefs: [],
|
||||
|
||||
/**
|
||||
* An array of preferences under `devtools.performance.` that the view should
|
||||
* observe and callback `this._onObservedPrefChange` upon change.
|
||||
*/
|
||||
observedPrefs: [],
|
||||
|
||||
/**
|
||||
* Called when recording stops or is selected.
|
||||
*/
|
||||
|
@ -103,6 +109,10 @@ let DetailsSubview = {
|
|||
* Fired when a preference in `devtools.performance.ui.` is changed.
|
||||
*/
|
||||
_onPrefChanged: function (_, prefName) {
|
||||
if (~this.observedPrefs.indexOf(prefName) && this._onObservedPrefChange) {
|
||||
this._onObservedPrefChange(_, prefName);
|
||||
}
|
||||
|
||||
// All detail views require a recording to be complete, so do not
|
||||
// attempt to render if recording is in progress or does not exist.
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
|
@ -115,7 +125,7 @@ let DetailsSubview = {
|
|||
}
|
||||
|
||||
if (this._onRerenderPrefChanged) {
|
||||
this._onRerenderPrefChanged();
|
||||
this._onRerenderPrefChanged(_, prefName);
|
||||
}
|
||||
|
||||
if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {
|
||||
|
|
|
@ -64,8 +64,8 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
*/
|
||||
_prepareCallTree: function (profile, { startTime, endTime }, options) {
|
||||
let threadSamples = profile.threads[0].samples;
|
||||
let contentOnly = !PerformanceController.getPref("show-platform-data");
|
||||
let invertTree = PerformanceController.getPref("invert-call-tree");
|
||||
let contentOnly = !PerformanceController.getOption("show-platform-data");
|
||||
let invertTree = PerformanceController.getOption("invert-call-tree");
|
||||
|
||||
let threadNode = new ThreadNode(threadSamples,
|
||||
{ startTime, endTime, contentOnly, invertTree });
|
||||
|
@ -108,7 +108,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
|
||||
// When platform data isn't shown, hide the cateogry labels, since they're
|
||||
// only available for C++ frames.
|
||||
let contentOnly = !PerformanceController.getPref("show-platform-data");
|
||||
let contentOnly = !PerformanceController.getOption("show-platform-data");
|
||||
root.toggleCategories(!contentOnly);
|
||||
},
|
||||
|
||||
|
|
|
@ -53,10 +53,10 @@ let JsFlameGraphView = Heritage.extend(DetailsSubview, {
|
|||
let samples = profile.threads[0].samples;
|
||||
|
||||
let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
|
||||
invertStack: PerformanceController.getPref("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getPref("flatten-tree-recursion"),
|
||||
filterFrames: !PerformanceController.getPref("show-platform-data") && FrameNode.isContent,
|
||||
showIdleBlocks: PerformanceController.getPref("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
invertStack: PerformanceController.getOption("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
|
||||
filterFrames: !PerformanceController.getOption("show-platform-data") && FrameNode.isContent,
|
||||
showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
});
|
||||
|
||||
this.graph.setData({ data,
|
||||
|
|
|
@ -62,7 +62,7 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
*/
|
||||
_prepareCallTree: function (allocations, { startTime, endTime }, options) {
|
||||
let samples = RecordingUtils.getSamplesFromAllocations(allocations);
|
||||
let invertTree = PerformanceController.getPref("invert-call-tree");
|
||||
let invertTree = PerformanceController.getOption("invert-call-tree");
|
||||
|
||||
let threadNode = new ThreadNode(samples,
|
||||
{ startTime, endTime, invertTree });
|
||||
|
|
|
@ -52,9 +52,9 @@ let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
|
|||
|
||||
let samples = RecordingUtils.getSamplesFromAllocations(allocations);
|
||||
let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
|
||||
invertStack: PerformanceController.getPref("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getPref("flatten-tree-recursion"),
|
||||
showIdleBlocks: PerformanceController.getPref("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
invertStack: PerformanceController.getOption("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
|
||||
showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
});
|
||||
|
||||
this.graph.setData({ data,
|
||||
|
|
|
@ -8,6 +8,14 @@
|
|||
*/
|
||||
let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
observedPrefs: [
|
||||
"hidden-markers"
|
||||
],
|
||||
|
||||
rerenderPrefs: [
|
||||
"hidden-markers"
|
||||
],
|
||||
|
||||
rangeChangeDebounceTime: 10, // ms
|
||||
|
||||
/**
|
||||
|
@ -16,7 +24,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||
initialize: function () {
|
||||
DetailsSubview.initialize.call(this);
|
||||
|
||||
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"), TIMELINE_BLUEPRINT);
|
||||
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"));
|
||||
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
|
||||
|
||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||
|
@ -26,6 +34,8 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||
this.waterfall.on("unselected", this._onMarkerSelected);
|
||||
this.details.on("resize", this._onResize);
|
||||
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
this.waterfall.recalculateBounds();
|
||||
},
|
||||
|
||||
|
@ -79,5 +89,13 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever an observed pref is changed.
|
||||
*/
|
||||
_onObservedPrefChange: function(_, prefName) {
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
},
|
||||
|
||||
toString: () => "[object WaterfallView]"
|
||||
});
|
||||
|
|
|
@ -4,19 +4,41 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Details view containing profiler call tree and markers waterfall. Manages
|
||||
* subviews and toggles visibility between them.
|
||||
* Details view containing call trees, flamegraphs and markers waterfall.
|
||||
* Manages subviews and toggles visibility between them.
|
||||
*/
|
||||
let DetailsView = {
|
||||
/**
|
||||
* Name to node+object mapping of subviews.
|
||||
* Name to (node id, view object, actor requirements, pref killswitch)
|
||||
* mapping of subviews.
|
||||
*/
|
||||
components: {
|
||||
"waterfall": { id: "waterfall-view", view: WaterfallView, requires: ["timeline"] },
|
||||
"js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
|
||||
"js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView, requires: ["timeline"] },
|
||||
"memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, requires: ["memory"], pref: "enable-memory" },
|
||||
"memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, requires: ["memory", "timeline"], pref: "enable-memory" }
|
||||
"waterfall": {
|
||||
id: "waterfall-view",
|
||||
view: WaterfallView,
|
||||
requires: ["timeline"]
|
||||
},
|
||||
"js-calltree": {
|
||||
id: "js-calltree-view",
|
||||
view: JsCallTreeView
|
||||
},
|
||||
"js-flamegraph": {
|
||||
id: "js-flamegraph-view",
|
||||
view: JsFlameGraphView,
|
||||
requires: ["timeline"]
|
||||
},
|
||||
"memory-calltree": {
|
||||
id: "memory-calltree-view",
|
||||
view: MemoryCallTreeView,
|
||||
requires: ["memory"],
|
||||
pref: "enable-memory"
|
||||
},
|
||||
"memory-flamegraph": {
|
||||
id: "memory-flamegraph-view",
|
||||
view: MemoryFlameGraphView,
|
||||
requires: ["memory", "timeline"],
|
||||
pref: "enable-memory"
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -66,12 +88,13 @@ let DetailsView = {
|
|||
*/
|
||||
setAvailableViews: Task.async(function* () {
|
||||
let mocks = gFront.getMocksInUse();
|
||||
|
||||
for (let [name, { view, pref, requires }] of Iterator(this.components)) {
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
|
||||
let isRecorded = recording && !recording.isRecording();
|
||||
// View is enabled view prefs
|
||||
let isEnabled = !pref || PerformanceController.getPref(pref);
|
||||
// View is enabled by its corresponding pref
|
||||
let isEnabled = !pref || PerformanceController.getOption(pref);
|
||||
// View is supported by the server actor, and the requried actor is not being mocked
|
||||
let isSupported = !requires || requires.every(r => !mocks[r]);
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@ let OverviewView = {
|
|||
|
||||
// Toggle the initial visibility of memory and framerate graph containers
|
||||
// based off of prefs.
|
||||
$("#memory-overview").hidden = !PerformanceController.getPref("enable-memory");
|
||||
$("#time-framerate").hidden = !PerformanceController.getPref("enable-framerate");
|
||||
$("#memory-overview").hidden = !PerformanceController.getOption("enable-memory");
|
||||
$("#time-framerate").hidden = !PerformanceController.getOption("enable-framerate");
|
||||
|
||||
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
|
@ -55,6 +55,16 @@ let OverviewView = {
|
|||
* Unbinds events.
|
||||
*/
|
||||
destroy: function () {
|
||||
if (this.markersOverview) {
|
||||
this.markersOverview.destroy();
|
||||
}
|
||||
if (this.memoryOverview) {
|
||||
this.memoryOverview.destroy();
|
||||
}
|
||||
if (this.framerateGraph) {
|
||||
this.framerateGraph.destroy();
|
||||
}
|
||||
|
||||
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
|
@ -86,7 +96,7 @@ let OverviewView = {
|
|||
* Sets the time interval selection for all graphs in this overview.
|
||||
*
|
||||
* @param object interval
|
||||
* The { starTime, endTime }, in milliseconds.
|
||||
* The { startTime, endTime }, in milliseconds.
|
||||
*/
|
||||
setTimeInterval: function(interval, options = {}) {
|
||||
if (this.isDisabled()) {
|
||||
|
@ -138,7 +148,8 @@ let OverviewView = {
|
|||
yield this.markersOverview.ready();
|
||||
return true;
|
||||
}
|
||||
this.markersOverview = new MarkersOverview($("#markers-overview"), TIMELINE_BLUEPRINT);
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.markersOverview = new MarkersOverview($("#markers-overview"), blueprint);
|
||||
this.markersOverview.headerHeight = MARKERS_GRAPH_HEADER_HEIGHT;
|
||||
this.markersOverview.rowHeight = MARKERS_GRAPH_ROW_HEIGHT;
|
||||
this.markersOverview.groupPadding = MARKERS_GROUP_VERTICAL_PADDING;
|
||||
|
@ -155,7 +166,7 @@ let OverviewView = {
|
|||
* ready to use, `false` if the graph is disabled.
|
||||
*/
|
||||
_memoryGraphAvailable: Task.async(function *() {
|
||||
if (!PerformanceController.getPref("enable-memory")) {
|
||||
if (!PerformanceController.getOption("enable-memory")) {
|
||||
return false;
|
||||
}
|
||||
if (this.memoryOverview) {
|
||||
|
@ -179,7 +190,7 @@ let OverviewView = {
|
|||
* ready to use, `false` if the graph is disabled.
|
||||
*/
|
||||
_framerateGraphAvailable: Task.async(function *() {
|
||||
if (!PerformanceController.getPref("enable-framerate")) {
|
||||
if (!PerformanceController.getOption("enable-framerate")) {
|
||||
return false;
|
||||
}
|
||||
if (this.framerateGraph) {
|
||||
|
@ -340,14 +351,26 @@ let OverviewView = {
|
|||
* Called whenever a preference in `devtools.performance.ui.` changes. Used
|
||||
* to toggle the visibility of memory and framerate graphs.
|
||||
*/
|
||||
_onPrefChanged: function (_, prefName) {
|
||||
if (prefName === "enable-memory") {
|
||||
$("#memory-overview").hidden = !PerformanceController.getPref("enable-memory");
|
||||
_onPrefChanged: Task.async(function* (_, prefName, prefValue) {
|
||||
switch (prefName) {
|
||||
case "enable-memory": {
|
||||
$("#memory-overview").hidden = !prefValue;
|
||||
break;
|
||||
}
|
||||
case "enable-framerate": {
|
||||
$("#time-framerate").hidden = !prefValue;
|
||||
break;
|
||||
}
|
||||
case "hidden-markers": {
|
||||
if (yield this._markersGraphAvailable()) {
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.markersOverview.setBlueprint(blueprint);
|
||||
this.markersOverview.refresh({ force: true });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prefName === "enable-framerate") {
|
||||
$("#time-framerate").hidden = !PerformanceController.getPref("enable-framerate");
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
toString: () => "[object OverviewView]"
|
||||
};
|
||||
|
|
|
@ -11,6 +11,9 @@ let ToolbarView = {
|
|||
* Sets up the view with event binding.
|
||||
*/
|
||||
initialize: Task.async(function *() {
|
||||
this._onFilterPopupShowing = this._onFilterPopupShowing.bind(this);
|
||||
this._onFilterPopupHiding = this._onFilterPopupHiding.bind(this);
|
||||
this._onHiddenMarkersChanged = this._onHiddenMarkersChanged.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
|
||||
this.optionsView = new OptionsView({
|
||||
|
@ -20,16 +23,87 @@ let ToolbarView = {
|
|||
|
||||
yield this.optionsView.initialize();
|
||||
this.optionsView.on("pref-changed", this._onPrefChanged);
|
||||
|
||||
this._buildMarkersFilterPopup();
|
||||
this._updateHiddenMarkersPopup();
|
||||
$("#performance-filter-menupopup").addEventListener("popupshowing", this._onFilterPopupShowing);
|
||||
$("#performance-filter-menupopup").addEventListener("popuphiding", this._onFilterPopupHiding);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Unbinds events and cleans up view.
|
||||
*/
|
||||
destroy: function () {
|
||||
$("#performance-filter-menupopup").removeEventListener("popupshowing", this._onFilterPopupShowing);
|
||||
$("#performance-filter-menupopup").removeEventListener("popuphiding", this._onFilterPopupHiding);
|
||||
|
||||
this.optionsView.off("pref-changed", this._onPrefChanged);
|
||||
this.optionsView.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the timeline markers filter popup.
|
||||
*/
|
||||
_buildMarkersFilterPopup: function() {
|
||||
for (let [markerName, markerDetails] of Iterator(TIMELINE_BLUEPRINT)) {
|
||||
let menuitem = document.createElement("menuitem");
|
||||
menuitem.setAttribute("closemenu", "none");
|
||||
menuitem.setAttribute("type", "checkbox");
|
||||
menuitem.setAttribute("align", "center");
|
||||
menuitem.setAttribute("flex", "1");
|
||||
menuitem.setAttribute("label", markerDetails.label);
|
||||
menuitem.setAttribute("marker-type", markerName);
|
||||
|
||||
menuitem.addEventListener("command", this._onHiddenMarkersChanged);
|
||||
|
||||
// Style used by pseudo element ::before in performance.inc.css
|
||||
let bulletStyle = `--bullet-bg: ${markerDetails.fill};`
|
||||
bulletStyle += `--bullet-border: ${markerDetails.stroke}`;
|
||||
menuitem.setAttribute("style", bulletStyle);
|
||||
|
||||
$("#performance-filter-menupopup").appendChild(menuitem);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the menu items checked state in the timeline markers filter popup.
|
||||
*/
|
||||
_updateHiddenMarkersPopup: function() {
|
||||
let menuItems = $$("#performance-filter-menupopup menuitem[marker-type]");
|
||||
let hiddenMarkers = PerformanceController.getPref("hidden-markers");
|
||||
|
||||
for (let menuitem of menuItems) {
|
||||
if (~hiddenMarkers.indexOf(menuitem.getAttribute("marker-type"))) {
|
||||
menuitem.removeAttribute("checked");
|
||||
} else {
|
||||
menuitem.setAttribute("checked", "true");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the markers filter popup starts to show.
|
||||
*/
|
||||
_onFilterPopupShowing: function() {
|
||||
$("#filter-button").setAttribute("open", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the markers filter popup starts to hide.
|
||||
*/
|
||||
_onFilterPopupHiding: function() {
|
||||
$("#filter-button").removeAttribute("open");
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a menu item in the markers filter popup is checked or unchecked.
|
||||
*/
|
||||
_onHiddenMarkersChanged: function() {
|
||||
let checkedMenuItems = $$("#performance-filter-menupopup menuitem[marker-type]:not([checked])");
|
||||
let hiddenMarkers = Array.map(checkedMenuItems, e => e.getAttribute("marker-type"));
|
||||
PerformanceController.setPref("hidden-markers", hiddenMarkers);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a preference changes in the underlying OptionsView.
|
||||
* Propogated by the PerformanceController.
|
||||
|
|
|
@ -148,8 +148,6 @@ let PrefObserver = {
|
|||
this.branch.removeObserver("", this);
|
||||
},
|
||||
observe: function(subject, topic, pref) {
|
||||
Prefs.refresh();
|
||||
|
||||
if (pref == "ui.show-platform-data") {
|
||||
RecordingsListView.forceSelect(RecordingsListView.selectedItem);
|
||||
}
|
||||
|
|
|
@ -20,14 +20,23 @@ function test() {
|
|||
"The pref was was correctly changed (2).");
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.enabled", originalPrefValue);
|
||||
info("The pref value was reset.");
|
||||
|
||||
info("The pref value was reset (1).");
|
||||
is(Prefs.foo, !originalPrefValue,
|
||||
"The cached pref value hasn't changed yet.");
|
||||
"The cached pref value hasn't changed yet (1).");
|
||||
|
||||
Prefs.refresh();
|
||||
Services.prefs.setBoolPref("devtools.debugger.enabled", !originalPrefValue);
|
||||
info("The pref value was reset (2).");
|
||||
is(Prefs.foo, !originalPrefValue,
|
||||
"The cached pref value hasn't changed yet (2).");
|
||||
|
||||
Prefs.registerObserver();
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.enabled", originalPrefValue);
|
||||
info("The pref value was reset (3).");
|
||||
is(Prefs.foo, originalPrefValue,
|
||||
"The cached pref value has changed now.");
|
||||
|
||||
Prefs.unregisterObserver();
|
||||
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"Heritage", "ViewHelpers", "WidgetMethods",
|
||||
|
@ -392,25 +393,57 @@ ViewHelpers.L10N.prototype = {
|
|||
* let prefs = new ViewHelpers.Prefs("root.path.to.branch", {
|
||||
* myIntPref: ["Int", "leaf.path.to.my-int-pref"],
|
||||
* myCharPref: ["Char", "leaf.path.to.my-char-pref"],
|
||||
* myJsonPref: ["Json", "leaf.path.to.my-json-pref"]
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* Get/set:
|
||||
* prefs.myCharPref = "foo";
|
||||
* let aux = prefs.myCharPref;
|
||||
*
|
||||
* Observe:
|
||||
* prefs.registerObserver();
|
||||
* prefs.on("pref-changed", (prefName, prefValue) => {
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* @param string aPrefsRoot
|
||||
* The root path to the required preferences branch.
|
||||
* @param object aPrefsObject
|
||||
* @param object aPrefsBlueprint
|
||||
* An object containing { accessorName: [prefType, prefName] } keys.
|
||||
*/
|
||||
ViewHelpers.Prefs = function(aPrefsRoot = "", aPrefsObject = {}) {
|
||||
this._root = aPrefsRoot;
|
||||
this._cache = new Map();
|
||||
ViewHelpers.Prefs = function(aPrefsRoot = "", aPrefsBlueprint = {}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
for (let accessorName in aPrefsObject) {
|
||||
let [prefType, prefName] = aPrefsObject[accessorName];
|
||||
this.map(accessorName, prefType, prefName);
|
||||
this._cache = new Map();
|
||||
let self = this;
|
||||
|
||||
for (let [accessorName, [prefType, prefName]] of Iterator(aPrefsBlueprint)) {
|
||||
this._map(accessorName, prefType, aPrefsRoot, prefName);
|
||||
}
|
||||
|
||||
let observer = {
|
||||
register: function() {
|
||||
this.branch = Services.prefs.getBranch(aPrefsRoot + ".");
|
||||
this.branch.addObserver("", this, false);
|
||||
},
|
||||
unregister: function() {
|
||||
this.branch.removeObserver("", this);
|
||||
},
|
||||
observe: function(_, __, aPrefName) {
|
||||
// If this particular pref isn't handled by the blueprint object,
|
||||
// even though it's in the specified branch, ignore it.
|
||||
let accessor = self._accessor(aPrefsBlueprint, aPrefName);
|
||||
if (!(accessor in self)) {
|
||||
return;
|
||||
}
|
||||
self._cache.delete(aPrefName);
|
||||
self.emit("pref-changed", accessor, self[accessor]);
|
||||
}
|
||||
};
|
||||
|
||||
this.registerObserver = () => observer.register();
|
||||
this.unregisterObserver = () => observer.unregister();
|
||||
};
|
||||
|
||||
ViewHelpers.Prefs.prototype = {
|
||||
|
@ -418,15 +451,16 @@ ViewHelpers.Prefs.prototype = {
|
|||
* Helper method for getting a pref value.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string aPrefsRoot
|
||||
* @param string aPrefName
|
||||
* @return any
|
||||
*/
|
||||
_get: function(aType, aPrefName) {
|
||||
_get: function(aType, aPrefsRoot, aPrefName) {
|
||||
let cachedPref = this._cache.get(aPrefName);
|
||||
if (cachedPref !== undefined) {
|
||||
return cachedPref;
|
||||
}
|
||||
let value = Services.prefs["get" + aType + "Pref"](aPrefName);
|
||||
let value = Services.prefs["get" + aType + "Pref"]([aPrefsRoot, aPrefName].join("."));
|
||||
this._cache.set(aPrefName, value);
|
||||
return value;
|
||||
},
|
||||
|
@ -435,11 +469,12 @@ ViewHelpers.Prefs.prototype = {
|
|||
* Helper method for setting a pref value.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string aPrefsRoot
|
||||
* @param string aPrefName
|
||||
* @param any aValue
|
||||
*/
|
||||
_set: function(aType, aPrefName, aValue) {
|
||||
Services.prefs["set" + aType + "Pref"](aPrefName, aValue);
|
||||
_set: function(aType, aPrefsRoot, aPrefName, aValue) {
|
||||
Services.prefs["set" + aType + "Pref"]([aPrefsRoot, aPrefName].join("."), aValue);
|
||||
this._cache.set(aPrefName, aValue);
|
||||
},
|
||||
|
||||
|
@ -450,26 +485,36 @@ ViewHelpers.Prefs.prototype = {
|
|||
*
|
||||
* @param string aAccessorName
|
||||
* @param string aType
|
||||
* @param string aPrefsRoot
|
||||
* @param string aPrefName
|
||||
* @param array aSerializer
|
||||
*/
|
||||
map: function(aAccessorName, aType, aPrefName, aSerializer = { in: e => e, out: e => e }) {
|
||||
_map: function(aAccessorName, aType, aPrefsRoot, aPrefName, aSerializer = { in: e => e, out: e => e }) {
|
||||
if (aPrefName in this) {
|
||||
throw new Error(`Can't use ${aPrefName} because it's already a property.`);
|
||||
}
|
||||
if (aType == "Json") {
|
||||
this.map(aAccessorName, "Char", aPrefName, { in: JSON.parse, out: JSON.stringify });
|
||||
this._map(aAccessorName, "Char", aPrefsRoot, aPrefName, { in: JSON.parse, out: JSON.stringify });
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, aAccessorName, {
|
||||
get: () => aSerializer.in(this._get(aType, [this._root, aPrefName].join("."))),
|
||||
set: (e) => this._set(aType, [this._root, aPrefName].join("."), aSerializer.out(e))
|
||||
get: () => aSerializer.in(this._get(aType, aPrefsRoot, aPrefName)),
|
||||
set: (e) => this._set(aType, aPrefsRoot, aPrefName, aSerializer.out(e))
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all the cached preferences' values.
|
||||
* Finds the accessor in this object for the provided property name,
|
||||
* based on the blueprint object used in the constructor.
|
||||
*/
|
||||
refresh: function() {
|
||||
this._cache.clear();
|
||||
_accessor: function(aPrefsBlueprint, aPrefName) {
|
||||
for (let [accessorName, [, prefName]] of Iterator(aPrefsBlueprint)) {
|
||||
if (prefName == aPrefName) {
|
||||
return accessorName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -138,9 +138,9 @@ function finishUp() {
|
|||
inspector.destroy();
|
||||
inspector = null;
|
||||
completer = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
progress = null;
|
||||
progressDiv = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ support-files =
|
|||
head.js
|
||||
|
||||
[browser_timeline_aaa_run_first_leaktest.js]
|
||||
[browser_timeline_filters.js]
|
||||
[browser_timeline_overview-initial-selection-01.js]
|
||||
[browser_timeline_overview-initial-selection-02.js]
|
||||
[browser_timeline_overview-update.js]
|
||||
|
|
|
@ -49,9 +49,7 @@ add_task(function*() {
|
|||
ok(!isVisible($("#web-audio-editor-tabs")),
|
||||
"InspectorView tabs are still hidden.");
|
||||
|
||||
let nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield nodeSet;
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
|
||||
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
|
||||
"Empty message hides even when loading node while open.");
|
||||
|
|
|
@ -1,63 +1,56 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
|
||||
/**
|
||||
* Test that the WebAudioInspector's Width is saved as
|
||||
* a preference
|
||||
*/
|
||||
|
||||
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
|
||||
reload(target);
|
||||
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
get3(gFront, "create-node"),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
let nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
|
||||
ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
|
||||
|
||||
|
||||
// Open inspector pane
|
||||
$("#inspector-pane-toggle").click();
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
|
||||
|
||||
|
||||
let newInspectorWidth = 500;
|
||||
|
||||
|
||||
// Setting width to new_inspector_width
|
||||
$("#web-audio-inspector").setAttribute("width", newInspectorWidth);
|
||||
reload(target);
|
||||
|
||||
|
||||
//Width should be 500 after reloading
|
||||
[actors] = yield Promise.all([
|
||||
get3(gFront, "create-node"),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
|
||||
// Open inspector pane
|
||||
$("#inspector-pane-toggle").click();
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
|
||||
|
||||
let nodeSet = Promise.all([
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
once(panelWin, EVENTS.UI_PROPERTIES_TAB_RENDERED),
|
||||
once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED)
|
||||
]);
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield nodeSet;
|
||||
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
|
||||
// Getting the width of the audio inspector
|
||||
let width = $("#web-audio-inspector").getAttribute("width");
|
||||
|
||||
|
||||
is(width, newInspectorWidth, "WebAudioEditor's Inspector width should be saved as a preference");
|
||||
|
||||
|
||||
yield teardown(target);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,12 +29,7 @@ add_task(function*() {
|
|||
"InspectorView tabs view should be hidden when no node's selected.");
|
||||
|
||||
// Wait for the node to be set as well as the inspector to come fully into the view
|
||||
let nodeSet = Promise.all([
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
|
||||
]);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield nodeSet;
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]), true);
|
||||
|
||||
ok(InspectorView.isVisible(), "InspectorView shown once node selected.");
|
||||
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
|
||||
|
@ -45,9 +40,7 @@ add_task(function*() {
|
|||
is($("#web-audio-editor-tabs").selectedIndex, 0,
|
||||
"default tab selected should be the parameters tab.");
|
||||
|
||||
nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield nodeSet;
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
|
||||
yield teardown(target);
|
||||
});
|
||||
|
|
|
@ -110,6 +110,7 @@ function once(aTarget, aEventName, aUseCapture = false) {
|
|||
if ((add in aTarget) && (remove in aTarget)) {
|
||||
aTarget[add](aEventName, function onEvent(...aArgs) {
|
||||
aTarget[remove](aEventName, onEvent, aUseCapture);
|
||||
info("Got event: '" + aEventName + "' on " + aTarget + ".");
|
||||
deferred.resolve(...aArgs);
|
||||
}, aUseCapture);
|
||||
break;
|
||||
|
@ -352,13 +353,15 @@ function wait (n) {
|
|||
|
||||
/**
|
||||
* Clicks a graph node based on actorID or passing in an element.
|
||||
* Returns a promise that resolves once
|
||||
* UI_INSPECTOR_NODE_SET is fired.
|
||||
* Returns a promise that resolves once UI_INSPECTOR_NODE_SET is fired and
|
||||
* the tabs have rendered, completing all RDP requests for the node.
|
||||
*/
|
||||
function clickGraphNode (panelWin, el, waitForToggle = false) {
|
||||
let { promise, resolve } = Promise.defer();
|
||||
let promises = [
|
||||
once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET)
|
||||
once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
once(panelWin, panelWin.EVENTS.UI_PROPERTIES_TAB_RENDERED),
|
||||
once(panelWin, panelWin.EVENTS.UI_AUTOMATION_TAB_RENDERED)
|
||||
];
|
||||
|
||||
if (waitForToggle) {
|
||||
|
|
|
@ -39,7 +39,7 @@ let test = asyncTest(function* () {
|
|||
],
|
||||
});
|
||||
|
||||
testClickOpenNewTab(hud, results);
|
||||
yield testClickOpenNewTab(hud, results);
|
||||
});
|
||||
|
||||
function pushPrefEnv()
|
||||
|
@ -57,18 +57,5 @@ function testClickOpenNewTab(hud, results) {
|
|||
let warningNode = results[0].clickableElements[0];
|
||||
ok(warningNode, "link element");
|
||||
ok(warningNode.classList.contains("learn-more-link"), "link class name");
|
||||
|
||||
// Invoke the click event and check if a new tab would open to the correct page.
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == LEARN_MORE_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
|
||||
}
|
||||
|
|
|
@ -43,11 +43,11 @@ let test = asyncTest(function* () {
|
|||
],
|
||||
});
|
||||
|
||||
testClickOpenNewTab(hud, results[0]);
|
||||
yield testClickOpenNewTab(hud, results[0]);
|
||||
|
||||
let results2 = yield mixedContentOverrideTest2(hud, browser);
|
||||
|
||||
testClickOpenNewTab(hud, results2[0]);
|
||||
yield testClickOpenNewTab(hud, results2[0]);
|
||||
});
|
||||
|
||||
function pushPrefEnv()
|
||||
|
@ -118,19 +118,5 @@ function testClickOpenNewTab(hud, match) {
|
|||
let warningNode = match.clickableElements[0];
|
||||
ok(warningNode, "link element");
|
||||
ok(warningNode.classList.contains("learn-more-link"), "link class name");
|
||||
|
||||
// Invoke the click event and check if a new tab would
|
||||
// open to the correct page.
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == LEARN_MORE_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Connection closed");
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-632275-getters.html";
|
||||
|
||||
let getterValue = null;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
const TEST_URI = "data:text/html;charset=utf8,Web Console mixed content test";
|
||||
const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
|
||||
|
||||
let test = asyncTest(function* () {
|
||||
Services.prefs.setBoolPref("security.mixed_content.block_display_content", false);
|
||||
|
@ -27,45 +28,33 @@ let test = asyncTest(function* () {
|
|||
Services.prefs.clearUserPref("security.mixed_content.block_active_content");
|
||||
});
|
||||
|
||||
function testMixedContent(hud) {
|
||||
let testMixedContent = Task.async(function*(hud) {
|
||||
content.location = TEST_HTTPS_URI;
|
||||
|
||||
return waitForMessages({
|
||||
let results = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "example.com",
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_WARNING,
|
||||
}],
|
||||
}).then((results) => {
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "page load logged");
|
||||
ok(msg.classList.contains("mixed-content"), ".mixed-content element");
|
||||
|
||||
let link = msg.querySelector(".learn-more-link");
|
||||
ok(link, "mixed content link element");
|
||||
is(link.textContent, "[Mixed Content]", "link text is accurate");
|
||||
|
||||
let oldOpenLink = hud.openLink;
|
||||
let linkOpened = false;
|
||||
hud.openLink = (url) => {
|
||||
is(url, "https://developer.mozilla.org/docs/Security/MixedContent",
|
||||
"url opened");
|
||||
linkOpened = true;
|
||||
};
|
||||
|
||||
EventUtils.synthesizeMouse(link, 2, 2, {}, link.ownerDocument.defaultView);
|
||||
|
||||
ok(linkOpened, "clicking the Mixed Content link opened a page");
|
||||
|
||||
hud.openLink = oldOpenLink;
|
||||
|
||||
ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
|
||||
|
||||
hud.setFilterState("netwarn", false);
|
||||
|
||||
ok(msg.classList.contains("filtered-by-type"), "message is filtered");
|
||||
|
||||
hud.setFilterState("netwarn", true);
|
||||
});
|
||||
}
|
||||
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "page load logged");
|
||||
ok(msg.classList.contains("mixed-content"), ".mixed-content element");
|
||||
|
||||
let link = msg.querySelector(".learn-more-link");
|
||||
ok(link, "mixed content link element");
|
||||
is(link.textContent, "[Mixed Content]", "link text is accurate");
|
||||
|
||||
yield simulateMessageLinkClick(link, LEARN_MORE_URI);
|
||||
|
||||
ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
|
||||
|
||||
hud.setFilterState("netwarn", false);
|
||||
|
||||
ok(msg.classList.contains("filtered-by-type"), "message is filtered");
|
||||
|
||||
hud.setFilterState("netwarn", true);
|
||||
});
|
||||
|
|
|
@ -40,26 +40,12 @@ let test = asyncTest(function* () {
|
|||
],
|
||||
});
|
||||
|
||||
testClickOpenNewTab(hud, result);
|
||||
yield testClickOpenNewTab(hud, result);
|
||||
});
|
||||
|
||||
function testClickOpenNewTab(hud, [result]) {
|
||||
let msg = [...result.matched][0];
|
||||
let warningNode = msg.querySelector(".learn-more-link");
|
||||
ok(warningNode, "learn more link");
|
||||
|
||||
// Invoke the click event and check if a new tab would open to the correct
|
||||
// page
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == INSECURE_PASSWORDS_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Insecure Passwords Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
return simulateMessageLinkClick(warningNode, INSECURE_PASSWORDS_URI);
|
||||
}
|
||||
|
|
|
@ -6,45 +6,30 @@ const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/t
|
|||
const HSTS_INVALID_HEADER_MSG = "The site specified an invalid Strict-Transport-Security header.";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
|
||||
|
||||
function test()
|
||||
{
|
||||
loadTab(TEST_URI).then(() => {
|
||||
openConsole().then((hud) => {
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "Invalid HSTS header error displayed successfully",
|
||||
text: HSTS_INVALID_HEADER_MSG,
|
||||
category: CATEGORY_SECURITY,
|
||||
severity: SEVERITY_WARNING,
|
||||
objects: true,
|
||||
},
|
||||
],
|
||||
}).then((results) => testClickOpenNewTab(hud, results));
|
||||
})
|
||||
let test = asyncTest(function* () {
|
||||
yield loadTab(TEST_URI);
|
||||
|
||||
let hud = yield openConsole();
|
||||
|
||||
let results = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "Invalid HSTS header error displayed successfully",
|
||||
text: HSTS_INVALID_HEADER_MSG,
|
||||
category: CATEGORY_SECURITY,
|
||||
severity: SEVERITY_WARNING,
|
||||
objects: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
yield testClickOpenNewTab(hud, results);
|
||||
});
|
||||
|
||||
function testClickOpenNewTab(hud, results) {
|
||||
let warningNode = results[0].clickableElements[0];
|
||||
ok(warningNode, "link element");
|
||||
ok(warningNode.classList.contains("learn-more-link"), "link class name");
|
||||
|
||||
// Invoke the click event and check if a new tab would
|
||||
// open to the correct page.
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == LEARN_MORE_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
|
||||
finishTest();
|
||||
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
|
||||
}
|
||||
|
|
|
@ -1646,3 +1646,32 @@ function getSourceActor(aSources, aURL) {
|
|||
let item = aSources.getItemForAttachment(a => a.source.url === aURL);
|
||||
return item && item.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that clicking on a link from a popup notification message tries to
|
||||
* open the expected URL.
|
||||
*/
|
||||
function simulateMessageLinkClick(element, expectedLink) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
// Invoke the click event and check if a new tab would
|
||||
// open to the correct page.
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(link) {
|
||||
if (link == expectedLink) {
|
||||
ok(true, "Clicking the message link opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
let event = new MouseEvent("click", {
|
||||
detail: 1,
|
||||
button: 0,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
element.dispatchEvent(event);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
|
|
@ -153,6 +153,15 @@ function loadAddonManager() {
|
|||
startupManager();
|
||||
}
|
||||
|
||||
// Starts the addon manager without creating app info. We can't directly use
|
||||
// |loadAddonManager| defined above in test_conditions.js as it would make the test fail.
|
||||
function startAddonManagerOnly() {
|
||||
let addonManager = Cc["@mozilla.org/addons/integration;1"]
|
||||
.getService(Ci.nsIObserver)
|
||||
.QueryInterface(Ci.nsITimerCallback);
|
||||
addonManager.observe(null, "addons-startup", null);
|
||||
}
|
||||
|
||||
function getExperimentAddons(previous=false) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
|
|
|
@ -5,8 +5,14 @@
|
|||
|
||||
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryPing.jsm", this);
|
||||
Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gDatareportingService",
|
||||
() => Cc["@mozilla.org/datareporting/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject);
|
||||
|
||||
const FILE_MANIFEST = "experiments.manifest";
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
@ -45,6 +51,17 @@ function applicableFromManifestData(data, policy) {
|
|||
return entry.isApplicable();
|
||||
}
|
||||
|
||||
function initialiseTelemetry() {
|
||||
// Send the needed startup notifications to the datareporting service
|
||||
// to ensure that it has been initialized.
|
||||
if ("@mozilla.org/datareporting/service;1" in Cc) {
|
||||
gDatareportingService.observe(null, "app-startup", null);
|
||||
gDatareportingService.observe(null, "profile-after-change", null);
|
||||
}
|
||||
|
||||
return TelemetryPing.setup().then(TelemetrySession.setup);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
@ -52,7 +69,8 @@ function run_test() {
|
|||
add_task(function* test_setup() {
|
||||
createAppInfo();
|
||||
gProfileDir = do_get_profile();
|
||||
yield TelemetrySession.setup();
|
||||
startAddonManagerOnly();
|
||||
yield initialiseTelemetry();
|
||||
gPolicy = new Experiments.Policy();
|
||||
|
||||
gReporter = yield getReporter("json_payload_simple");
|
||||
|
@ -312,5 +330,5 @@ add_task(function* test_times() {
|
|||
});
|
||||
|
||||
add_task(function* test_shutdown() {
|
||||
yield TelemetrySession.shutdown();
|
||||
yield TelemetrySession.shutdown(false);
|
||||
});
|
||||
|
|
|
@ -64,9 +64,13 @@
|
|||
- on the "+" (new tab) button for a profile when a selection is available. -->
|
||||
<!ENTITY profilerUI.newtab.tooltiptext "Add new tab from selection">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.toolbar.filter.tooltiptext): This string
|
||||
- is displayed next to the filter button-->
|
||||
<!ENTITY profilerUI.options.filter.tooltiptext "Select what data to display in the timeline">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.options.tooltiptext): This is the tooltip
|
||||
- for the options button. -->
|
||||
<!ENTITY profilerUI.options.tooltiptext "Configure performance preferences.">
|
||||
<!ENTITY profilerUI.options.gear.tooltiptext "Configure performance preferences.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.invertTree): This is the label shown next to
|
||||
- a checkbox that inverts and un-inverts the profiler's call tree. -->
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
-moz-border-end-color: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
#performance-toolbar-control-other {
|
||||
-moz-padding-end: 5px;
|
||||
}
|
||||
|
||||
#performance-toolbar-controls-detail-views > toolbarbutton {
|
||||
min-width: 0;
|
||||
}
|
||||
|
@ -34,6 +38,31 @@
|
|||
-moz-padding-end: 8px;
|
||||
}
|
||||
|
||||
#filter-button {
|
||||
list-style-image: url(timeline-filter.svg#filter);
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
#filter-button[disabled] {
|
||||
list-style-image: url(timeline-filter.svg#filter-disabled);
|
||||
}
|
||||
|
||||
#filter-button[open] {
|
||||
list-style-image: url(timeline-filter.svg#filter-open);
|
||||
}
|
||||
|
||||
#performance-filter-menupopup > menuitem:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin: 0 8px;
|
||||
border: 1px solid;
|
||||
border-radius: 1px;
|
||||
background-color: var(--bullet-bg);
|
||||
border-color: var(--bullet-border);
|
||||
}
|
||||
|
||||
/* Recording Notice */
|
||||
|
||||
#performance-view .notice-container {
|
||||
|
|
|
@ -36,6 +36,7 @@ tier_UPDATE_PACKAGING = update-packaging
|
|||
tier_PRETTY_UPDATE_PACKAGING = pretty-update-packaging
|
||||
tier_UPLOAD_SYMBOLS = uploadsymbols
|
||||
tier_UPLOAD = upload
|
||||
tier_SDK = sdk
|
||||
|
||||
# Automation build steps. Everything in MOZ_AUTOMATION_TIERS also gets used in
|
||||
# TIERS for mach display. As such, the MOZ_AUTOMATION_TIERS are roughly sorted
|
||||
|
@ -55,6 +56,7 @@ moz_automation_symbols = \
|
|||
L10N_CHECK \
|
||||
PRETTY_L10N_CHECK \
|
||||
UPLOAD \
|
||||
SDK \
|
||||
$(NULL)
|
||||
MOZ_AUTOMATION_TIERS := $(foreach sym,$(moz_automation_symbols),$(if $(filter 1,$(MOZ_AUTOMATION_$(sym))),$(tier_$(sym))))
|
||||
|
||||
|
@ -76,15 +78,17 @@ automation/upload: automation/package
|
|||
automation/upload: automation/package-tests
|
||||
automation/upload: automation/buildsymbols
|
||||
automation/upload: automation/update-packaging
|
||||
automation/upload: automation/sdk
|
||||
|
||||
# automation/{pretty-}package should depend on build (which is implicit due to
|
||||
# the way client.mk invokes automation/build), but buildsymbols changes the
|
||||
# binaries/libs, and that's what we package/test.
|
||||
automation/pretty-package: automation/buildsymbols
|
||||
|
||||
# The installer and packager both run stage-package, and may conflict
|
||||
# The installer, sdk and packager all run stage-package, and may conflict
|
||||
# with each other.
|
||||
automation/installer: automation/package
|
||||
automation/sdk: automation/installer automation/package
|
||||
|
||||
# The 'pretty' versions of targets run before the regular ones to avoid
|
||||
# conflicts in writing to the same files.
|
||||
|
|
|
@ -17,6 +17,7 @@ mk_add_options "export MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}"
|
|||
mk_add_options "export MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-0}"
|
||||
mk_add_options "export MOZ_AUTOMATION_UPLOAD=${MOZ_AUTOMATION_UPLOAD-1}"
|
||||
mk_add_options "export MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-0}"
|
||||
mk_add_options "export MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-0}"
|
||||
|
||||
# If we are also building with MOZ_PKG_PRETTYNAMES, set the corresponding
|
||||
# stages.
|
||||
|
|
|
@ -252,16 +252,16 @@ SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
|
|||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
if (mUpdating) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
if (IsNaN(mMediaSource->Duration()) ||
|
||||
aStart < 0 || aStart > mMediaSource->Duration() ||
|
||||
aEnd <= aStart || IsNaN(aEnd)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
return;
|
||||
}
|
||||
if (mUpdating) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
|
||||
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
|
||||
}
|
||||
|
|
|
@ -40,5 +40,5 @@ interface SourceBuffer : EventTarget {
|
|||
[Throws]
|
||||
void abort();
|
||||
[Throws]
|
||||
void remove(double start, double end);
|
||||
void remove(double start, unrestricted double end);
|
||||
};
|
||||
|
|
|
@ -138,14 +138,18 @@ public:
|
|||
sink->Close();
|
||||
|
||||
RefPtr<ID2D1BitmapBrush> brush;
|
||||
rt->CreateBitmapBrush(mOldSurfBitmap, D2D1::BitmapBrushProperties(), D2D1::BrushProperties(), byRef(brush));
|
||||
HRESULT hr = rt->CreateBitmapBrush(mOldSurfBitmap, D2D1::BitmapBrushProperties(), D2D1::BrushProperties(), byRef(brush));
|
||||
if (FAILED(hr)) {
|
||||
gfxCriticalError(CriticalLog::DefaultOptions(false)) << "[D2D] CreateBitmapBrush failure " << hexa(hr);
|
||||
return;
|
||||
}
|
||||
|
||||
rt->FillGeometry(invClippedArea, brush);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
DrawTargetD2D *mDT;
|
||||
DrawTargetD2D *mDT;
|
||||
|
||||
// If we have an operator unbound by the source, this will contain a bitmap
|
||||
// with the old dest surface data.
|
||||
|
@ -308,7 +312,12 @@ DrawTargetD2D::GetBitmapForSurface(SourceSurface *aSurface,
|
|||
|
||||
D2D1_BITMAP_PROPERTIES props =
|
||||
D2D1::BitmapProperties(D2DPixelFormat(srcSurf->GetFormat()));
|
||||
mRT->CreateBitmap(D2D1::SizeU(UINT32(sourceRect.width), UINT32(sourceRect.height)), data, stride, props, byRef(bitmap));
|
||||
HRESULT hr = mRT->CreateBitmap(D2D1::SizeU(UINT32(sourceRect.width), UINT32(sourceRect.height)), data, stride, props, byRef(bitmap));
|
||||
if (FAILED(hr)) {
|
||||
IntSize size(sourceRect.width, sourceRect.height);
|
||||
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(size))) << "[D2D] 1CreateBitmap failure " << size << " Code: " << hexa(hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// subtract the integer part leaving the fractional part
|
||||
aSource.x -= (uint32_t)aSource.x;
|
||||
|
@ -2415,14 +2424,17 @@ DrawTargetD2D::CreateBrushForPattern(const Pattern &aPattern, Float aAlpha)
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
mRT->CreateBitmapBrush(bitmap,
|
||||
|
||||
HRESULT hr = mRT->CreateBitmapBrush(bitmap,
|
||||
D2D1::BitmapBrushProperties(D2DExtend(pat->mExtendMode),
|
||||
D2DExtend(pat->mExtendMode),
|
||||
D2DFilter(pat->mFilter)),
|
||||
D2D1::BrushProperties(aAlpha, D2DMatrix(mat)),
|
||||
byRef(bmBrush));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
gfxCriticalError() << "[D2D] 1CreateBitmapBrush failure: " << hexa(hr);
|
||||
return nullptr;
|
||||
}
|
||||
return bmBrush.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -827,7 +827,7 @@ DrawTargetD2D1::Init(const IntSize &aSize, SurfaceFormat aFormat)
|
|||
hr = mDC->CreateBitmap(D2DIntSize(aSize), nullptr, 0, props, (ID2D1Bitmap1**)byRef(mTempBitmap));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
gfxCriticalError() << "[D2D1.1] failed to create new TempBitmap " << aSize << " Code: " << hexa(hr);
|
||||
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) << "[D2D1.1] failed to create new TempBitmap " << aSize << " Code: " << hexa(hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -965,7 +965,13 @@ DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern)
|
|||
}
|
||||
|
||||
RefPtr<ID2D1Bitmap> tmpBitmap;
|
||||
mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), byRef(tmpBitmap));
|
||||
HRESULT hr = mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), byRef(tmpBitmap));
|
||||
if (FAILED(hr)) {
|
||||
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(mSize))) << "[D2D1.1] 5CreateBitmap failure " << mSize << " Code: " << hexa(hr);
|
||||
// For now, crash in this scenario; this should happen because tmpBitmap is
|
||||
// null and CopyFromBitmap call below dereferences it.
|
||||
// return;
|
||||
}
|
||||
|
||||
// This flush is important since the copy method will not know about the context drawing to the surface.
|
||||
// We also need to pop all the clips to make sure any drawn content will have made it to the final bitmap.
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace JS {
|
|||
class JS_PUBLIC_API(ProfilingFrameIterator)
|
||||
{
|
||||
JSRuntime *rt_;
|
||||
uint32_t sampleBufferGen_;
|
||||
js::Activation *activation_;
|
||||
|
||||
// When moving past a JitActivation, we need to save the prevJitTop
|
||||
|
@ -68,6 +69,10 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
|
|||
|
||||
void settle();
|
||||
|
||||
bool hasSampleBufferGen() const {
|
||||
return sampleBufferGen_ != UINT32_MAX;
|
||||
}
|
||||
|
||||
public:
|
||||
struct RegisterState
|
||||
{
|
||||
|
@ -77,7 +82,8 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
|
|||
void *lr;
|
||||
};
|
||||
|
||||
ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state);
|
||||
ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state,
|
||||
uint32_t sampleBufferGen = UINT32_MAX);
|
||||
~ProfilingFrameIterator();
|
||||
void operator++();
|
||||
bool done() const { return !activation_; }
|
||||
|
@ -117,6 +123,18 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
|
|||
bool isJit() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* After each sample run, this method should be called with the latest sample
|
||||
* buffer generation, and the lapCount. It will update corresponding fields on
|
||||
* JSRuntime.
|
||||
*
|
||||
* See fields |profilerSampleBufferGen|, |profilerSampleBufferLapCount| on
|
||||
* JSRuntime for documentation about what these values are used for.
|
||||
*/
|
||||
JS_FRIEND_API(void)
|
||||
UpdateJSRuntimeProfilerSampleBufferGen(JSRuntime *runtime, uint32_t generation,
|
||||
uint32_t lapCount);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
#endif /* js_ProfilingFrameIterator_h */
|
||||
|
|
|
@ -2203,7 +2203,7 @@ js::LookupAsmJSModuleInCache(ExclusiveContext *cx,
|
|||
|
||||
uint32_t srcStart = parser.pc->maybeFunction->pn_body->pn_pos.begin;
|
||||
uint32_t srcBodyStart = parser.tokenStream.currentToken().pos.end;
|
||||
bool strict = parser.pc->sc->strict && !parser.pc->sc->hasExplicitUseStrict();
|
||||
bool strict = parser.pc->sc->strict() && !parser.pc->sc->hasExplicitUseStrict();
|
||||
|
||||
// usesSignalHandlers will be clobbered when deserializing
|
||||
ScopedJSDeletePtr<AsmJSModule> module(
|
||||
|
|
|
@ -1548,7 +1548,7 @@ class MOZ_STACK_CLASS ModuleCompiler
|
|||
// "use strict" should be added to the source if we are in an implicit
|
||||
// strict context, see also comment above addUseStrict in
|
||||
// js::FunctionToString.
|
||||
bool strict = parser_.pc->sc->strict && !parser_.pc->sc->hasExplicitUseStrict();
|
||||
bool strict = parser_.pc->sc->strict() && !parser_.pc->sc->hasExplicitUseStrict();
|
||||
module_ = cx_->new_<AsmJSModule>(parser_.ss, srcStart, srcBodyStart, strict,
|
||||
cx_->canUseSignalHandlers());
|
||||
if (!module_)
|
||||
|
|
|
@ -65,6 +65,14 @@ class SplayTree
|
|||
return !root;
|
||||
}
|
||||
|
||||
T *maybeLookup(const T &v)
|
||||
{
|
||||
if (!root)
|
||||
return nullptr;
|
||||
Node *last = lookup(v);
|
||||
return (C::compare(v, last->item) == 0) ? &(last->item) : nullptr;
|
||||
}
|
||||
|
||||
bool contains(const T &v, T *res)
|
||||
{
|
||||
if (!root)
|
||||
|
|
|
@ -247,9 +247,9 @@ UpdateDepth(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t target)
|
|||
static bool
|
||||
CheckStrictOrSloppy(BytecodeEmitter *bce, JSOp op)
|
||||
{
|
||||
if (IsCheckStrictOp(op) && !bce->sc->strict)
|
||||
if (IsCheckStrictOp(op) && !bce->sc->strict())
|
||||
return false;
|
||||
if (IsCheckSloppyOp(op) && bce->sc->strict)
|
||||
if (IsCheckSloppyOp(op) && bce->sc->strict())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1527,11 +1527,11 @@ StrictifySetNameOp(JSOp op, BytecodeEmitter *bce)
|
|||
{
|
||||
switch (op) {
|
||||
case JSOP_SETNAME:
|
||||
if (bce->sc->strict)
|
||||
if (bce->sc->strict())
|
||||
op = JSOP_STRICTSETNAME;
|
||||
break;
|
||||
case JSOP_SETGNAME:
|
||||
if (bce->sc->strict)
|
||||
if (bce->sc->strict())
|
||||
op = JSOP_STRICTSETGNAME;
|
||||
break;
|
||||
default:;
|
||||
|
@ -1664,7 +1664,7 @@ TryConvertFreeName(BytecodeEmitter *bce, ParseNode *pn)
|
|||
// worth the trouble for doubly-nested eval code. So we conservatively
|
||||
// approximate. If the outer eval code is strict, then this eval code will
|
||||
// be: thus, don't optimize if we're compiling strict code inside an eval.
|
||||
if (bce->insideEval && bce->sc->strict)
|
||||
if (bce->insideEval && bce->sc->strict())
|
||||
return false;
|
||||
|
||||
JSOp op;
|
||||
|
@ -2233,7 +2233,7 @@ BytecodeEmitter::reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...)
|
|||
|
||||
va_list args;
|
||||
va_start(args, errorNumber);
|
||||
bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict,
|
||||
bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(),
|
||||
errorNumber, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
|
@ -2458,7 +2458,7 @@ EmitPropIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce)
|
|||
return false;
|
||||
}
|
||||
|
||||
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
if (!EmitAtomOp(cx, pn->pn_kid, setOp, bce)) // N? N+1
|
||||
return false;
|
||||
if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT
|
||||
|
@ -2581,7 +2581,7 @@ EmitElemIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce)
|
|||
return false;
|
||||
}
|
||||
|
||||
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
if (!EmitElemOpBase(cx, bce, setOp)) // N? N+1
|
||||
return false;
|
||||
if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT
|
||||
|
@ -3443,7 +3443,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *targ
|
|||
return false;
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0)
|
||||
return false;
|
||||
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
if (!EmitAtomOp(cx, target, setOp, bce))
|
||||
return false;
|
||||
break;
|
||||
|
@ -3454,7 +3454,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *targ
|
|||
// See the comment at `case PNK_DOT:` above. This case,
|
||||
// `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP
|
||||
// is emitted by EmitElemOperands.
|
||||
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
if (!EmitElemOp(cx, target, setOp, bce))
|
||||
return false;
|
||||
break;
|
||||
|
@ -4215,7 +4215,7 @@ EmitAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp
|
|||
break;
|
||||
case PNK_DOT:
|
||||
{
|
||||
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
if (!EmitIndexOp(cx, setOp, atomIndex, bce))
|
||||
return false;
|
||||
break;
|
||||
|
@ -4226,7 +4226,7 @@ EmitAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp
|
|||
break;
|
||||
case PNK_ELEM:
|
||||
{
|
||||
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
if (Emit1(cx, bce, setOp) < 0)
|
||||
return false;
|
||||
break;
|
||||
|
@ -5408,7 +5408,7 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
|||
|
||||
if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals())
|
||||
funbox->setMightAliasLocals(); // inherit mightAliasLocals from parent
|
||||
MOZ_ASSERT_IF(outersc->strict, funbox->strict);
|
||||
MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript);
|
||||
|
||||
// Inherit most things (principals, version, etc) from the parent.
|
||||
Rooted<JSScript*> parent(cx, bce->script);
|
||||
|
@ -6034,7 +6034,7 @@ EmitStatement(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
|||
// later in the script, because such statements are misleading.
|
||||
const char *directive = nullptr;
|
||||
if (atom == cx->names().useStrict) {
|
||||
if (!bce->sc->strict)
|
||||
if (!bce->sc->strictScript)
|
||||
directive = js_useStrict_str;
|
||||
} else if (atom == cx->names().useAsm) {
|
||||
if (bce->sc->isFunctionBox()) {
|
||||
|
@ -6076,14 +6076,14 @@ EmitDelete(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
|||
break;
|
||||
case PNK_DOT:
|
||||
{
|
||||
JSOp delOp = bce->sc->strict ? JSOP_STRICTDELPROP : JSOP_DELPROP;
|
||||
JSOp delOp = bce->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
|
||||
if (!EmitPropOp(cx, pn2, delOp, bce))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case PNK_ELEM:
|
||||
{
|
||||
JSOp delOp = bce->sc->strict ? JSOP_STRICTDELELEM : JSOP_DELELEM;
|
||||
JSOp delOp = bce->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
|
||||
if (!EmitElemOp(cx, pn2, delOp, bce))
|
||||
return false;
|
||||
break;
|
||||
|
@ -6559,6 +6559,103 @@ EmitConditionalExpression(ExclusiveContext *cx, BytecodeEmitter *bce, Conditiona
|
|||
return SetSrcNoteOffset(cx, bce, noteIndex, 0, jmp - beq);
|
||||
}
|
||||
|
||||
static bool
|
||||
EmitPropertyList(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
|
||||
MutableHandlePlainObject objp, PropListType type)
|
||||
{
|
||||
for (ParseNode *propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
|
||||
if (!UpdateSourceCoordNotes(cx, bce, propdef->pn_pos.begin))
|
||||
return false;
|
||||
|
||||
// Handle __proto__: v specially because *only* this form, and no other
|
||||
// involving "__proto__", performs [[Prototype]] mutation.
|
||||
if (propdef->isKind(PNK_MUTATEPROTO)) {
|
||||
MOZ_ASSERT(type == ObjectLiteral);
|
||||
if (!EmitTree(cx, bce, propdef->pn_kid))
|
||||
return false;
|
||||
objp.set(nullptr);
|
||||
if (!Emit1(cx, bce, JSOP_MUTATEPROTO))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
|
||||
ParseNode *key = propdef->pn_left;
|
||||
bool isIndex = false;
|
||||
if (key->isKind(PNK_NUMBER)) {
|
||||
if (!EmitNumberOp(cx, key->pn_dval, bce))
|
||||
return false;
|
||||
isIndex = true;
|
||||
} else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
|
||||
// EmitClass took care of constructor already.
|
||||
if (type == ClassBody && key->pn_atom == cx->names().constructor)
|
||||
continue;
|
||||
|
||||
// The parser already checked for atoms representing indexes and
|
||||
// used PNK_NUMBER instead, but also watch for ids which TI treats
|
||||
// as indexes for simpliciation of downstream analysis.
|
||||
jsid id = NameToId(key->pn_atom->asPropertyName());
|
||||
if (id != IdToTypeId(id)) {
|
||||
if (!EmitTree(cx, bce, key))
|
||||
return false;
|
||||
isIndex = true;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME));
|
||||
if (!EmitTree(cx, bce, key->pn_kid))
|
||||
return false;
|
||||
isIndex = true;
|
||||
}
|
||||
|
||||
/* Emit code for the property initializer. */
|
||||
if (!EmitTree(cx, bce, propdef->pn_right))
|
||||
return false;
|
||||
|
||||
JSOp op = propdef->getOp();
|
||||
MOZ_ASSERT(op == JSOP_INITPROP ||
|
||||
op == JSOP_INITPROP_GETTER ||
|
||||
op == JSOP_INITPROP_SETTER);
|
||||
|
||||
if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
|
||||
objp.set(nullptr);
|
||||
|
||||
if (isIndex) {
|
||||
objp.set(nullptr);
|
||||
switch (op) {
|
||||
case JSOP_INITPROP: op = JSOP_INITELEM; break;
|
||||
case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break;
|
||||
case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break;
|
||||
default: MOZ_CRASH("Invalid op");
|
||||
}
|
||||
if (Emit1(cx, bce, op) < 0)
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING));
|
||||
|
||||
jsatomid index;
|
||||
if (!bce->makeAtomIndex(key->pn_atom, &index))
|
||||
return false;
|
||||
|
||||
if (objp) {
|
||||
MOZ_ASSERT(!objp->inDictionaryMode());
|
||||
Rooted<jsid> id(cx, AtomToId(key->pn_atom));
|
||||
RootedValue undefinedValue(cx, UndefinedValue());
|
||||
if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr,
|
||||
JSPROP_ENUMERATE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (objp->inDictionaryMode())
|
||||
objp.set(nullptr);
|
||||
}
|
||||
|
||||
if (!EmitIndex32(cx, op, index, bce))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
|
||||
* the comment on EmitSwitch.
|
||||
|
@ -6590,91 +6687,8 @@ EmitObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
|||
return false;
|
||||
}
|
||||
|
||||
for (ParseNode *propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
|
||||
if (!UpdateSourceCoordNotes(cx, bce, propdef->pn_pos.begin))
|
||||
return false;
|
||||
|
||||
// Handle __proto__: v specially because *only* this form, and no other
|
||||
// involving "__proto__", performs [[Prototype]] mutation.
|
||||
if (propdef->isKind(PNK_MUTATEPROTO)) {
|
||||
if (!EmitTree(cx, bce, propdef->pn_kid))
|
||||
return false;
|
||||
obj = nullptr;
|
||||
if (!Emit1(cx, bce, JSOP_MUTATEPROTO))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
|
||||
ParseNode *key = propdef->pn_left;
|
||||
bool isIndex = false;
|
||||
if (key->isKind(PNK_NUMBER)) {
|
||||
if (!EmitNumberOp(cx, key->pn_dval, bce))
|
||||
return false;
|
||||
isIndex = true;
|
||||
} else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
|
||||
// The parser already checked for atoms representing indexes and
|
||||
// used PNK_NUMBER instead, but also watch for ids which TI treats
|
||||
// as indexes for simpliciation of downstream analysis.
|
||||
jsid id = NameToId(key->pn_atom->asPropertyName());
|
||||
if (id != IdToTypeId(id)) {
|
||||
if (!EmitTree(cx, bce, key))
|
||||
return false;
|
||||
isIndex = true;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME));
|
||||
if (!EmitTree(cx, bce, key->pn_kid))
|
||||
return false;
|
||||
isIndex = true;
|
||||
}
|
||||
|
||||
/* Emit code for the property initializer. */
|
||||
if (!EmitTree(cx, bce, propdef->pn_right))
|
||||
return false;
|
||||
|
||||
JSOp op = propdef->getOp();
|
||||
MOZ_ASSERT(op == JSOP_INITPROP ||
|
||||
op == JSOP_INITPROP_GETTER ||
|
||||
op == JSOP_INITPROP_SETTER);
|
||||
|
||||
if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
|
||||
obj = nullptr;
|
||||
|
||||
if (isIndex) {
|
||||
obj = nullptr;
|
||||
switch (op) {
|
||||
case JSOP_INITPROP: op = JSOP_INITELEM; break;
|
||||
case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break;
|
||||
case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break;
|
||||
default: MOZ_CRASH("Invalid op");
|
||||
}
|
||||
if (Emit1(cx, bce, op) < 0)
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING));
|
||||
|
||||
jsatomid index;
|
||||
if (!bce->makeAtomIndex(key->pn_atom, &index))
|
||||
return false;
|
||||
|
||||
if (obj) {
|
||||
MOZ_ASSERT(!obj->inDictionaryMode());
|
||||
Rooted<jsid> id(cx, AtomToId(key->pn_atom));
|
||||
RootedValue undefinedValue(cx, UndefinedValue());
|
||||
if (!NativeDefineProperty(cx, obj, id, undefinedValue, nullptr, nullptr,
|
||||
JSPROP_ENUMERATE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (obj->inDictionaryMode())
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
if (!EmitIndex32(cx, op, index, bce))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!EmitPropertyList(cx, bce, pn, &obj, ObjectLiteral))
|
||||
return false;
|
||||
|
||||
if (obj) {
|
||||
/*
|
||||
|
@ -6865,6 +6879,120 @@ EmitDefaults(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
EmitLexicalInitialization(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
|
||||
JSOp globalDefOp)
|
||||
{
|
||||
/*
|
||||
* This function is significantly more complicated than it needs to be.
|
||||
* In fact, it shouldn't exist at all. This should all be a
|
||||
* JSOP_INITLEXIAL. Unfortunately, toplevel lexicals are broken, and
|
||||
* are emitted as vars :(. As such, we have to do these ministrations to
|
||||
* to make sure that all works properly.
|
||||
*/
|
||||
MOZ_ASSERT(pn->isKind(PNK_NAME));
|
||||
|
||||
if (!BindNameToSlot(cx, bce, pn))
|
||||
return false;
|
||||
|
||||
jsatomid atomIndex;
|
||||
if (!MaybeEmitVarDecl(cx, bce, globalDefOp, pn, &atomIndex))
|
||||
return false;
|
||||
|
||||
if (pn->getOp() != JSOP_INITLEXICAL) {
|
||||
bool global = js_CodeSpec[pn->getOp()].format & JOF_GNAME;
|
||||
if (!EmitIndex32(cx, global ? JSOP_BINDGNAME : JSOP_BINDNAME, atomIndex, bce))
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pn->pn_cookie.isFree()) {
|
||||
if (!EmitVarOp(cx, pn, pn->getOp(), bce))
|
||||
return false;
|
||||
} else {
|
||||
if (!EmitIndexOp(cx, pn->getOp(), atomIndex, bce))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
|
||||
// (BindingClassDeclarationEvaluation).
|
||||
static bool
|
||||
EmitClass(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
{
|
||||
ClassNode &classNode = pn->as<ClassNode>();
|
||||
|
||||
ClassNames *names = classNode.names();
|
||||
|
||||
MOZ_ASSERT(!classNode.heritage(), "For now, no heritage expressions");
|
||||
LexicalScopeNode *innerBlock = classNode.scope();
|
||||
|
||||
ParseNode *classMethods = innerBlock->pn_expr;
|
||||
MOZ_ASSERT(classMethods->isKind(PNK_CLASSMETHODLIST));
|
||||
|
||||
ParseNode *constructor = nullptr;
|
||||
for (ParseNode *mn = classMethods->pn_head; mn; mn = mn->pn_next) {
|
||||
ClassMethod &method = mn->as<ClassMethod>();
|
||||
ParseNode &methodName = method.name();
|
||||
if (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) &&
|
||||
methodName.pn_atom == cx->names().constructor)
|
||||
{
|
||||
constructor = &method.method();
|
||||
break;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(constructor, "For now, no default constructors");
|
||||
|
||||
bool savedStrictness = bce->sc->setLocalStrictMode(true);
|
||||
|
||||
StmtInfoBCE stmtInfo(cx);
|
||||
if (!EnterBlockScope(cx, bce, &stmtInfo, innerBlock->pn_objbox, JSOP_UNINITIALIZED))
|
||||
return false;
|
||||
|
||||
if (!EmitFunc(cx, bce, constructor))
|
||||
return false;
|
||||
|
||||
if (!EmitNewInit(cx, bce, JSProto_Object))
|
||||
return false;
|
||||
|
||||
if (Emit1(cx, bce, JSOP_DUP2) < 0)
|
||||
return false;
|
||||
if (!EmitAtomOp(cx, cx->names().prototype, JSOP_INITLOCKEDPROP, bce))
|
||||
return false;
|
||||
if (!EmitAtomOp(cx, cx->names().constructor, JSOP_INITHIDDENPROP, bce))
|
||||
return false;
|
||||
|
||||
RootedPlainObject obj(cx);
|
||||
if (!EmitPropertyList(cx, bce, classMethods, &obj, ClassBody))
|
||||
return false;
|
||||
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0)
|
||||
return false;
|
||||
|
||||
// That DEFCONST is never gonna be used, but use it here for logical consistency.
|
||||
ParseNode *innerName = names->innerBinding();
|
||||
if (!EmitLexicalInitialization(cx, bce, innerName, JSOP_DEFCONST))
|
||||
return false;
|
||||
|
||||
if (!LeaveNestedScope(cx, bce, &stmtInfo))
|
||||
return false;
|
||||
|
||||
ParseNode *outerName = names->outerBinding();
|
||||
if (!EmitLexicalInitialization(cx, bce, outerName, JSOP_DEFVAR))
|
||||
return false;
|
||||
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0)
|
||||
return false;
|
||||
|
||||
MOZ_ALWAYS_TRUE(bce->sc->setLocalStrictMode(savedStrictness));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
{
|
||||
|
@ -7312,6 +7440,10 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
|||
MOZ_ASSERT(pn->getArity() == PN_NULLARY);
|
||||
break;
|
||||
|
||||
case PNK_CLASS:
|
||||
ok = EmitClass(cx, bce, pn);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT(0);
|
||||
}
|
||||
|
|
|
@ -80,6 +80,13 @@ ContainsHoistedDeclaration(ExclusiveContext *cx, ParseNode *node, bool *result)
|
|||
*result = false;
|
||||
return true;
|
||||
|
||||
// Similarly to the lexical declarations above, classes cannot add hoisted
|
||||
// declarations
|
||||
case PNK_CLASS:
|
||||
MOZ_ASSERT(node->isArity(PN_TERNARY));
|
||||
*result = false;
|
||||
return true;
|
||||
|
||||
// ContainsHoistedDeclaration is only called on nested nodes, so any
|
||||
// instance of this can't be function statements at body level. In
|
||||
// SpiderMonkey, a binding induced by a function statement is added when
|
||||
|
@ -405,6 +412,9 @@ ContainsHoistedDeclaration(ExclusiveContext *cx, ParseNode *node, bool *result)
|
|||
case PNK_FORIN:
|
||||
case PNK_FOROF:
|
||||
case PNK_FORHEAD:
|
||||
case PNK_CLASSMETHOD:
|
||||
case PNK_CLASSMETHODLIST:
|
||||
case PNK_CLASSNAMES:
|
||||
MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
|
||||
"some parent node without recurring to test this node");
|
||||
|
||||
|
|
|
@ -281,6 +281,16 @@ class FullParseHandler
|
|||
return literal;
|
||||
}
|
||||
|
||||
ParseNode *newClass(ParseNode *name, ParseNode *heritage, ParseNode *methodBlock) {
|
||||
return new_<ClassNode>(name, heritage, methodBlock);
|
||||
}
|
||||
ParseNode *newClassMethodList(uint32_t begin) {
|
||||
return new_<ListNode>(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1));
|
||||
}
|
||||
ParseNode *newClassNames(ParseNode *outer, ParseNode *inner, const TokenPos &pos) {
|
||||
return new_<ClassNames>(outer, inner, pos);
|
||||
}
|
||||
|
||||
bool addPrototypeMutation(ParseNode *literal, uint32_t begin, ParseNode *expr) {
|
||||
// Object literals with mutated [[Prototype]] are non-constant so that
|
||||
// singleton objects will have Object.prototype as their [[Prototype]].
|
||||
|
@ -323,7 +333,7 @@ class FullParseHandler
|
|||
return true;
|
||||
}
|
||||
|
||||
bool addMethodDefinition(ParseNode *literal, ParseNode *key, ParseNode *fn, JSOp op)
|
||||
bool addObjectMethodDefinition(ParseNode *literal, ParseNode *key, ParseNode *fn, JSOp op)
|
||||
{
|
||||
MOZ_ASSERT(literal->isArity(PN_LIST));
|
||||
MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
|
||||
|
@ -339,6 +349,22 @@ class FullParseHandler
|
|||
return true;
|
||||
}
|
||||
|
||||
bool addClassMethodDefinition(ParseNode *methodList, ParseNode *key, ParseNode *fn, JSOp op)
|
||||
{
|
||||
MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST));
|
||||
MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
|
||||
key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
|
||||
key->isKind(PNK_STRING) ||
|
||||
key->isKind(PNK_COMPUTED_NAME));
|
||||
|
||||
// For now, there's no such thing as static methods.
|
||||
ParseNode *classMethod = new_<ClassMethod>(key, fn, op, false);
|
||||
if (!classMethod)
|
||||
return false;
|
||||
methodList->append(classMethod);
|
||||
return true;
|
||||
}
|
||||
|
||||
ParseNode *newYieldExpression(uint32_t begin, ParseNode *value, ParseNode *gen,
|
||||
JSOp op = JSOP_YIELD) {
|
||||
TokenPos pos(begin, value ? value->pn_pos.end : begin + 1);
|
||||
|
|
|
@ -268,6 +268,8 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
|
|||
case PNK_WHILE:
|
||||
case PNK_SWITCH:
|
||||
case PNK_LETBLOCK:
|
||||
case PNK_CLASSNAMES:
|
||||
case PNK_CLASSMETHOD:
|
||||
case PNK_FOR: {
|
||||
MOZ_ASSERT(pn->isArity(PN_BINARY));
|
||||
stack->push(pn->pn_left);
|
||||
|
@ -381,6 +383,16 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
|
|||
return PushResult::Recyclable;
|
||||
}
|
||||
|
||||
// classes might have an optinal node for the heritage
|
||||
case PNK_CLASS: {
|
||||
MOZ_ASSERT(pn->isArity(PN_TERNARY));
|
||||
stack->push(pn->pn_kid1);
|
||||
if (pn->pn_kid2)
|
||||
stack->push(pn->pn_kid2);
|
||||
stack->push(pn->pn_kid3);
|
||||
return PushResult::Recyclable;
|
||||
}
|
||||
|
||||
// if-statement nodes have condition and consequent children and a
|
||||
// possibly-null alternative.
|
||||
case PNK_IF: {
|
||||
|
@ -461,6 +473,7 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
|
|||
case PNK_EXPORT_SPEC_LIST:
|
||||
case PNK_SEQ:
|
||||
case PNK_ARGSBODY:
|
||||
case PNK_CLASSMETHODLIST:
|
||||
return PushListNodeChildren(pn, stack);
|
||||
|
||||
// Array comprehension nodes are lists with a single child -- PNK_FOR for
|
||||
|
@ -630,7 +643,7 @@ Parser<FullParseHandler>::cloneParseTree(ParseNode *opn)
|
|||
|
||||
case PN_CODE:
|
||||
NULLCHECK(pn->pn_funbox = newFunctionBox(pn, opn->pn_funbox->function(), pc,
|
||||
Directives(/* strict = */ opn->pn_funbox->strict),
|
||||
Directives(/* strict = */ opn->pn_funbox->strict()),
|
||||
opn->pn_funbox->generatorKind()));
|
||||
NULLCHECK(pn->pn_body = cloneParseTree(opn->pn_body));
|
||||
pn->pn_cookie = opn->pn_cookie;
|
||||
|
|
|
@ -149,6 +149,10 @@ class UpvarCookie
|
|||
F(ARGSBODY) \
|
||||
F(SPREAD) \
|
||||
F(MUTATEPROTO) \
|
||||
F(CLASS) \
|
||||
F(CLASSMETHOD) \
|
||||
F(CLASSMETHODLIST) \
|
||||
F(CLASSNAMES) \
|
||||
\
|
||||
/* Unary operators. */ \
|
||||
F(TYPEOF) \
|
||||
|
@ -571,6 +575,7 @@ class ParseNode
|
|||
union {
|
||||
unsigned iflags; /* JSITER_* flags for PNK_FOR node */
|
||||
ObjectBox *objbox; /* Only for PN_BINARY_OBJ */
|
||||
bool isStatic; /* Only for PNK_CLASSMETHOD */
|
||||
};
|
||||
} binary;
|
||||
struct { /* one kid if unary */
|
||||
|
@ -1102,6 +1107,10 @@ struct LexicalScopeNode : public ParseNode
|
|||
pn_expr = blockNode;
|
||||
pn_blockid = blockNode->pn_blockid;
|
||||
}
|
||||
|
||||
static bool test(const ParseNode &node) {
|
||||
return node.isKind(PNK_LEXICALSCOPE);
|
||||
}
|
||||
};
|
||||
|
||||
class LabeledStatement : public ParseNode
|
||||
|
@ -1319,6 +1328,91 @@ struct CallSiteNode : public ListNode {
|
|||
}
|
||||
};
|
||||
|
||||
struct ClassMethod : public BinaryNode {
|
||||
/*
|
||||
* Method defintions often keep a name and function body that overlap,
|
||||
* so explicitly define the beginning and end here.
|
||||
*/
|
||||
ClassMethod(ParseNode *name, ParseNode *body, JSOp op, bool isStatic)
|
||||
: BinaryNode(PNK_CLASSMETHOD, op, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body)
|
||||
{
|
||||
pn_u.binary.isStatic = isStatic;
|
||||
}
|
||||
|
||||
static bool test(const ParseNode &node) {
|
||||
bool match = node.isKind(PNK_CLASSMETHOD);
|
||||
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
|
||||
return match;
|
||||
}
|
||||
|
||||
ParseNode &name() const {
|
||||
return *pn_u.binary.left;
|
||||
}
|
||||
ParseNode &method() const {
|
||||
return *pn_u.binary.right;
|
||||
}
|
||||
bool isStatic() const {
|
||||
return pn_u.binary.isStatic;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClassNames : public BinaryNode {
|
||||
ClassNames(ParseNode *outerBinding, ParseNode *innerBinding, const TokenPos &pos)
|
||||
: BinaryNode(PNK_CLASSNAMES, JSOP_NOP, pos, outerBinding, innerBinding)
|
||||
{
|
||||
MOZ_ASSERT(outerBinding->isKind(PNK_NAME));
|
||||
MOZ_ASSERT(innerBinding->isKind(PNK_NAME));
|
||||
MOZ_ASSERT(innerBinding->pn_atom == outerBinding->pn_atom);
|
||||
}
|
||||
|
||||
static bool test(const ParseNode &node) {
|
||||
bool match = node.isKind(PNK_CLASSNAMES);
|
||||
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
|
||||
return match;
|
||||
}
|
||||
|
||||
/*
|
||||
* Classes require two definitions: The first "outer" binding binds the
|
||||
* class into the scope in which it was declared. the outer binding is a
|
||||
* mutable lexial binding. The second "inner" binding binds the class by
|
||||
* name inside a block in which the methods are evaulated. It is immutable,
|
||||
* giving the methods access to the static members of the class even if
|
||||
* the outer binding has been overwritten.
|
||||
*/
|
||||
ParseNode *outerBinding() const {
|
||||
return pn_u.binary.left;
|
||||
}
|
||||
ParseNode *innerBinding() const {
|
||||
return pn_u.binary.right;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClassNode : public TernaryNode {
|
||||
ClassNode(ParseNode *names, ParseNode *heritage, ParseNode *methodBlock)
|
||||
: TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodBlock)
|
||||
{
|
||||
MOZ_ASSERT(names->is<ClassNames>());
|
||||
MOZ_ASSERT(methodBlock->is<LexicalScopeNode>());
|
||||
}
|
||||
|
||||
static bool test(const ParseNode &node) {
|
||||
bool match = node.isKind(PNK_CLASS);
|
||||
MOZ_ASSERT_IF(match, node.isArity(PN_TERNARY));
|
||||
return match;
|
||||
}
|
||||
|
||||
ClassNames *names() const {
|
||||
return &pn_kid1->as<ClassNames>();
|
||||
}
|
||||
ParseNode *heritage() const {
|
||||
return pn_kid2;
|
||||
}
|
||||
LexicalScopeNode *scope() const {
|
||||
return &pn_kid3->as<LexicalScopeNode>();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
void DumpParseTree(ParseNode *pn, int indent = 0);
|
||||
#endif
|
||||
|
@ -1553,7 +1647,7 @@ enum ParseReportKind
|
|||
ParseStrictError
|
||||
};
|
||||
|
||||
enum FunctionSyntaxKind { Expression, Statement, Arrow, Method };
|
||||
enum FunctionSyntaxKind { Expression, Statement, Arrow, Method, Lazy };
|
||||
|
||||
static inline ParseNode *
|
||||
FunctionArgsList(ParseNode *fn, unsigned *numFormals)
|
||||
|
|
|
@ -754,7 +754,7 @@ Parser<ParseHandler>::reportBadReturn(Node pn, ParseReportKind kind,
|
|||
} else {
|
||||
errnum = anonerrnum;
|
||||
}
|
||||
return report(kind, pc->sc->strict, pn, errnum, name.ptr());
|
||||
return report(kind, pc->sc->strict(), pn, errnum, name.ptr());
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -777,7 +777,7 @@ Parser<ParseHandler>::checkStrictAssignment(Node lhs)
|
|||
if (!AtomToPrintableString(context, atom, &name))
|
||||
return false;
|
||||
|
||||
if (!report(ParseStrictError, pc->sc->strict, lhs, JSMSG_BAD_STRICT_ASSIGN, name.ptr()))
|
||||
if (!report(ParseStrictError, pc->sc->strict(), lhs, JSMSG_BAD_STRICT_ASSIGN, name.ptr()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -800,7 +800,7 @@ Parser<ParseHandler>::checkStrictBinding(PropertyName *name, Node pn)
|
|||
JSAutoByteString bytes;
|
||||
if (!AtomToPrintableString(context, name, &bytes))
|
||||
return false;
|
||||
return report(ParseStrictError, pc->sc->strict, pn,
|
||||
return report(ParseStrictError, pc->sc->strict(), pn,
|
||||
JSMSG_BAD_BINDING, bytes.ptr());
|
||||
}
|
||||
|
||||
|
@ -1505,7 +1505,7 @@ Parser<ParseHandler>::defineArg(Node funcpn, HandlePropertyName name,
|
|||
JSAutoByteString bytes;
|
||||
if (!AtomToPrintableString(context, name, &bytes))
|
||||
return false;
|
||||
if (!report(ParseStrictError, pc->sc->strict, pn,
|
||||
if (!report(ParseStrictError, pc->sc->strict(), pn,
|
||||
JSMSG_DUPLICATE_FORMAL, bytes.ptr()))
|
||||
{
|
||||
return false;
|
||||
|
@ -1892,7 +1892,7 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
|
|||
* statements (e.g., functions in an "if" or "while" block) are
|
||||
* dynamically bound when control flow reaches the statement.
|
||||
*/
|
||||
MOZ_ASSERT(!pc->sc->strict);
|
||||
MOZ_ASSERT(!pc->sc->strict());
|
||||
MOZ_ASSERT(pn->pn_cookie.isFree());
|
||||
if (pc->sc->isFunctionBox()) {
|
||||
FunctionBox *funbox = pc->sc->asFunctionBox();
|
||||
|
@ -2278,7 +2278,7 @@ Parser<SyntaxParseHandler>::finishFunctionDefinition(Node pn, FunctionBox *funbo
|
|||
for (size_t i = 0; i < numInnerFunctions; i++)
|
||||
innerFunctions[i].init(pc->innerFunctions[i]);
|
||||
|
||||
if (pc->sc->strict)
|
||||
if (pc->sc->strict())
|
||||
lazy->setStrict();
|
||||
lazy->setGeneratorKind(funbox->generatorKind());
|
||||
if (funbox->usesArguments && funbox->usesApply && funbox->usesThis)
|
||||
|
@ -2470,7 +2470,7 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, unsigned st
|
|||
if (!funpc.init(tokenStream))
|
||||
return null();
|
||||
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, Normal, Statement)) {
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, Normal, Lazy)) {
|
||||
MOZ_ASSERT(directives == newDirectives);
|
||||
return null();
|
||||
}
|
||||
|
@ -2555,8 +2555,11 @@ Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu
|
|||
if (!body)
|
||||
return false;
|
||||
|
||||
if (fun->name() && !checkStrictBinding(fun->name(), pn))
|
||||
if (kind != Method && kind != Lazy &&
|
||||
fun->name() && !checkStrictBinding(fun->name(), pn))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bodyType == StatementListBody) {
|
||||
bool matched;
|
||||
|
@ -2574,7 +2577,7 @@ Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu
|
|||
if (tokenStream.hadError())
|
||||
return false;
|
||||
funbox->bufEnd = pos().end;
|
||||
if (kind == Statement && !MatchOrInsertSemicolon(tokenStream))
|
||||
if ((kind == Statement || kind == Lazy) && !MatchOrInsertSemicolon(tokenStream))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2587,7 +2590,7 @@ Parser<ParseHandler>::checkYieldNameValidity()
|
|||
{
|
||||
// In star generators and in JS >= 1.7, yield is a keyword. Otherwise in
|
||||
// strict mode, yield is a future reserved word.
|
||||
if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict) {
|
||||
if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict()) {
|
||||
report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
|
||||
return false;
|
||||
}
|
||||
|
@ -2626,7 +2629,7 @@ Parser<ParseHandler>::functionStmt()
|
|||
|
||||
/* We forbid function statements in strict mode code. */
|
||||
if (!pc->atBodyLevel() && pc->sc->needStrictChecks() &&
|
||||
!report(ParseStrictError, pc->sc->strict, null(), JSMSG_STRICT_FUNCTION_STATEMENT))
|
||||
!report(ParseStrictError, pc->sc->strict(), null(), JSMSG_STRICT_FUNCTION_STATEMENT))
|
||||
return null();
|
||||
|
||||
return functionDef(name, Normal, Statement, generatorKind);
|
||||
|
@ -2779,7 +2782,7 @@ Parser<ParseHandler>::maybeParseDirective(Node list, Node pn, bool *cont)
|
|||
// We're going to be in strict mode. Note that this scope explicitly
|
||||
// had "use strict";
|
||||
pc->sc->setExplicitUseStrict();
|
||||
if (!pc->sc->strict) {
|
||||
if (!pc->sc->strict()) {
|
||||
if (pc->sc->isFunctionBox()) {
|
||||
// Request that this function be reparsed as strict.
|
||||
pc->newDirectives->setStrict();
|
||||
|
@ -2792,7 +2795,7 @@ Parser<ParseHandler>::maybeParseDirective(Node list, Node pn, bool *cont)
|
|||
report(ParseError, false, null(), JSMSG_DEPRECATED_OCTAL);
|
||||
return false;
|
||||
}
|
||||
pc->sc->strict = true;
|
||||
pc->sc->strictScript = true;
|
||||
}
|
||||
}
|
||||
} else if (directive == context->names().useAsm) {
|
||||
|
@ -3263,7 +3266,7 @@ Parser<FullParseHandler>::makeSetCall(ParseNode *pn, unsigned msg)
|
|||
pn->isOp(JSOP_SPREADEVAL) || pn->isOp(JSOP_STRICTSPREADEVAL) ||
|
||||
pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY));
|
||||
|
||||
if (!report(ParseStrictError, pc->sc->strict, pn, msg))
|
||||
if (!report(ParseStrictError, pc->sc->strict(), pn, msg))
|
||||
return false;
|
||||
handler.markAsSetCall(pn);
|
||||
return true;
|
||||
|
@ -3322,7 +3325,7 @@ Parser<ParseHandler>::noteNameUse(HandlePropertyName name, Node pn)
|
|||
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::bindDestructuringVar(BindData<FullParseHandler> *data, ParseNode *pn)
|
||||
Parser<FullParseHandler>::bindInitialized(BindData<FullParseHandler> *data, ParseNode *pn)
|
||||
{
|
||||
MOZ_ASSERT(pn->isKind(PNK_NAME));
|
||||
|
||||
|
@ -3382,7 +3385,7 @@ Parser<FullParseHandler>::checkDestructuringObject(BindData<FullParseHandler> *d
|
|||
report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME);
|
||||
return false;
|
||||
}
|
||||
ok = bindDestructuringVar(data, expr);
|
||||
ok = bindInitialized(data, expr);
|
||||
} else {
|
||||
ok = checkAndMarkAsAssignmentLhs(expr, KeyedDestructuringAssignment);
|
||||
}
|
||||
|
@ -3430,7 +3433,7 @@ Parser<FullParseHandler>::checkDestructuringArray(BindData<FullParseHandler> *da
|
|||
report(ParseError, false, target, JSMSG_NO_VARIABLE_NAME);
|
||||
return false;
|
||||
}
|
||||
ok = bindDestructuringVar(data, target);
|
||||
ok = bindInitialized(data, target);
|
||||
} else {
|
||||
ok = checkAndMarkAsAssignmentLhs(target, KeyedDestructuringAssignment);
|
||||
}
|
||||
|
@ -3660,7 +3663,7 @@ Parser<ParseHandler>::deprecatedLetBlockOrExpression(LetContext letContext)
|
|||
*
|
||||
* See bug 569464.
|
||||
*/
|
||||
if (!reportWithOffset(ParseStrictError, pc->sc->strict, begin,
|
||||
if (!reportWithOffset(ParseStrictError, pc->sc->strict(), begin,
|
||||
JSMSG_STRICT_CODE_LET_EXPR_STMT))
|
||||
{
|
||||
return null();
|
||||
|
@ -3684,7 +3687,7 @@ Parser<ParseHandler>::deprecatedLetBlockOrExpression(LetContext letContext)
|
|||
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET);
|
||||
|
||||
addTelemetry(JSCompartment::DeprecatedLetBlock);
|
||||
if (!report(ParseWarning, pc->sc->strict, expr, JSMSG_DEPRECATED_LET_BLOCK))
|
||||
if (!report(ParseWarning, pc->sc->strict(), expr, JSMSG_DEPRECATED_LET_BLOCK))
|
||||
return null();
|
||||
} else {
|
||||
MOZ_ASSERT(letContext == LetExpression);
|
||||
|
@ -3693,7 +3696,7 @@ Parser<ParseHandler>::deprecatedLetBlockOrExpression(LetContext letContext)
|
|||
return null();
|
||||
|
||||
addTelemetry(JSCompartment::DeprecatedLetExpression);
|
||||
if (!report(ParseWarning, pc->sc->strict, expr, JSMSG_DEPRECATED_LET_EXPRESSION))
|
||||
if (!report(ParseWarning, pc->sc->strict(), expr, JSMSG_DEPRECATED_LET_EXPRESSION))
|
||||
return null();
|
||||
}
|
||||
handler.setLexicalScopeBody(block, expr);
|
||||
|
@ -3950,136 +3953,169 @@ Parser<ParseHandler>::variables(ParseNodeKind kind, bool *psimple,
|
|||
return pn;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos &errorPos)
|
||||
{
|
||||
/*
|
||||
* This is a lexical declaration. We must be directly under a block per the
|
||||
* proposed ES4 specs, but not an implicit block created due to
|
||||
* 'for (let ...)'. If we pass this error test, make the enclosing
|
||||
* StmtInfoPC be our scope. Further let declarations in this block will
|
||||
* find this scope statement and use the same block object.
|
||||
*
|
||||
* If we are the first let declaration in this block (i.e., when the
|
||||
* enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then
|
||||
* we also need to set pc->blockNode to be our PNK_LEXICALSCOPE.
|
||||
*/
|
||||
StmtInfoPC *stmt = pc->topStmt;
|
||||
if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
|
||||
reportWithOffset(ParseError, false, errorPos.begin, JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
|
||||
isConst ? "const" : "lexical");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stmt && stmt->isBlockScope) {
|
||||
MOZ_ASSERT(pc->staticScope == stmt->staticScope);
|
||||
} else {
|
||||
if (pc->atBodyLevel()) {
|
||||
/*
|
||||
* When bug 589199 is fixed, let variables will be stored in
|
||||
* the slots of a new scope chain object, encountered just
|
||||
* before the global object in the overall chain. This extra
|
||||
* object is present in the scope chain for all code in that
|
||||
* global, including self-hosted code. But self-hosted code
|
||||
* must be usable against *any* global object, including ones
|
||||
* with other let variables -- variables possibly placed in
|
||||
* conflicting slots. Forbid top-level let declarations to
|
||||
* prevent such conflicts from ever occurring.
|
||||
*/
|
||||
bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt;
|
||||
if (options().selfHostingMode && isGlobal) {
|
||||
report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL,
|
||||
isConst ? "'const'" : "'let'");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some obvious assertions here, but they may help clarify the
|
||||
* situation. This stmt is not yet a scope, so it must not be a
|
||||
* catch block (catch is a lexical scope by definition).
|
||||
*/
|
||||
MOZ_ASSERT(!stmt->isBlockScope);
|
||||
MOZ_ASSERT(stmt != pc->topScopeStmt);
|
||||
MOZ_ASSERT(stmt->type == STMT_BLOCK ||
|
||||
stmt->type == STMT_SWITCH ||
|
||||
stmt->type == STMT_TRY ||
|
||||
stmt->type == STMT_FINALLY);
|
||||
MOZ_ASSERT(!stmt->downScope);
|
||||
|
||||
/* Convert the block statement into a scope statement. */
|
||||
StaticBlockObject *blockObj = StaticBlockObject::create(context);
|
||||
if (!blockObj)
|
||||
return false;
|
||||
|
||||
ObjectBox *blockbox = newObjectBox(blockObj);
|
||||
if (!blockbox)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked
|
||||
* list stack, if it isn't already there. If it is there, but it
|
||||
* lacks the SIF_SCOPE flag, it must be a try, catch, or finally
|
||||
* block.
|
||||
*/
|
||||
stmt->isBlockScope = stmt->isNestedScope = true;
|
||||
stmt->downScope = pc->topScopeStmt;
|
||||
pc->topScopeStmt = stmt;
|
||||
|
||||
blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
|
||||
pc->staticScope = blockObj;
|
||||
stmt->staticScope = blockObj;
|
||||
|
||||
#ifdef DEBUG
|
||||
ParseNode *tmp = pc->blockNode;
|
||||
MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
|
||||
#endif
|
||||
|
||||
/* Create a new lexical scope node for these statements. */
|
||||
ParseNode *pn1 = handler.new_<LexicalScopeNode>(blockbox, pc->blockNode);
|
||||
if (!pn1)
|
||||
return false;;
|
||||
pc->blockNode = pn1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static StaticBlockObject *
|
||||
CurrentLexicalStaticBlock(ParseContext<FullParseHandler> *pc)
|
||||
{
|
||||
return pc->atBodyLevel() ? nullptr :
|
||||
&pc->staticScope->as<StaticBlockObject>();
|
||||
}
|
||||
|
||||
template <>
|
||||
ParseNode *
|
||||
Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name, bool isConst,
|
||||
const TokenPos &pos)
|
||||
{
|
||||
// Handle the silliness of global and body level lexical decls.
|
||||
BindData<FullParseHandler> data(context);
|
||||
if (pc->atGlobalLevel()) {
|
||||
data.initVarOrGlobalConst(isConst ? JSOP_DEFCONST : JSOP_DEFVAR);
|
||||
} else {
|
||||
if (!checkAndPrepareLexical(isConst, pos))
|
||||
return null();
|
||||
data.initLexical(HoistVars, CurrentLexicalStaticBlock(pc), JSMSG_TOO_MANY_LOCALS, isConst);
|
||||
}
|
||||
ParseNode *dn = newBindingNode(name, pc->atGlobalLevel());
|
||||
if (!dn)
|
||||
return null();
|
||||
handler.setPosition(dn, pos);
|
||||
|
||||
if (!bindInitialized(&data, dn))
|
||||
return null();
|
||||
|
||||
return dn;
|
||||
}
|
||||
|
||||
template <>
|
||||
ParseNode *
|
||||
Parser<FullParseHandler>::lexicalDeclaration(bool isConst)
|
||||
{
|
||||
handler.disableSyntaxParser();
|
||||
|
||||
ParseNode *pn;
|
||||
if (!checkAndPrepareLexical(isConst, pos()))
|
||||
return null();
|
||||
|
||||
do {
|
||||
/*
|
||||
* This is a let declaration. We must be directly under a block per the
|
||||
* proposed ES4 specs, but not an implicit block created due to
|
||||
* 'for (let ...)'. If we pass this error test, make the enclosing
|
||||
* StmtInfoPC be our scope. Further let declarations in this block will
|
||||
* find this scope statement and use the same block object.
|
||||
*
|
||||
* If we are the first let declaration in this block (i.e., when the
|
||||
* enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then
|
||||
* we also need to set pc->blockNode to be our PNK_LEXICALSCOPE.
|
||||
*/
|
||||
StmtInfoPC *stmt = pc->topStmt;
|
||||
if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
|
||||
report(ParseError, false, null(), JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
|
||||
isConst ? "const" : "let");
|
||||
return null();
|
||||
}
|
||||
|
||||
if (stmt && stmt->isBlockScope) {
|
||||
MOZ_ASSERT(pc->staticScope == stmt->staticScope);
|
||||
} else {
|
||||
if (pc->atBodyLevel()) {
|
||||
/*
|
||||
* When bug 589199 is fixed, let variables will be stored in
|
||||
* the slots of a new scope chain object, encountered just
|
||||
* before the global object in the overall chain. This extra
|
||||
* object is present in the scope chain for all code in that
|
||||
* global, including self-hosted code. But self-hosted code
|
||||
* must be usable against *any* global object, including ones
|
||||
* with other let variables -- variables possibly placed in
|
||||
* conflicting slots. Forbid top-level let declarations to
|
||||
* prevent such conflicts from ever occurring.
|
||||
*/
|
||||
bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt;
|
||||
if (options().selfHostingMode && isGlobal) {
|
||||
report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL,
|
||||
isConst ? "'const'" : "'let'");
|
||||
return null();
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse body-level lets without a new block object. ES6 specs
|
||||
* that an execution environment's initial lexical environment
|
||||
* is the VariableEnvironment, i.e., body-level lets are in
|
||||
* the same environment record as vars.
|
||||
*
|
||||
* However, they cannot be parsed exactly as vars, as ES6
|
||||
* requires that uninitialized lets throw ReferenceError on use.
|
||||
*
|
||||
* See 8.1.1.1.6 and the note in 13.2.1.
|
||||
*
|
||||
* FIXME global-level lets are still considered vars until
|
||||
* other bugs are fixed.
|
||||
*/
|
||||
ParseNodeKind kind = PNK_LET;
|
||||
if (isGlobal)
|
||||
kind = isConst ? PNK_GLOBALCONST : PNK_VAR;
|
||||
else if (isConst)
|
||||
kind = PNK_CONST;
|
||||
pn = variables(kind);
|
||||
if (!pn)
|
||||
return null();
|
||||
pn->pn_xflags |= PNX_POPVAR;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some obvious assertions here, but they may help clarify the
|
||||
* situation. This stmt is not yet a scope, so it must not be a
|
||||
* catch block (catch is a lexical scope by definition).
|
||||
*/
|
||||
MOZ_ASSERT(!stmt->isBlockScope);
|
||||
MOZ_ASSERT(stmt != pc->topScopeStmt);
|
||||
MOZ_ASSERT(stmt->type == STMT_BLOCK ||
|
||||
stmt->type == STMT_SWITCH ||
|
||||
stmt->type == STMT_TRY ||
|
||||
stmt->type == STMT_FINALLY);
|
||||
MOZ_ASSERT(!stmt->downScope);
|
||||
|
||||
/* Convert the block statement into a scope statement. */
|
||||
StaticBlockObject *blockObj = StaticBlockObject::create(context);
|
||||
if (!blockObj)
|
||||
return null();
|
||||
|
||||
ObjectBox *blockbox = newObjectBox(blockObj);
|
||||
if (!blockbox)
|
||||
return null();
|
||||
|
||||
/*
|
||||
* Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked
|
||||
* list stack, if it isn't already there. If it is there, but it
|
||||
* lacks the SIF_SCOPE flag, it must be a try, catch, or finally
|
||||
* block.
|
||||
*/
|
||||
stmt->isBlockScope = stmt->isNestedScope = true;
|
||||
stmt->downScope = pc->topScopeStmt;
|
||||
pc->topScopeStmt = stmt;
|
||||
|
||||
blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
|
||||
pc->staticScope = blockObj;
|
||||
stmt->staticScope = blockObj;
|
||||
|
||||
#ifdef DEBUG
|
||||
ParseNode *tmp = pc->blockNode;
|
||||
MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
|
||||
#endif
|
||||
|
||||
/* Create a new lexical scope node for these statements. */
|
||||
ParseNode *pn1 = handler.new_<LexicalScopeNode>(blockbox, pc->blockNode);
|
||||
if (!pn1)
|
||||
return null();
|
||||
pc->blockNode = pn1;
|
||||
}
|
||||
|
||||
pn = variables(isConst ? PNK_CONST : PNK_LET, nullptr,
|
||||
&pc->staticScope->as<StaticBlockObject>(), HoistVars);
|
||||
if (!pn)
|
||||
return null();
|
||||
pn->pn_xflags = PNX_POPVAR;
|
||||
} while (0);
|
||||
/*
|
||||
* Parse body-level lets without a new block object. ES6 specs
|
||||
* that an execution environment's initial lexical environment
|
||||
* is the VariableEnvironment, i.e., body-level lets are in
|
||||
* the same environment record as vars.
|
||||
*
|
||||
* However, they cannot be parsed exactly as vars, as ES6
|
||||
* requires that uninitialized lets throw ReferenceError on use.
|
||||
*
|
||||
* See 8.1.1.1.6 and the note in 13.2.1.
|
||||
*
|
||||
* FIXME global-level lets are still considered vars until
|
||||
* other bugs are fixed.
|
||||
*/
|
||||
ParseNodeKind kind = PNK_LET;
|
||||
if (pc->atGlobalLevel())
|
||||
kind = isConst ? PNK_GLOBALCONST : PNK_VAR;
|
||||
else if (isConst)
|
||||
kind = PNK_CONST;
|
||||
|
||||
ParseNode *pn = variables(kind, nullptr,
|
||||
CurrentLexicalStaticBlock(pc),
|
||||
HoistVars);
|
||||
if (!pn)
|
||||
return null();
|
||||
pn->pn_xflags = PNX_POPVAR;
|
||||
return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr;
|
||||
}
|
||||
|
||||
|
@ -4616,7 +4652,7 @@ Parser<FullParseHandler>::forStatement()
|
|||
isForEach = true;
|
||||
addTelemetry(JSCompartment::DeprecatedForEach);
|
||||
if (versionNumber() < JSVERSION_LATEST) {
|
||||
if (!report(ParseWarning, pc->sc->strict, null(), JSMSG_DEPRECATED_FOR_EACH))
|
||||
if (!report(ParseWarning, pc->sc->strict(), null(), JSMSG_DEPRECATED_FOR_EACH))
|
||||
return null();
|
||||
}
|
||||
}
|
||||
|
@ -5484,7 +5520,7 @@ Parser<FullParseHandler>::withStatement()
|
|||
// construct that is forbidden in strict mode code, but doesn't even merit a
|
||||
// warning under JSOPTION_EXTRA_WARNINGS. See
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1.
|
||||
if (pc->sc->strict && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH))
|
||||
if (pc->sc->strict() && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH))
|
||||
return null();
|
||||
|
||||
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH);
|
||||
|
@ -5787,6 +5823,80 @@ Parser<ParseHandler>::debuggerStatement()
|
|||
return handler.newDebuggerStatement(p);
|
||||
}
|
||||
|
||||
template <>
|
||||
ParseNode *
|
||||
Parser<FullParseHandler>::classStatement()
|
||||
{
|
||||
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS));
|
||||
|
||||
TokenKind tt;
|
||||
if (!tokenStream.getToken(&tt))
|
||||
return null();
|
||||
|
||||
RootedPropertyName name(context);
|
||||
if (tt == TOK_NAME) {
|
||||
name = tokenStream.currentName();
|
||||
} else if (tt == TOK_YIELD) {
|
||||
if (!checkYieldNameValidity())
|
||||
return null();
|
||||
name = tokenStream.currentName();
|
||||
} else {
|
||||
// Class statements must have a bound name
|
||||
report(ParseError, false, null(), JSMSG_UNNAMED_CLASS_STMT);
|
||||
return null();
|
||||
}
|
||||
|
||||
if (name == context->names().let) {
|
||||
report(ParseError, false, null(), JSMSG_LET_CLASS_BINDING);
|
||||
return null();
|
||||
}
|
||||
|
||||
// Because the binding definitions keep track of their blockId, we need to
|
||||
// create at least the inner binding later. Keep track of the name's position
|
||||
// in order to provide it for the nodes created later.
|
||||
TokenPos namePos = pos();
|
||||
|
||||
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
|
||||
|
||||
bool savedStrictness = setLocalStrictMode(true);
|
||||
|
||||
StmtInfoPC classStmt(context);
|
||||
ParseNode *classBlock = pushLexicalScope(&classStmt);
|
||||
if (!classBlock)
|
||||
return null();
|
||||
|
||||
ParseNode *classMethods = propertyList(ClassBody);
|
||||
if (!classMethods)
|
||||
return null();
|
||||
handler.setLexicalScopeBody(classBlock, classMethods);
|
||||
|
||||
ParseNode *innerBinding = makeInitializedLexicalBinding(name, true, namePos);
|
||||
if (!innerBinding)
|
||||
return null();
|
||||
|
||||
PopStatementPC(tokenStream, pc);
|
||||
|
||||
ParseNode *outerBinding = makeInitializedLexicalBinding(name, false, namePos);
|
||||
if (!outerBinding)
|
||||
return null();
|
||||
|
||||
ParseNode *nameNode = handler.newClassNames(outerBinding, innerBinding, namePos);
|
||||
if (!nameNode)
|
||||
return null();
|
||||
|
||||
MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
|
||||
|
||||
return handler.newClass(nameNode, null(), classBlock);
|
||||
}
|
||||
|
||||
template <>
|
||||
SyntaxParseHandler::Node
|
||||
Parser<SyntaxParseHandler>::classStatement()
|
||||
{
|
||||
JS_ALWAYS_FALSE(abortIfSyntaxParser());
|
||||
return SyntaxParseHandler::NodeFailure;
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
typename ParseHandler::Node
|
||||
Parser<ParseHandler>::statement(bool canHaveDirectives)
|
||||
|
@ -5854,6 +5964,11 @@ Parser<ParseHandler>::statement(bool canHaveDirectives)
|
|||
return functionStmt();
|
||||
case TOK_DEBUGGER:
|
||||
return debuggerStatement();
|
||||
case TOK_CLASS:
|
||||
if (!abortIfSyntaxParser())
|
||||
return null();
|
||||
return classStatement();
|
||||
|
||||
|
||||
/* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */
|
||||
case TOK_CATCH:
|
||||
|
@ -6408,7 +6523,7 @@ Parser<ParseHandler>::unaryExpr(InvokedPrediction invoked)
|
|||
// Per spec, deleting any unary expression is valid -- it simply
|
||||
// returns true -- except for one case that is illegal in strict mode.
|
||||
if (handler.isName(expr)) {
|
||||
if (!report(ParseStrictError, pc->sc->strict, expr, JSMSG_DEPRECATED_DELETE_OPERAND))
|
||||
if (!report(ParseStrictError, pc->sc->strict(), expr, JSMSG_DEPRECATED_DELETE_OPERAND))
|
||||
return null();
|
||||
pc->sc->setBindingsAccessedDynamically();
|
||||
}
|
||||
|
@ -6817,7 +6932,7 @@ Parser<FullParseHandler>::legacyComprehensionTail(ParseNode *bodyExpr, unsigned
|
|||
pn2->pn_iflags |= JSITER_FOREACH;
|
||||
addTelemetry(JSCompartment::DeprecatedForEach);
|
||||
if (versionNumber() < JSVERSION_LATEST) {
|
||||
if (!report(ParseWarning, pc->sc->strict, pn2, JSMSG_DEPRECATED_FOR_EACH))
|
||||
if (!report(ParseWarning, pc->sc->strict(), pn2, JSMSG_DEPRECATED_FOR_EACH))
|
||||
return null();
|
||||
}
|
||||
}
|
||||
|
@ -7073,7 +7188,7 @@ Parser<ParseHandler>::generatorComprehensionLambda(GeneratorKind comprehensionKi
|
|||
return null();
|
||||
|
||||
// Create box for fun->object early to root it.
|
||||
Directives directives(/* strict = */ outerpc->sc->strict);
|
||||
Directives directives(/* strict = */ outerpc->sc->strict());
|
||||
FunctionBox *genFunbox = newFunctionBox(genfn, fun, outerpc, directives, comprehensionKind);
|
||||
if (!genFunbox)
|
||||
return null();
|
||||
|
@ -7591,7 +7706,7 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred
|
|||
if (JSAtom *atom = handler.isName(lhs)) {
|
||||
if (tt == TOK_LP && atom == context->names().eval) {
|
||||
/* Select JSOP_EVAL and flag pc as heavyweight. */
|
||||
op = pc->sc->strict ? JSOP_STRICTEVAL : JSOP_EVAL;
|
||||
op = pc->sc->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
|
||||
pc->sc->setBindingsAccessedDynamically();
|
||||
pc->sc->setHasDirectEval();
|
||||
|
||||
|
@ -7599,7 +7714,7 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred
|
|||
* In non-strict mode code, direct calls to eval can add
|
||||
* variables to the call object.
|
||||
*/
|
||||
if (pc->sc->isFunctionBox() && !pc->sc->strict)
|
||||
if (pc->sc->isFunctionBox() && !pc->sc->strict())
|
||||
pc->sc->asFunctionBox()->setHasExtensibleScope();
|
||||
}
|
||||
} else if (JSAtom *atom = handler.isGetProp(lhs)) {
|
||||
|
@ -7885,15 +8000,27 @@ Parser<ParseHandler>::computedPropertyName(Node literal)
|
|||
|
||||
template <typename ParseHandler>
|
||||
typename ParseHandler::Node
|
||||
Parser<ParseHandler>::objectLiteral()
|
||||
Parser<ParseHandler>::newPropertyListNode(PropListType type)
|
||||
{
|
||||
if (type == ClassBody)
|
||||
return handler.newClassMethodList(pos().begin);
|
||||
|
||||
MOZ_ASSERT(type == ObjectLiteral);
|
||||
return handler.newObjectLiteral(pos().begin);
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
typename ParseHandler::Node
|
||||
Parser<ParseHandler>::propertyList(PropListType type)
|
||||
{
|
||||
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
|
||||
|
||||
Node literal = handler.newObjectLiteral(pos().begin);
|
||||
if (!literal)
|
||||
Node propList = newPropertyListNode(type);
|
||||
if (!propList)
|
||||
return null();
|
||||
|
||||
bool seenPrototypeMutation = false;
|
||||
bool seenConstructor = false;
|
||||
RootedAtom atom(context);
|
||||
for (;;) {
|
||||
TokenKind ltok;
|
||||
|
@ -7902,6 +8029,9 @@ Parser<ParseHandler>::objectLiteral()
|
|||
if (ltok == TOK_RC)
|
||||
break;
|
||||
|
||||
if (type == ClassBody && ltok == TOK_SEMI)
|
||||
continue;
|
||||
|
||||
bool isGenerator = false;
|
||||
if (ltok == TOK_MUL) {
|
||||
isGenerator = true;
|
||||
|
@ -7924,7 +8054,7 @@ Parser<ParseHandler>::objectLiteral()
|
|||
break;
|
||||
|
||||
case TOK_LB: {
|
||||
propname = computedPropertyName(literal);
|
||||
propname = computedPropertyName(propList);
|
||||
if (!propname)
|
||||
return null();
|
||||
break;
|
||||
|
@ -7980,7 +8110,7 @@ Parser<ParseHandler>::objectLiteral()
|
|||
if (!propname)
|
||||
return null();
|
||||
} else if (tt == TOK_LB) {
|
||||
propname = computedPropertyName(literal);
|
||||
propname = computedPropertyName(propList);
|
||||
if (!propname)
|
||||
return null();
|
||||
} else {
|
||||
|
@ -8017,12 +8147,30 @@ Parser<ParseHandler>::objectLiteral()
|
|||
return null();
|
||||
}
|
||||
|
||||
if (type == ClassBody) {
|
||||
if (atom == context->names().constructor) {
|
||||
if (isGenerator || op != JSOP_INITPROP) {
|
||||
report(ParseError, false, propname, JSMSG_BAD_METHOD_DEF);
|
||||
return null();
|
||||
}
|
||||
if (seenConstructor) {
|
||||
report(ParseError, false, propname, JSMSG_DUPLICATE_PROPERTY, "constructor");
|
||||
return null();
|
||||
}
|
||||
seenConstructor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (op == JSOP_INITPROP) {
|
||||
TokenKind tt;
|
||||
if (!tokenStream.getToken(&tt))
|
||||
return null();
|
||||
|
||||
if (tt == TOK_COLON) {
|
||||
if (type == ClassBody) {
|
||||
report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF);
|
||||
return null();
|
||||
}
|
||||
if (isGenerator) {
|
||||
report(ParseError, false, null(), JSMSG_BAD_PROP_ID);
|
||||
return null();
|
||||
|
@ -8047,13 +8195,13 @@ Parser<ParseHandler>::objectLiteral()
|
|||
// method/generator definitions, computed property name
|
||||
// versions of all of these, and shorthands do not.
|
||||
uint32_t begin = handler.getPosition(propname).begin;
|
||||
if (!handler.addPrototypeMutation(literal, begin, propexpr))
|
||||
if (!handler.addPrototypeMutation(propList, begin, propexpr))
|
||||
return null();
|
||||
} else {
|
||||
if (!handler.isConstant(propexpr))
|
||||
handler.setListFlag(literal, PNX_NONCONST);
|
||||
handler.setListFlag(propList, PNX_NONCONST);
|
||||
|
||||
if (!handler.addPropertyDefinition(literal, propname, propexpr))
|
||||
if (!handler.addPropertyDefinition(propList, propname, propexpr))
|
||||
return null();
|
||||
}
|
||||
} else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) {
|
||||
|
@ -8061,6 +8209,10 @@ Parser<ParseHandler>::objectLiteral()
|
|||
* Support, e.g., |var {x, y} = o| as destructuring shorthand
|
||||
* for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
|
||||
*/
|
||||
if (type == ClassBody) {
|
||||
report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF);
|
||||
return null();
|
||||
}
|
||||
if (isGenerator) {
|
||||
report(ParseError, false, null(), JSMSG_BAD_PROP_ID);
|
||||
return null();
|
||||
|
@ -8074,11 +8226,11 @@ Parser<ParseHandler>::objectLiteral()
|
|||
if (!nameExpr)
|
||||
return null();
|
||||
|
||||
if (!handler.addShorthand(literal, propname, nameExpr))
|
||||
if (!handler.addShorthand(propList, propname, nameExpr))
|
||||
return null();
|
||||
} else if (tt == TOK_LP) {
|
||||
tokenStream.ungetToken();
|
||||
if (!methodDefinition(literal, propname, Normal, Method,
|
||||
if (!methodDefinition(type, propList, propname, Normal, Method,
|
||||
isGenerator ? StarGenerator : NotGenerator, op)) {
|
||||
return null();
|
||||
}
|
||||
|
@ -8088,32 +8240,40 @@ Parser<ParseHandler>::objectLiteral()
|
|||
}
|
||||
} else {
|
||||
/* NB: Getter function in { get x(){} } is unnamed. */
|
||||
if (!methodDefinition(literal, propname, op == JSOP_INITPROP_GETTER ? Getter : Setter,
|
||||
if (!methodDefinition(type, propList, propname, op == JSOP_INITPROP_GETTER ? Getter : Setter,
|
||||
Expression, NotGenerator, op)) {
|
||||
return null();
|
||||
}
|
||||
}
|
||||
|
||||
TokenKind tt;
|
||||
if (!tokenStream.getToken(&tt))
|
||||
return null();
|
||||
if (tt == TOK_RC)
|
||||
break;
|
||||
if (tt != TOK_COMMA) {
|
||||
report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST);
|
||||
return null();
|
||||
if (type == ObjectLiteral) {
|
||||
TokenKind tt;
|
||||
if (!tokenStream.getToken(&tt))
|
||||
return null();
|
||||
if (tt == TOK_RC)
|
||||
break;
|
||||
if (tt != TOK_COMMA) {
|
||||
report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST);
|
||||
return null();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler.setEndPosition(literal, pos().end);
|
||||
return literal;
|
||||
// Default constructors not yet implemented. See bug 1105463
|
||||
if (type == ClassBody && !seenConstructor) {
|
||||
report(ParseError, false, null(), JSMSG_NO_CLASS_CONSTRUCTOR);
|
||||
return null();
|
||||
}
|
||||
|
||||
handler.setEndPosition(propList, pos().end);
|
||||
return propList;
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::methodDefinition(Node literal, Node propname, FunctionType type,
|
||||
FunctionSyntaxKind kind, GeneratorKind generatorKind,
|
||||
JSOp op)
|
||||
Parser<ParseHandler>::methodDefinition(PropListType listType, Node propList, Node propname,
|
||||
FunctionType type, FunctionSyntaxKind kind,
|
||||
GeneratorKind generatorKind, JSOp op)
|
||||
{
|
||||
RootedPropertyName funName(context);
|
||||
if (kind == Method && tokenStream.isCurrentTokenType(TOK_NAME))
|
||||
|
@ -8124,9 +8284,12 @@ Parser<ParseHandler>::methodDefinition(Node literal, Node propname, FunctionType
|
|||
Node fn = functionDef(funName, type, kind, generatorKind);
|
||||
if (!fn)
|
||||
return false;
|
||||
if (!handler.addMethodDefinition(literal, propname, fn, op))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
if (listType == ClassBody)
|
||||
return handler.addClassMethodDefinition(propList, propname, fn, op);
|
||||
|
||||
MOZ_ASSERT(listType == ObjectLiteral);
|
||||
return handler.addObjectMethodDefinition(propList, propname, fn, op);
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
|
@ -8144,7 +8307,7 @@ Parser<ParseHandler>::primaryExpr(TokenKind tt, InvokedPrediction invoked)
|
|||
return arrayInitializer();
|
||||
|
||||
case TOK_LC:
|
||||
return objectLiteral();
|
||||
return propertyList(ObjectLiteral);
|
||||
|
||||
case TOK_LET:
|
||||
return deprecatedLetBlockOrExpression(LetExpression);
|
||||
|
|
|
@ -301,6 +301,7 @@ struct ParseContext : public GenericParseContext
|
|||
// if (cond) { function f3() { if (cond) { function f4() { } } } }
|
||||
//
|
||||
bool atBodyLevel() { return !topStmt; }
|
||||
bool atGlobalLevel() { return atBodyLevel() && !sc->isFunctionBox() && (topStmt == topScopeStmt); }
|
||||
|
||||
// True if this is the ParseContext for the body of a function created by
|
||||
// the Function constructor.
|
||||
|
@ -316,7 +317,7 @@ struct ParseContext : public GenericParseContext
|
|||
template <typename ParseHandler>
|
||||
inline
|
||||
Directives::Directives(ParseContext<ParseHandler> *parent)
|
||||
: strict_(parent->sc->strict),
|
||||
: strict_(parent->sc->strict()),
|
||||
asmJS_(parent->useAsmOrInsideUseAsm())
|
||||
{}
|
||||
|
||||
|
@ -328,6 +329,7 @@ class CompExprTransplanter;
|
|||
enum LetContext { LetExpression, LetStatement };
|
||||
enum VarContext { HoistVars, DontHoistVars };
|
||||
enum FunctionType { Getter, Setter, Normal };
|
||||
enum PropListType { ObjectLiteral, ClassBody };
|
||||
|
||||
template <typename ParseHandler>
|
||||
class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
||||
|
@ -503,7 +505,11 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
|||
return versionNumber() >= JSVERSION_1_7 || pc->isGenerator();
|
||||
}
|
||||
|
||||
virtual bool strictMode() { return pc->sc->strict; }
|
||||
virtual bool strictMode() { return pc->sc->strict(); }
|
||||
bool setLocalStrictMode(bool strict) {
|
||||
MOZ_ASSERT(tokenStream.debugHasNoLookahead());
|
||||
return pc->sc->setLocalStrictMode(strict);
|
||||
}
|
||||
|
||||
const ReadOnlyCompileOptions &options() const {
|
||||
return tokenStream.options();
|
||||
|
@ -547,6 +553,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
|||
Node throwStatement();
|
||||
Node tryStatement();
|
||||
Node debuggerStatement();
|
||||
Node classStatement();
|
||||
|
||||
Node lexicalDeclaration(bool isConst);
|
||||
Node letDeclarationOrBlock();
|
||||
|
@ -569,8 +576,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
|||
Node parenExprOrGeneratorComprehension();
|
||||
Node exprInParens();
|
||||
|
||||
bool methodDefinition(Node literal, Node propname, FunctionType type, FunctionSyntaxKind kind,
|
||||
GeneratorKind generatorKind, JSOp Op);
|
||||
bool methodDefinition(PropListType listType, Node propList, Node propname, FunctionType type,
|
||||
FunctionSyntaxKind kind, GeneratorKind generatorKind, JSOp Op);
|
||||
|
||||
/*
|
||||
* Additional JS parsers.
|
||||
|
@ -651,16 +658,21 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
|||
Node pushLexicalScope(Handle<StaticBlockObject*> blockObj, StmtInfoPC *stmt);
|
||||
Node pushLetScope(Handle<StaticBlockObject*> blockObj, StmtInfoPC *stmt);
|
||||
bool noteNameUse(HandlePropertyName name, Node pn);
|
||||
Node objectLiteral();
|
||||
Node computedPropertyName(Node literal);
|
||||
Node arrayInitializer();
|
||||
Node newRegExp();
|
||||
|
||||
Node propertyList(PropListType type);
|
||||
Node newPropertyListNode(PropListType type);
|
||||
|
||||
bool checkAndPrepareLexical(bool isConst, const TokenPos &errorPos);
|
||||
Node makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos &pos);
|
||||
|
||||
Node newBindingNode(PropertyName *name, bool functionScope, VarContext varContext = HoistVars);
|
||||
bool checkDestructuring(BindData<ParseHandler> *data, Node left);
|
||||
bool checkDestructuringObject(BindData<ParseHandler> *data, Node objectPattern);
|
||||
bool checkDestructuringArray(BindData<ParseHandler> *data, Node arrayPattern);
|
||||
bool bindDestructuringVar(BindData<ParseHandler> *data, Node pn);
|
||||
bool bindInitialized(BindData<ParseHandler> *data, Node pn);
|
||||
bool bindDestructuringLHS(Node pn);
|
||||
bool makeSetCall(Node pn, unsigned msg);
|
||||
Node cloneDestructuringDefault(Node opn);
|
||||
|
|
|
@ -178,7 +178,8 @@ class SharedContext
|
|||
public:
|
||||
ExclusiveContext *const context;
|
||||
AnyContextFlags anyCxFlags;
|
||||
bool strict;
|
||||
bool strictScript;
|
||||
bool localStrict;
|
||||
bool extraWarnings;
|
||||
|
||||
// If it's function code, funbox must be non-nullptr and scopeChain must be
|
||||
|
@ -186,7 +187,8 @@ class SharedContext
|
|||
SharedContext(ExclusiveContext *cx, Directives directives, bool extraWarnings)
|
||||
: context(cx),
|
||||
anyCxFlags(),
|
||||
strict(directives.strict()),
|
||||
strictScript(directives.strict()),
|
||||
localStrict(false),
|
||||
extraWarnings(extraWarnings)
|
||||
{}
|
||||
|
||||
|
@ -208,9 +210,18 @@ class SharedContext
|
|||
|
||||
inline bool allLocalsAliased();
|
||||
|
||||
bool strict() {
|
||||
return strictScript || localStrict;
|
||||
}
|
||||
bool setLocalStrictMode(bool strict) {
|
||||
bool retVal = localStrict;
|
||||
localStrict = strict;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// JSOPTION_EXTRA_WARNINGS warnings or strict mode errors.
|
||||
bool needStrictChecks() {
|
||||
return strict || extraWarnings;
|
||||
return strict() || extraWarnings;
|
||||
}
|
||||
|
||||
bool isDotVariable(JSAtom *atom) const {
|
||||
|
|
|
@ -186,10 +186,12 @@ class SyntaxParseHandler
|
|||
void addArrayElement(Node literal, Node element) { }
|
||||
|
||||
Node newObjectLiteral(uint32_t begin) { return NodeGeneric; }
|
||||
Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
|
||||
bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
|
||||
bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
|
||||
bool addShorthand(Node literal, Node name, Node expr) { return true; }
|
||||
bool addMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
|
||||
bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
|
||||
bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
|
||||
Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeUnparenthesizedYieldExpr; }
|
||||
Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
macro(LET, "keyword 'let'") \
|
||||
macro(EXPORT, "keyword 'export'") \
|
||||
macro(IMPORT, "keyword 'import'") \
|
||||
macro(CLASS, "keyword 'class'") \
|
||||
macro(RESERVED, "reserved keyword") \
|
||||
/* reserved keywords in strict mode */ \
|
||||
macro(STRICT_RESERVED, "reserved keyword") \
|
||||
|
|
|
@ -976,8 +976,14 @@ TokenStream::putIdentInTokenbuf(const char16_t *identStart)
|
|||
bool
|
||||
TokenStream::checkForKeyword(const KeywordInfo *kw, TokenKind *ttp)
|
||||
{
|
||||
if (kw->tokentype == TOK_RESERVED)
|
||||
if (kw->tokentype == TOK_RESERVED
|
||||
#ifndef JS_HAS_CLASSES
|
||||
|| kw->tokentype == TOK_CLASS
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return reportError(JSMSG_RESERVED_ID, kw->chars);
|
||||
}
|
||||
|
||||
if (kw->tokentype != TOK_STRICT_RESERVED) {
|
||||
if (kw->version <= versionNumber()) {
|
||||
|
|
|
@ -531,6 +531,11 @@ class MOZ_STACK_CLASS TokenStream
|
|||
void tell(Position *);
|
||||
void seek(const Position &pos);
|
||||
bool seek(const Position &pos, const TokenStream &other);
|
||||
#ifdef DEBUG
|
||||
inline bool debugHasNoLookahead() const {
|
||||
return lookahead == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char16_t *rawCharPtrAt(size_t offset) const {
|
||||
return userbuf.rawCharPtrAt(offset);
|
||||
|
|
|
@ -802,11 +802,11 @@ TypeSet::MarkTypeRoot(JSTracer *trc, TypeSet::Type *v, const char *name)
|
|||
{
|
||||
JS_ROOT_MARKING_ASSERT(trc);
|
||||
trc->setTracingName(name);
|
||||
if (v->isSingleton()) {
|
||||
if (v->isSingletonUnchecked()) {
|
||||
JSObject *obj = v->singleton();
|
||||
MarkInternal(trc, &obj);
|
||||
*v = TypeSet::ObjectType(obj);
|
||||
} else if (v->isGroup()) {
|
||||
} else if (v->isGroupUnchecked()) {
|
||||
ObjectGroup *group = v->group();
|
||||
MarkInternal(trc, &group);
|
||||
*v = TypeSet::ObjectType(group);
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче