Merge mozilla-central to mozilla-inbound

This commit is contained in:
Iris Hsiao 2017-05-18 14:16:22 +08:00
Родитель 5308137759 77020e4e53
Коммит 34b5af799e
147 изменённых файлов: 3313 добавлений и 948 удалений

Просмотреть файл

@ -18,6 +18,10 @@ Cu.import("resource://gre/modules/NotificationDB.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyGetter(this, "extensionNameFromURI", () => {
return Cu.import("resource://gre/modules/ExtensionParent.jsm", {}).extensionNameFromURI;
});
// lazy module getters
/* global AboutHome:false,
@ -6879,6 +6883,11 @@ var gIdentityHandler = {
*/
_uriHasHost: false,
/**
* Whether this is a "moz-extension:" page, loaded from a WebExtension.
*/
_isExtensionPage: false,
/**
* Whether this._uri refers to an internally implemented browser page.
*
@ -7013,6 +7022,10 @@ var gIdentityHandler = {
delete this._connectionIcon;
return this._connectionIcon = document.getElementById("connection-icon");
},
get _extensionIcon() {
delete this._extensionIcon;
return this._extensionIcon = document.getElementById("extension-icon");
},
get _overrideService() {
delete this._overrideService;
return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
@ -7291,7 +7304,11 @@ var gIdentityHandler = {
icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
"rtl" : "ltr";
}
} else if (this._isExtensionPage) {
this._identityBox.className = "extensionPage";
let extensionName = extensionNameFromURI(this._uri);
icon_label = gNavigatorBundle.getFormattedString(
"identity.extension.label", [extensionName]);
} else if (this._uriHasHost && this._isSecure) {
this._identityBox.className = "verifiedDomain";
if (this._isMixedActiveContentBlocked) {
@ -7359,6 +7376,13 @@ var gIdentityHandler = {
// Push the appropriate strings out to the UI
this._connectionIcon.tooltipText = tooltip;
if (this._isExtensionPage) {
let extensionName = extensionNameFromURI(this._uri);
this._extensionIcon.tooltipText = gNavigatorBundle.getFormattedString(
"identity.extension.tooltip", [extensionName]);
}
this._identityIconLabels.tooltipText = tooltip;
this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip");
this._identityIconLabel.value = icon_label;
@ -7390,6 +7414,8 @@ var gIdentityHandler = {
let connection = "not-secure";
if (this._isSecureInternalUI) {
connection = "chrome";
} else if (this._isExtensionPage) {
connection = "extension";
} else if (this._isURILoadedFromFile) {
connection = "file";
} else if (this._isEV) {
@ -7470,6 +7496,10 @@ var gIdentityHandler = {
hostless = true;
}
if (this._isExtensionPage) {
host = extensionNameFromURI(this._uri);
}
// Fill in the CA name if we have a valid TLS certificate.
if (this._isSecure || this._isCertUserOverridden) {
verifier = this._identityIconLabels.tooltipText;
@ -7523,6 +7553,8 @@ var gIdentityHandler = {
let whitelist = /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|searchreset|sessionrestore|support|welcomeback)(?:[?#]|$)/i;
this._isSecureInternalUI = uri.schemeIs("about") && whitelist.test(uri.path);
this._isExtensionPage = uri.schemeIs("moz-extension");
// Create a channel for the sole purpose of getting the resolved URI
// of the request to determine if it's loaded from the file system.
this._isURILoadedFromFile = false;

Просмотреть файл

@ -846,6 +846,7 @@
tooltiptext="&urlbar.persistentStorageNotificationAnchor.tooltip;"/>
</box>
<image id="connection-icon"/>
<image id="extension-icon"/>
<image id="remote-control-icon"
tooltiptext="&urlbar.remoteControlNotificationAnchor.tooltip;"/>
<hbox id="identity-icon-labels">

Просмотреть файл

@ -3,12 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* globals
waitForExplicitFinish, whenNewWindowLoaded, whenNewTabLoaded,
executeSoon, registerCleanupFunction, finish, is
*/
/* exported test */
// This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing
function test() {
// initialization

Просмотреть файл

@ -2,9 +2,6 @@
* 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";
/* globals waitForExplicitFinish, executeSoon, finish, whenNewWindowLoaded, ok */
/* globals is */
/* exported test */
function test() {
// initialization

Просмотреть файл

@ -32,6 +32,7 @@
when-connection="secure secure-ev">&identity.connectionSecure;</description>
<description when-connection="chrome">&identity.connectionInternal;</description>
<description when-connection="file">&identity.connectionFile;</description>
<description when-connection="extension">&identity.extensionPage;</description>
<vbox id="identity-popup-security-descriptions">
<description class="identity-popup-warning-gray"

Просмотреть файл

@ -1,7 +1,6 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* eslint-env worker */
/* globals OS, ParseSymbols */
"use strict";

Просмотреть файл

@ -54,7 +54,6 @@ extensions.on("page-shutdown", (type, context) => {
}
});
/* eslint-enable mozilla/balanced-listeners */
/* eslint-enable mozilla/balanced-listeners */
global.openOptionsPage = (extension) => {
let window = windowTracker.topWindow;

Просмотреть файл

@ -5,7 +5,6 @@
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
/* globals TabBase, WindowBase, TabTrackerBase, WindowTrackerBase, TabManagerBase, WindowManagerBase */
Cu.import("resource://gre/modules/ExtensionTabs.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",

Просмотреть файл

@ -7,29 +7,6 @@ module.exports = {
"webextensions": true,
},
"globals": {
"NetUtil": true,
"XPCOMUtils": true,
"Task": true,
// Browser window globals.
"PanelUI": false,
// Test harness globals
"ExtensionTestUtils": false,
"TestUtils": false,
"clickBrowserAction": true,
"clickPageAction": true,
"closeContextMenu": true,
"closeExtensionContextMenu": true,
"focusWindow": true,
"makeWidgetId": true,
"openContextMenu": true,
"openExtensionContextMenu": true,
"CustomizableUI": true,
},
"rules": {
"no-shadow": 0,
},

Просмотреть файл

@ -66,6 +66,7 @@ support-files =
[browser_ext_devtools_panel.js]
[browser_ext_geckoProfiler_symbolicate.js]
[browser_ext_getViews.js]
[browser_ext_identity_indication.js]
[browser_ext_incognito_views.js]
[browser_ext_incognito_popup.js]
[browser_ext_lastError.js]

Просмотреть файл

@ -0,0 +1,61 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function confirmDefaults() {
let identityIconURL = getComputedStyle(document.getElementById("identity-icon")).listStyleImage;
is(identityIconURL, "url(\"chrome://browser/skin/identity-icon.svg#normal\")", "Identity icon should be the default identity icon");
let connectionIconURL = getComputedStyle(document.getElementById("connection-icon")).listStyleImage;
is(connectionIconURL, "none", "Connection icon should not be displayed");
let extensionIconURL = getComputedStyle(document.getElementById("extension-icon")).listStyleImage;
is(extensionIconURL, "none", "Extension icon should not be displayed");
let label = document.getElementById("identity-icon-label").value;
is(label, "", "No label should be used before the extension is started");
}
function confirmExtensionPage() {
let identityIcon = getComputedStyle(document.getElementById("identity-icon")).listStyleImage;
is(identityIcon, "url(\"chrome://browser/skin/identity-icon.svg#normal\")", "Identity icon should be the default identity icon");
let connectionIconURL = getComputedStyle(document.getElementById("connection-icon")).listStyleImage;
is(connectionIconURL, "none", "Connection icon should not be displayed");
let extensionIconEl = document.getElementById("extension-icon");
let extensionIconURL = getComputedStyle(extensionIconEl).listStyleImage;
is(extensionIconURL, "url(\"chrome://browser/skin/controlcenter/extension.svg\")", "Extension icon should be the default extension icon");
let tooltip = extensionIconEl.tooltipText;
is(tooltip, "Loaded by extension: Test Extension", "The correct tooltip should be used");
let label = document.getElementById("identity-icon-label").value;
is(label, "Extension (Test Extension)", "The correct label should be used");
}
add_task(async function testIdentityIndication() {
let extension = ExtensionTestUtils.loadExtension({
background() {
browser.test.sendMessage("url", browser.extension.getURL("icon.png"));
},
manifest: {
name: "Test Extension",
},
files: {
"icon.png": "",
},
});
await extension.startup();
confirmDefaults();
let url = await extension.awaitMessage("url");
await BrowserTestUtils.withNewTab({gBrowser, url}, async function() {
confirmExtensionPage();
});
await extension.unload();
confirmDefaults();
});

Просмотреть файл

@ -2,8 +2,6 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* global runTests */
Services.scriptloader.loadSubScript(new URL("head_pageAction.js", gTestPath).href,
this);

Просмотреть файл

@ -2,8 +2,6 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* global runTests */
Services.scriptloader.loadSubScript(new URL("head_pageAction.js", gTestPath).href,
this);

Просмотреть файл

@ -2,8 +2,6 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
requestLongerTimeout(2);
Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,

Просмотреть файл

@ -2,8 +2,6 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
SimpleTest.requestCompleteLog();
Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,

Просмотреть файл

@ -1,8 +1,8 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* globals makeExtension */
"use strict";
/* import-globals-from ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js */
Services.scriptloader.loadSubScript(new URL("head_webrequest.js", gTestPath).href,
this);
@ -117,4 +117,3 @@ add_task(async function test_subframe() {
add_task(async function teardown() {
await extension.unload();
});

Просмотреть файл

@ -371,7 +371,6 @@ async function clickPageAction(extension, win = window) {
//
// Unfortunately, that doesn't happen automatically in browser chrome
// tests.
/* globals SetPageProxyState */
SetPageProxyState("valid");
await promiseAnimationFrame(win);

Просмотреть файл

@ -3,7 +3,8 @@
"use strict";
/* exported runTests */
/* globals getListStyleImage, promiseAnimationFrame */
// This file is imported into the same scope as head.js.
/* import-globals-from head.js */
async function runTests(options) {
function background(getTests) {
@ -157,4 +158,3 @@ async function runTests(options) {
await BrowserTestUtils.closeWindow(win);
}
}

Просмотреть файл

@ -3,8 +3,6 @@
* 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/. */
/* global BrowserFeedWriter */
var SubscribeHandler = {
/**
* The nsIFeedWriter object that produces the UI

Просмотреть файл

@ -10,8 +10,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");

Просмотреть файл

@ -46,7 +46,6 @@ XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
Cu.importGlobalProperties(["URL"]);
/* globals kUndoStateFullPath */
XPCOMUtils.defineLazyGetter(this, "kUndoStateFullPath", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "initialMigrationMetadata.jsonlz4");
});

Просмотреть файл

@ -24,8 +24,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");

Просмотреть файл

@ -7,10 +7,10 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MSMigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");

Просмотреть файл

@ -16,7 +16,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",

Просмотреть файл

@ -13,10 +13,10 @@ const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storag
const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MSMigrationUtils.jsm");

Просмотреть файл

@ -10,10 +10,10 @@ var Cu = Components.utils;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");

Просмотреть файл

@ -24,7 +24,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
// Initialize profile.
var gProfD = do_get_profile();
Cu.import("resource://testing-common/AppInfo.jsm"); /* globals updateAppInfo */
Cu.import("resource://testing-common/AppInfo.jsm");
updateAppInfo();
/**

Просмотреть файл

@ -28,7 +28,7 @@ add_task(async function() {
// importing osfile will sometimes greedily fetch certain path identifiers
// from the dir service, which means they get cached, which means we can't
// register a fake path for them anymore.
Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
Cu.import("resource://gre/modules/osfile.jsm");
await OS.File.makeDir(target.path, {from: rootDir.parent.path, ignoreExisting: true});
target.append("Bookmarks");

Просмотреть файл

@ -1,8 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* globals do_get_tempdir */
"use strict";
function run_test() {

Просмотреть файл

@ -1,6 +1,3 @@
/* global Services, Preferences, EventEmitter, XPCOMUtils */
/* exported NewTabPrefsProvider */
"use strict";
this.EXPORTED_SYMBOLS = ["NewTabPrefsProvider"];

Просмотреть файл

@ -1,6 +1,3 @@
/* global XPCOMUtils, ContentSearch, Task, Services, EventEmitter */
/* exported NewTabSearchProvider */
"use strict";
this.EXPORTED_SYMBOLS = ["NewTabSearchProvider"];

Просмотреть файл

@ -2,9 +2,6 @@
* 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/. */
/* globals XPCOMUtils, aboutNewTabService*/
/* exported NewTabURL */
"use strict";
const {utils: Cu} = Components;

Просмотреть файл

@ -1,14 +1,3 @@
/* global
NewTabPrefsProvider,
Services,
EventEmitter,
Preferences,
XPCOMUtils,
WebChannel,
NewTabRemoteResources
*/
/* exported NewTabWebChannel */
"use strict";
this.EXPORTED_SYMBOLS = ["NewTabWebChannel"];

Просмотреть файл

@ -1,6 +1,3 @@
/* global XPCOMUtils, BackgroundPageThumbs, FileUtils, PageThumbsStorage, Task, MIMEService */
/* exported PreviewProvider */
"use strict";
this.EXPORTED_SYMBOLS = ["PreviewProvider"];

Просмотреть файл

@ -4,7 +4,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* globals XPCOMUtils, Preferences, Services */
"use strict";
const {utils: Cu, interfaces: Ci} = Components;

Просмотреть файл

@ -1,4 +1,3 @@
/* globals XPCOMUtils, Services, PreviewProvider, registerCleanupFunction */
"use strict";
let Cu = Components.utils;

Просмотреть файл

@ -1,15 +1,3 @@
/* globals
XPCOMUtils,
aboutNewTabService,
Services,
ContentTask,
TestUtils,
BrowserOpenTab,
registerCleanupFunction,
is,
content
*/
"use strict";
let Cu = Components.utils;

Просмотреть файл

@ -1,4 +1,3 @@
/* globals Cu, XPCOMUtils, TestUtils, aboutNewTabService, ContentTask, content, is */
"use strict";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

Просмотреть файл

@ -2,8 +2,6 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* globals Services, XPCOMUtils, Preferences, aboutNewTabService, do_register_cleanup */
"use strict";
const {utils: Cu} = Components;

Просмотреть файл

@ -1,8 +1,5 @@
"use strict";
/* global XPCOMUtils, equal, Preferences, NewTabPrefsProvider, run_next_test */
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");

Просмотреть файл

@ -1,8 +1,5 @@
"use strict";
/* global XPCOMUtils, NewTabSearchProvider, run_next_test, ok, equal, do_check_true, do_get_profile, Services */
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

Просмотреть файл

@ -2,7 +2,6 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* globals Services, NewTabURL, XPCOMUtils, aboutNewTabService */
"use strict";
const {utils: Cu} = Components;

Просмотреть файл

@ -1,6 +1,6 @@
self.randomValue = Math.random();
/* global onconnect:true */
/* eslint-env worker */
onconnect = function(e) {
let port = e.ports[0];

Просмотреть файл

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/mochitest-test"
]
};

Просмотреть файл

@ -15,7 +15,6 @@
<body>
<pre id="test"></pre>
<script type="application/javascript">
/* globals SpecialPowers, SimpleTest, is, ok, */
"use strict";
const {

Просмотреть файл

@ -7,11 +7,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
"resource://testing-common/TestUtils.jsm");
// Imported via PlacesOverlay.xul
/* global doGetPlacesControllerForCommand:false, PlacesControllerDragHelper:false,
PlacesUIUtils:false
*/
// We need to cache this before test runs...
var cachedLeftPaneFolderIdGetter;
var getter = PlacesUIUtils.__lookupGetter__("leftPaneFolderId");

Просмотреть файл

@ -3,8 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// These globals are imported via placesOverlay.xul.
/* globals PlacesUIUtils, PlacesUtils, NS_ASSERT */
/* eslint-env mozilla/places-overlay */
/**
* SelectBookmarkDialog controls the user interface for the "Use Bookmark for

Просмотреть файл

@ -1426,6 +1426,11 @@
// Add weak referenced observers to invalidate our cached list of engines.
Services.prefs.addObserver("browser.search.hiddenOneOffs", this, true);
Services.obs.addObserver(this, "browser-search-engine-modified", true);
// Rebuild the buttons when the theme changes. See bug 1357800 for
// details. Summary: On Linux, switching between themes can cause a row
// of buttons to disappear.
Services.obs.addObserver(this, "lightweight-theme-changed", true);
]]></constructor>
<!-- This handles events outside the one-off buttons, like on the popup

Просмотреть файл

@ -8,7 +8,7 @@
"use strict";
/* global content */
/* eslint-env mozilla/frame-script */
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global content:false, sendAsyncMessage:false */
/* eslint-env mozilla/frame-script */
let { utils: Cu } = Components;

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global content:false, addMessageListener:false, removeMessageListener: false */
/* eslint-env mozilla/frame-script */
let { utils: Cu } = Components;

Просмотреть файл

@ -2,8 +2,6 @@
* 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/. */
/* global AddonManager */
"use strict";
add_task(async function installed() {

Просмотреть файл

@ -2,8 +2,6 @@
* 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/. */
/* globals XPCOMUtils, UAOverrider, IOService */
"use strict";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

Просмотреть файл

@ -729,6 +729,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY identity.connectionFile "This page is stored on your computer.">
<!ENTITY identity.connectionVerified2 "You are securely connected to this site, owned by:">
<!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
<!ENTITY identity.extensionPage "This page is loaded from an extension.">
<!ENTITY identity.insecureLoginForms2 "Logins entered on this page could be compromised.">
<!-- Strings for connection state warnings. -->

Просмотреть файл

@ -450,6 +450,8 @@ identity.identified.verified_by_you=You have added a security exception for this
identity.identified.state_and_country=%S, %S
identity.icon.tooltip=Show site information
identity.extension.label=Extension (%S)
identity.extension.tooltip=Loaded by extension: %S
identity.showDetails.tooltip=Show connection details
identity.hideDetails.tooltip=Hide connection details

Просмотреть файл

@ -1,7 +1,6 @@
/* 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/. */
/* globals XPCOMUtils, Services, Task, Promise, SearchSuggestionController, FormHistory, PrivateBrowsingUtils */
"use strict";
this.EXPORTED_SYMBOLS = [

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global addMessageListener:false, sendAsyncMessage:false, XPCOMUtils */
/* eslint-env mozilla/frame-script */
const TEST_MSG = "ContentSearchTest";
const SERVICE_EVENT_TYPE = "ContentSearchService";

Просмотреть файл

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="64" height="64" viewBox="0 0 64 64">
#include ../icon-colors.inc.svg
<path class="fieldtext" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

Просмотреть файл

@ -16,6 +16,7 @@
#identity-popup[connection=secure] [when-connection~=secure],
#identity-popup[connection=chrome] [when-connection~=chrome],
#identity-popup[connection=file] [when-connection~=file],
#identity-popup[connection=extension] [when-connection~=extension],
/* Show insecure login forms messages when needed. */
#identity-popup[loginforms=insecure] [when-loginforms=insecure],
/* Show weak cipher messages when needed. */
@ -249,6 +250,11 @@
background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);
}
#identity-popup[connection=extension] #identity-popup-securityView,
#identity-popup[connection=extension] #identity-popup-security-content {
background-image: url(chrome://browser/skin/controlcenter/extension.svg);
}
#identity-popup-security-descriptions > description {
margin-top: 6px;
color: Graytext;

Просмотреть файл

@ -13,6 +13,11 @@
list-style-image: url(chrome://browser/skin/identity-icon.svg#hover@iconVariant@);
}
#identity-box.extensionPage > #extension-icon@selectorSuffix@ {
list-style-image: url(chrome://browser/skin/controlcenter/extension.svg);
visibility: visible;
}
#identity-box.grantedPermissions > #identity-icon@selectorSuffix@ {
list-style-image: url(chrome://browser/skin/identity-icon.svg#notice@iconVariant@);
}

Просмотреть файл

@ -155,16 +155,15 @@
visibility: collapse;
}
/* CONNECTION ICON */
/* CONNECTION ICON, EXTENSION ICON */
#connection-icon {
#connection-icon, #extension-icon {
width: 16px;
height: 16px;
margin-inline-start: 2px;
visibility: collapse;
}
/* REMOTE CONTROL ICON */
#main-window[remotecontrol] #remote-control-icon {

Просмотреть файл

@ -28,6 +28,7 @@
* skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg)
* skin/classic/browser/controlcenter/connection.svg (../shared/controlcenter/connection.svg)
* skin/classic/browser/controlcenter/mcb-disabled.svg (../shared/controlcenter/mcb-disabled.svg)
* skin/classic/browser/controlcenter/extension.svg (../shared/controlcenter/extension.svg)
* skin/classic/browser/controlcenter/permissions.svg (../shared/controlcenter/permissions.svg)
* skin/classic/browser/controlcenter/tracking-protection.svg (../shared/controlcenter/tracking-protection.svg)
skin/classic/browser/controlcenter/warning-gray.svg (../shared/controlcenter/warning-gray.svg)

Просмотреть файл

@ -164,8 +164,9 @@ var onConnectionReady = Task.async(function* ([aType, aTraits]) {
*/
function buildAddonLink(addon, parent) {
let a = document.createElement("a");
a.onclick = function () {
openToolbox(addon, true, "jsdebugger", false);
a.onclick = async function () {
const isTabActor = addon.isWebExtension;
openToolbox(addon, true, "webconsole", isTabActor);
};
a.textContent = addon.name;

Просмотреть файл

@ -351,15 +351,15 @@ TabTarget.prototype = {
},
get isAddon() {
return !!(this._form && this._form.actor && (
this._form.actor.match(/conn\d+\.addon\d+/) ||
this._form.actor.match(/conn\d+\.webExtension\d+/)
));
return !!(this._form && this._form.actor &&
this._form.actor.match(/conn\d+\.addon\d+/)) || this.isWebExtension;
},
get isWebExtension() {
return !!(this._form && this._form.actor &&
this._form.actor.match(/conn\d+\.webExtension\d+/));
return !!(this._form && this._form.actor && (
this._form.actor.match(/conn\d+\.webExtension\d+/) ||
this._form.actor.match(/child\d+\/webExtension\d+/)
));
},
get isLocalTab() {
@ -370,12 +370,31 @@ TabTarget.prototype = {
return !this.window;
},
getExtensionPathName(url) {
// Return the url if the target is not a webextension.
if (!this.isWebExtension) {
throw new Error("Target is not a WebExtension");
}
try {
const parsedURL = new URL(url);
// Only moz-extension URL should be shortened into the URL pathname.
if (parsedURL.protocol !== "moz-extension:") {
return url;
}
return parsedURL.pathname;
} catch (e) {
// Return the url if unable to resolve the pathname.
return url;
}
},
/**
* Adds remote protocol capabilities to the target, so that it can be used
* for tools that support the Remote Debugging Protocol even for local
* connections.
*/
makeRemote: function () {
makeRemote: async function () {
if (this._remote) {
return this._remote.promise;
}
@ -398,6 +417,22 @@ TabTarget.prototype = {
this._client = new DebuggerClient(DebuggerServer.connectPipe());
// A local TabTarget will never perform chrome debugging.
this._chrome = false;
} else if (this._form.isWebExtension &&
this.client.mainRoot.traits.webExtensionAddonConnect) {
// The addonActor form is related to a WebExtensionParentActor instance,
// which isn't a tab actor on its own, it is an actor living in the parent process
// with access to the addon metadata, it can control the addon (e.g. reloading it)
// and listen to the AddonManager events related to the lifecycle of the addon
// (e.g. when the addon is disabled or uninstalled ).
// To retrieve the TabActor instance, we call its "connect" method,
// (which fetches the TabActor form from a WebExtensionChildActor instance).
let {form} = await this._client.request({
to: this._form.actor, type: "connect",
});
this._form = form;
this._url = form.url;
this._title = form.title;
}
this._setupRemoteListeners();
@ -498,8 +533,11 @@ TabTarget.prototype = {
event.nativeConsoleAPI = packet.nativeConsoleAPI;
event.isFrameSwitching = packet.isFrameSwitching;
if (!packet.isFrameSwitching) {
// Update the title and url unless this is a frame switch.
// Keep the title unmodified when a developer toolbox switches frame
// for a tab (Bug 1261687), but always update the title when the target
// is a WebExtension (where the addon name is always included in the title
// and the url is supposed to be updated every time the selected frame changes).
if (!packet.isFrameSwitching || this.isWebExtension) {
this._url = packet.url;
this._title = packet.title;
}

Просмотреть файл

@ -46,17 +46,11 @@ var connect = Task.async(function*() {
if (addonID) {
let { addons } = yield gClient.listAddons();
let addonActor = addons.filter(addon => addon.id === addonID).pop();
openToolbox({
form: addonActor,
chrome: true,
isTabActor: addonActor.isWebExtension ? true : false
});
let isTabActor = addonActor.isWebExtension;
openToolbox({form: addonActor, chrome: true, isTabActor});
} else {
let response = yield gClient.getProcess();
openToolbox({
form: response.form,
chrome: true
});
openToolbox({form: response.form, chrome: true});
}
});

Просмотреть файл

@ -1813,8 +1813,10 @@ Toolbox.prototype = {
_refreshHostTitle: function () {
let title;
if (this.target.name && this.target.name != this.target.url) {
const url = this.target.isWebExtension ?
this.target.getExtensionPathName(this.target.url) : this.target.url;
title = L10N.getFormatStr("toolbox.titleTemplate2", this.target.name,
this.target.url);
url);
} else {
title = L10N.getFormatStr("toolbox.titleTemplate1", this.target.url);
}
@ -1885,9 +1887,16 @@ Toolbox.prototype = {
// A frame is checked if it's the selected one.
let checked = frame.id == this.selectedFrameId;
let label = frame.url;
if (this.target.isWebExtension) {
// Show a shorter url for extensions page.
label = this.target.getExtensionPathName(frame.url);
}
// Create menu item.
menu.append(new MenuItem({
label: frame.url,
label,
type: "radio",
checked,
click: () => {

Просмотреть файл

@ -2446,6 +2446,8 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
this.rootDoc.defaultView) {
this.onFrameUnload({ window: this.rootDoc.defaultView });
}
// Update all DOM objects references to target the new document.
this.rootWin = window;
this.rootDoc = window.document;
this.rootNode = this.document();
this.queueMutation({
@ -2987,7 +2989,7 @@ function DocumentWalker(node, rootWin,
whatToShow = nodeFilterConstants.SHOW_ALL,
filter = standardTreeWalkerFilter,
skipTo = SKIP_TO_PARENT) {
if (!rootWin.location) {
if (Cu.isDeadWrapper(rootWin) || !rootWin.location) {
throw new Error("Got an invalid root window in DocumentWalker");
}

Просмотреть файл

@ -62,6 +62,7 @@ DevToolsModules(
'webbrowser.js',
'webconsole.js',
'webextension-inspected-window.js',
'webextension-parent.js',
'webextension.js',
'webgl.js',
'window.js',

Просмотреть файл

@ -190,7 +190,10 @@ RootActor.prototype = {
heapSnapshots: true,
// Whether or not the timeline actor can emit DOMContentLoaded and Load
// markers, currently in use by the network monitor. Fx45+
documentLoadingMarkers: true
documentLoadingMarkers: true,
// Whether or not the webextension addon actor have to be connected
// to retrieve the extension child process tab actors.
webExtensionAddonConnect: true,
},
/**

Просмотреть файл

@ -597,6 +597,12 @@ TabActor.prototype = {
this._updateChildDocShells();
},
_unwatchDocShell(docShell) {
if (this._progressListener) {
this._progressListener.unwatch(docShell);
}
},
onSwitchToFrame(request) {
let windowId = request.windowId;
let win;
@ -700,9 +706,43 @@ TabActor.prototype = {
},
_onDocShellDestroy(docShell) {
// Stop watching this docshell (the unwatch() method will check if we
// started watching it before).
this._unwatchDocShell(docShell);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
this._notifyDocShellDestroy(webProgress);
if (webProgress.DOMWindow == this._originalWindow) {
// If the original top level document we connected to is removed,
// we try to switch to any other top level document
let rootDocShells = this.docShells
.filter(d => {
return d != this.docShell &&
this._isRootDocShell(d);
});
if (rootDocShells.length > 0) {
let newRoot = rootDocShells[0];
this._originalWindow = newRoot.DOMWindow;
this._changeTopLevelDocument(this._originalWindow);
} else {
// If for some reason (typically during Firefox shutdown), the original
// document is destroyed, and there is no other top level docshell,
// we detach the tab actor to unregister all listeners and prevent any
// exception
this.exit();
}
return;
}
// If the currently targeted context is destroyed,
// and we aren't on the top-level document,
// we have to switch to the top-level one.
if (webProgress.DOMWindow == this.window &&
this.window != this._originalWindow) {
this._changeTopLevelDocument(this._originalWindow);
}
},
_isRootDocShell(docShell) {
@ -715,36 +755,34 @@ TabActor.prototype = {
return !docShell.parent;
},
_docShellToWindow(docShell) {
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
let window = webProgress.DOMWindow;
let id = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
let parentID = undefined;
// Ignore the parent of the original document on non-e10s firefox,
// as we get the xul window as parent and don't care about it.
if (window.parent && window != this._originalWindow) {
parentID = window.parent
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
}
return {
id,
parentID,
url: window.location.href,
title: window.document.title,
};
},
// Convert docShell list to windows objects list being sent to the client
_docShellsToWindows(docshells) {
return docshells.map(docShell => {
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
let window = webProgress.DOMWindow;
let id = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
let parentID = undefined;
// Ignore the parent of the original document on non-e10s firefox,
// as we get the xul window as parent and don't care about it.
if (window.parent && window != this._originalWindow) {
parentID = window.parent
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
}
// Collect the addonID from the document origin attributes.
let addonID = window.document.nodePrincipal.addonId;
return {
id,
parentID,
addonID,
url: window.location.href,
title: window.document.title,
};
});
return docshells.map(docShell => this._docShellToWindow(docShell));
},
_notifyDocShellsUpdate(docshells) {
@ -780,41 +818,6 @@ TabActor.prototype = {
destroy: true
}]
});
// Stop watching this docshell (the unwatch() method will check if we
// started watching it before).
webProgress.QueryInterface(Ci.nsIDocShell);
this._progressListener.unwatch(webProgress);
if (webProgress.DOMWindow == this._originalWindow) {
// If the original top level document we connected to is removed,
// we try to switch to any other top level document
let rootDocShells = this.docShells
.filter(d => {
return d != this.docShell &&
this._isRootDocShell(d);
});
if (rootDocShells.length > 0) {
let newRoot = rootDocShells[0];
this._originalWindow = newRoot.DOMWindow;
this._changeTopLevelDocument(this._originalWindow);
} else {
// If for some reason (typically during Firefox shutdown), the original
// document is destroyed, and there is no other top level docshell,
// we detach the tab actor to unregister all listeners and prevent any
// exception
this.exit();
}
return;
}
// If the currently targeted context is destroyed,
// and we aren't on the top-level document,
// we have to switch to the top-level one.
if (webProgress.DOMWindow == this.window &&
this.window != this._originalWindow) {
this._changeTopLevelDocument(this._originalWindow);
}
},
_notifyDocShellDestroyAll() {
@ -866,7 +869,7 @@ TabActor.prototype = {
// Check for docShell availability, as it can be already gone
// during Firefox shutdown.
if (this.docShell) {
this._progressListener.unwatch(this.docShell);
this._unwatchDocShell(this.docShell);
this._restoreDocumentSettings();
}
if (this._progressListener) {
@ -1165,6 +1168,12 @@ TabActor.prototype = {
this._setWindow(window);
DevToolsUtils.executeSoon(() => {
// No need to do anything more if the actor is not attached anymore
// e.g. the client has been closed and the actors destroyed in the meantime.
if (!this.attached) {
return;
}
// Then fake window-ready and navigate on the given document
this._windowReady(window, true);
DevToolsUtils.executeSoon(() => {

Просмотреть файл

@ -14,7 +14,7 @@ var DevToolsUtils = require("devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
loader.lazyRequireGetter(this, "WebExtensionActor", "devtools/server/actors/webextension", true);
loader.lazyRequireGetter(this, "WebExtensionParentActor", "devtools/server/actors/webextension-parent", true);
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true);
loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker-list", true);
loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
@ -835,7 +835,7 @@ BrowserAddonList.prototype.getList = function () {
let actor = this._actorByAddonId.get(addon.id);
if (!actor) {
if (addon.isWebExtension) {
actor = new WebExtensionActor(this._connection, addon);
actor = new WebExtensionParentActor(this._connection, addon);
} else {
actor = new BrowserAddonActor(this._connection, addon);
}
@ -843,8 +843,10 @@ BrowserAddonList.prototype.getList = function () {
this._actorByAddonId.set(addon.id, actor);
}
}
deferred.resolve([...this._actorByAddonId].map(([_, actor]) => actor));
});
return deferred.promise;
};

Просмотреть файл

@ -0,0 +1,210 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {DebuggerServer} = require("devtools/server/main");
const protocol = require("devtools/shared/protocol");
const {webExtensionSpec} = require("devtools/shared/specs/webextension-parent");
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
loader.lazyImporter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm");
/**
* Creates the actor that represents the addon in the parent process, which connects
* itself to a WebExtensionChildActor counterpart which is created in the
* extension process (or in the main process if the WebExtensions OOP mode is disabled).
*
* The WebExtensionParentActor subscribes itself as an AddonListener on the AddonManager
* and forwards this events to child actor (e.g. on addon reload or when the addon is
* uninstalled completely) and connects to the child extension process using a `browser`
* element provided by the extension internals (it is not related to any single extension,
* but it will be created automatically to the currently selected "WebExtensions OOP mode"
* and it persist across the extension reloads (it is destroyed once the actor exits).
* WebExtensionActor is a child of RootActor, it can be retrieved via
* RootActor.listAddons request.
*
* @param {DebuggerServerConnection} conn
* The connection to the client.
* @param {AddonWrapper} addon
* The target addon.
*/
const WebExtensionParentActor = protocol.ActorClassWithSpec(webExtensionSpec, {
initialize(conn, addon) {
this.conn = conn;
this.addon = addon;
this.id = addon.id;
this._childFormPromise = null;
AddonManager.addAddonListener(this);
},
destroy() {
AddonManager.removeAddonListener(this);
this.addon = null;
this._childFormPromise = null;
if (this._destroyProxyChildActor) {
this._destroyProxyChildActor();
delete this._destroyProxyChildActor;
}
},
setOptions() {
// NOTE: not used anymore for webextensions, still used in the legacy addons,
// addon manager is currently going to call it automatically on every addon.
},
reload() {
return this.addon.reload().then(() => {
return {};
});
},
form() {
return {
actor: this.actorID,
id: this.id,
name: this.addon.name,
iconURL: this.addon.iconURL,
debuggable: this.addon.isDebuggable,
temporarilyInstalled: this.addon.temporarilyInstalled,
isWebExtension: true,
};
},
connect() {
if (this._childFormPormise) {
return this._childFormPromise;
}
let proxy = new ProxyChildActor(this.conn, this);
this._childFormPromise = proxy.connect().then(form => {
// Merge into the child actor form, some addon metadata
// (e.g. the addon name shown in the addon debugger window title).
return Object.assign(form, {
id: this.addon.id,
name: this.addon.name,
iconURL: this.addon.iconURL,
// Set the isOOP attribute on the connected child actor form.
isOOP: proxy.isOOP,
});
});
this._destroyProxyChildActor = () => proxy.destroy();
return this._childFormPromise;
},
// ProxyChildActor callbacks.
onProxyChildActorDestroy() {
// Invalidate the cached child actor and form Promise
// if the child actor exits.
this._childFormPromise = null;
delete this._destroyProxyChildActor;
},
// AddonManagerListener callbacks.
onInstalled(addon) {
if (addon.id != this.id) {
return;
}
// Update the AddonManager's addon object on reload/update.
this.addon = addon;
},
onUninstalled(addon) {
if (addon != this.addon) {
return;
}
this.destroy();
},
});
exports.WebExtensionParentActor = WebExtensionParentActor;
function ProxyChildActor(connection, parentActor) {
this._conn = connection;
this._parentActor = parentActor;
this.addonId = parentActor.id;
this._onChildExit = this._onChildExit.bind(this);
this._form = null;
this._browser = null;
this._childActorID = null;
}
ProxyChildActor.prototype = {
/**
* Connect the webextension child actor.
*/
async connect() {
if (this._browser) {
throw new Error("This actor is already connected to the extension process");
}
// Called when the debug browser element has been destroyed
// (no actor is using it anymore to connect the child extension process).
const onDestroy = this.destroy.bind(this);
this._browser = await ExtensionParent.DebugUtils.getExtensionProcessBrowser(this);
this._form = await DebuggerServer.connectToChild(this._conn, this._browser, onDestroy,
{addonId: this.addonId});
this._childActorID = this._form.actor;
// Exit the proxy child actor if the child actor has been destroyed.
this._mm.addMessageListener("debug:webext_child_exit", this._onChildExit);
return this._form;
},
get isOOP() {
return this._browser ? this._browser.isRemoteBrowser : undefined;
},
get _mm() {
return this._browser && (
this._browser.messageManager ||
this._browser.frameLoader.messageManager);
},
destroy() {
if (this._mm) {
this._mm.removeMessageListener("debug:webext_child_exit", this._onChildExit);
this._mm.sendAsyncMessage("debug:webext_parent_exit", {
actor: this._childActorID,
});
ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(this);
}
if (this._parentActor) {
this._parentActor.onProxyChildActorDestroy();
}
this._parentActor = null;
this._browser = null;
this._childActorID = null;
this._form = null;
},
/**
* Handle the child actor exit.
*/
_onChildExit(msg) {
if (msg.json.actor !== this._childActorID) {
return;
}
this.destroy();
},
};

Просмотреть файл

@ -4,63 +4,77 @@
"use strict";
const { Ci, Cu } = require("chrome");
const { Ci, Cu, Cc } = require("chrome");
const Services = require("Services");
const { ChromeActor } = require("./chrome");
const makeDebugger = require("./utils/make-debugger");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { assert } = DevToolsUtils;
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
loader.lazyImporter(this, "XPIProvider", "resource://gre/modules/addons/XPIProvider.jsm");
const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet.";
/**
* Creates a TabActor for debugging all the contexts associated to a target WebExtensions
* add-on.
* add-on running in a child extension process.
* Most of the implementation is inherited from ChromeActor (which inherits most of its
* implementation from TabActor).
* WebExtensionActor is a child of RootActor, it can be retrieved via
* RootActor.listAddons request.
* WebExtensionActor exposes all tab actors via its form() request, like TabActor.
* WebExtensionChildActor is created by a WebExtensionParentActor counterpart, when its
* parent actor's `connect` method has been called (on the listAddons RDP package),
* it runs in the same process that the extension is running into (which can be the main
* process if the extension is running in non-oop mode, or the child extension process
* if the extension is running in oop-mode).
*
* A WebExtensionChildActor contains all tab actors, like a regular ChromeActor
* or TabActor.
*
* History lecture:
* The add-on actors used to not inherit TabActor because of the different way the
* - The add-on actors used to not inherit TabActor because of the different way the
* add-on APIs where exposed to the add-on itself, and for this reason the Addon Debugger
* has only a sub-set of the feature available in the Tab or in the Browser Toolbox.
* In a WebExtensions add-on all the provided contexts (background and popup pages etc.),
* - In a WebExtensions add-on all the provided contexts (background, popups etc.),
* besides the Content Scripts which run in the content process, hooked to an existent
* tab, by creating a new WebExtensionActor which inherits from ChromeActor, we can
* provide a full features Addon Toolbox (which is basically like a BrowserToolbox which
* filters the visible sources and frames to the one that are related to the target
* add-on).
* - When the WebExtensions OOP mode has been introduced, this actor has been refactored
* and moved from the main process to the new child extension process.
*
* @param conn DebuggerServerConnection
* @param {DebuggerServerConnection} conn
* The connection to the client.
* @param addon AddonWrapper
* The target addon.
* @param {nsIMessageSender} chromeGlobal.
* The chromeGlobal where this actor has been injected by the
* DebuggerServer.connectToChild method.
* @param {string} prefix
* the custom RDP prefix to use.
* @param {string} addonId
* the addonId of the target WebExtension.
*/
function WebExtensionActor(conn, addon) {
function WebExtensionChildActor(conn, chromeGlobal, prefix, addonId) {
ChromeActor.call(this, conn);
this.id = addon.id;
this.addon = addon;
this._chromeGlobal = chromeGlobal;
this._prefix = prefix;
this.id = addonId;
// Bind the _allowSource helper to this, it is used in the
// TabActor to lazily create the TabSources instance.
this._allowSource = this._allowSource.bind(this);
this._onParentExit = this._onParentExit.bind(this);
this._chromeGlobal.addMessageListener("debug:webext_parent_exit", this._onParentExit);
// Set the consoleAPIListener filtering options
// (retrieved and used in the related webconsole child actor).
this.consoleAPIListenerOptions = {
addonId: addon.id,
addonId: this.id,
};
this.aps = Cc["@mozilla.org/addons/policy-service;1"]
.getService(Ci.nsIAddonPolicyService);
// This creates a Debugger instance for debugging all the add-on globals.
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: dbg => {
@ -69,135 +83,50 @@ function WebExtensionActor(conn, addon) {
shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee.bind(this),
});
// Discover the preferred debug global for the target addon
this.preferredTargetWindow = null;
this._findAddonPreferredTargetWindow();
// Try to discovery an existent extension page to attach (which will provide the initial
// URL shown in the window tittle when the addon debugger is opened).
let extensionWindow = this._searchForExtensionWindow();
AddonManager.addAddonListener(this);
if (extensionWindow) {
this._setWindow(extensionWindow);
}
}
exports.WebExtensionActor = WebExtensionActor;
exports.WebExtensionChildActor = WebExtensionChildActor;
WebExtensionActor.prototype = Object.create(ChromeActor.prototype);
WebExtensionChildActor.prototype = Object.create(ChromeActor.prototype);
WebExtensionActor.prototype.actorPrefix = "webExtension";
WebExtensionActor.prototype.constructor = WebExtensionActor;
WebExtensionChildActor.prototype.actorPrefix = "webExtension";
WebExtensionChildActor.prototype.constructor = WebExtensionChildActor;
// NOTE: This is needed to catch in the webextension webconsole all the
// errors raised by the WebExtension internals that are not currently
// associated with any window.
WebExtensionActor.prototype.isRootActor = true;
WebExtensionActor.prototype.form = function () {
assert(this.actorID, "addon should have an actorID.");
let baseForm = ChromeActor.prototype.form.call(this);
return Object.assign(baseForm, {
actor: this.actorID,
id: this.id,
name: this.addon.name,
url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
iconURL: this.addon.iconURL,
debuggable: this.addon.isDebuggable,
temporarilyInstalled: this.addon.temporarilyInstalled,
isWebExtension: this.addon.isWebExtension,
});
};
WebExtensionActor.prototype._attach = function () {
// NOTE: we need to be sure that `this.window` can return a
// window before calling the ChromeActor.onAttach, or the TabActor
// will not be subscribed to the child doc shell updates.
// If a preferredTargetWindow exists, set it as the target for this actor
// when the client request to attach this actor.
if (this.preferredTargetWindow) {
this._setWindow(this.preferredTargetWindow);
} else {
this._createFallbackWindow();
}
// Call ChromeActor's _attach to listen for any new/destroyed chrome docshell
ChromeActor.prototype._attach.apply(this);
};
WebExtensionActor.prototype._detach = function () {
this._destroyFallbackWindow();
// Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners.
ChromeActor.prototype._detach.apply(this);
};
WebExtensionChildActor.prototype.isRootActor = true;
/**
* Called when the actor is removed from the connection.
*/
WebExtensionActor.prototype.exit = function () {
AddonManager.removeAddonListener(this);
WebExtensionChildActor.prototype.exit = function () {
if (this._chromeGlobal) {
let chromeGlobal = this._chromeGlobal;
this._chromeGlobal = null;
chromeGlobal.removeMessageListener("debug:webext_parent_exit", this._onParentExit);
chromeGlobal.sendAsyncMessage("debug:webext_child_exit", {
actor: this.actorID
});
}
this.preferredTargetWindow = null;
this.addon = null;
this.id = null;
return ChromeActor.prototype.exit.apply(this);
};
// Addon Specific Remote Debugging requestTypes and methods.
// Private helpers.
/**
* Reloads the addon.
*/
WebExtensionActor.prototype.onReload = function () {
return this.addon.reload()
.then(() => {
// send an empty response
return {};
});
};
/**
* Set the preferred global for the add-on (called from the AddonManager).
*/
WebExtensionActor.prototype.setOptions = function (addonOptions) {
if ("global" in addonOptions) {
// Set the proposed debug global as the preferred target window
// (the actor will eventually set it as the target once it is attached)
this.preferredTargetWindow = addonOptions.global;
}
};
// AddonManagerListener callbacks.
WebExtensionActor.prototype.onInstalled = function (addon) {
if (addon.id != this.id) {
return;
}
// Update the AddonManager's addon object on reload/update.
this.addon = addon;
};
WebExtensionActor.prototype.onUninstalled = function (addon) {
if (addon != this.addon) {
return;
}
this.exit();
};
WebExtensionActor.prototype.onPropertyChanged = function (addon, changedPropNames) {
if (addon != this.addon) {
return;
}
// Refresh the preferred debug global on disabled/reloaded/upgraded addon.
if (changedPropNames.includes("debugGlobal")) {
this._findAddonPreferredTargetWindow();
}
};
// Private helpers
WebExtensionActor.prototype._createFallbackWindow = function () {
WebExtensionChildActor.prototype._createFallbackWindow = function () {
if (this.fallbackWindow) {
// Skip if there is already an existent fallback window.
return;
@ -207,26 +136,16 @@ WebExtensionActor.prototype._createFallbackWindow = function () {
// not defined for the target add-on or not yet when the actor instance has been
// created).
this.fallbackWebNav = Services.appShell.createWindowlessBrowser(true);
this.fallbackWebNav.loadURI(
`data:text/html;charset=utf-8,${FALLBACK_DOC_MESSAGE}`,
0, null, null, null
);
this.fallbackDocShell = this.fallbackWebNav
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
// Save the reference to the fallback DOMWindow.
this.fallbackWindow = this.fallbackWebNav.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
Object.defineProperty(this, "docShell", {
value: this.fallbackDocShell,
configurable: true
});
// Save the reference to the fallback DOMWindow
this.fallbackWindow = this.fallbackDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
// Insert the fallback doc message.
this.fallbackWindow.document.body.innerText = FALLBACK_DOC_MESSAGE;
};
WebExtensionActor.prototype._destroyFallbackWindow = function () {
WebExtensionChildActor.prototype._destroyFallbackWindow = function () {
if (this.fallbackWebNav) {
// Explicitly close the fallback windowless browser to prevent it to leak
// (and to prevent it to freeze devtools xpcshell tests).
@ -238,65 +157,173 @@ WebExtensionActor.prototype._destroyFallbackWindow = function () {
}
};
/**
* Discover the preferred debug global and switch to it if the addon has been attached.
*/
WebExtensionActor.prototype._findAddonPreferredTargetWindow = function () {
return new Promise(resolve => {
let activeAddon = XPIProvider.activeAddons.get(this.id);
// Discovery an extension page to use as a default target window.
// NOTE: This currently fail to discovery an extension page running in a
// windowless browser when running in non-oop mode, and the background page
// is set later using _onNewExtensionWindow.
WebExtensionChildActor.prototype._searchForExtensionWindow = function () {
let e = Services.ww.getWindowEnumerator(null);
while (e.hasMoreElements()) {
let window = e.getNext();
if (!activeAddon) {
// The addon is not active, the background page is going to be destroyed,
// navigate to the fallback window (if it already exists).
resolve(null);
} else {
AddonManager.getAddonByInstanceID(activeAddon.instanceID)
.then(privateWrapper => {
let targetWindow = privateWrapper.getDebugGlobal();
// Do not use the preferred global if it is not a DOMWindow as expected.
if (!(targetWindow instanceof Ci.nsIDOMWindow)) {
targetWindow = null;
}
resolve(targetWindow);
});
if (window.document.nodePrincipal.addonId == this.id) {
return window;
}
}).then(preferredTargetWindow => {
this.preferredTargetWindow = preferredTargetWindow;
}
if (!preferredTargetWindow) {
// Create a fallback window if no preferred target window has been found.
return undefined;
};
// Customized ChromeActor/TabActor hooks.
WebExtensionChildActor.prototype._onDocShellDestroy = function (docShell) {
// Stop watching this docshell (the unwatch() method will check if we
// started watching it before).
this._unwatchDocShell(docShell);
// Let the _onDocShellDestroy notify that the docShell has been destroyed.
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
this._notifyDocShellDestroy(webProgress);
// If the destroyed docShell was the current docShell and the actor is
// currently attached, switch to the fallback window
if (this.attached && docShell == this.docShell) {
// Creates a fallback window if it doesn't exist yet.
this._createFallbackWindow();
this._changeTopLevelDocument(this.fallbackWindow);
}
};
WebExtensionChildActor.prototype._onNewExtensionWindow = function (window) {
if (!this.window || this.window === this.fallbackWindow) {
this._changeTopLevelDocument(window);
}
};
WebExtensionChildActor.prototype._attach = function () {
// NOTE: we need to be sure that `this.window` can return a
// window before calling the ChromeActor.onAttach, or the TabActor
// will not be subscribed to the child doc shell updates.
if (!this.window || this.window.document.nodePrincipal.addonId !== this.id) {
// Discovery an existent extension page to attach.
let extensionWindow = this._searchForExtensionWindow();
if (!extensionWindow) {
this._createFallbackWindow();
} else if (this.attached) {
// Change the top level document if the actor is already attached.
this._changeTopLevelDocument(preferredTargetWindow);
this._setWindow(this.fallbackWindow);
} else {
this._setWindow(extensionWindow);
}
}
// Call ChromeActor's _attach to listen for any new/destroyed chrome docshell
ChromeActor.prototype._attach.apply(this);
};
WebExtensionChildActor.prototype._detach = function () {
// Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners.
ChromeActor.prototype._detach.apply(this);
// Stop watching for new extension windows.
this._destroyFallbackWindow();
};
/**
* Return the json details related to a docShell.
*/
WebExtensionChildActor.prototype._docShellToWindow = function (docShell) {
const baseWindowDetails = ChromeActor.prototype._docShellToWindow.call(this, docShell);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
let window = webProgress.DOMWindow;
// Collect the addonID from the document origin attributes and its sameType top level
// frame.
let addonID = window.document.nodePrincipal.addonId;
let sameTypeRootAddonID = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
.sameTypeRootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.document.nodePrincipal.addonId;
return Object.assign(baseWindowDetails, {
addonID,
sameTypeRootAddonID,
});
};
/**
* Return an array of the json details related to an array/iterator of docShells.
*/
WebExtensionActor.prototype._docShellsToWindows = function (docshells) {
WebExtensionChildActor.prototype._docShellsToWindows = function (docshells) {
return ChromeActor.prototype._docShellsToWindows.call(this, docshells)
.filter(windowDetails => {
// filter the docShells based on the addon id
return windowDetails.addonID == this.id;
// Filter the docShells based on the addon id of the window or
// its sameType top level frame.
return windowDetails.addonID === this.id ||
windowDetails.sameTypeRootAddonID === this.id;
});
};
WebExtensionChildActor.prototype.isExtensionWindow = function (window) {
return window.document.nodePrincipal.addonId == this.id;
};
WebExtensionChildActor.prototype.isExtensionWindowDescendent = function (window) {
// Check if the source is coming from a descendant docShell of an extension window.
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
let rootWin = docShell.sameTypeRootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
return this.isExtensionWindow(rootWin);
};
/**
* Return true if the given source is associated with this addon and should be
* added to the visible sources (retrieved and used by the webbrowser actor module).
*/
WebExtensionActor.prototype._allowSource = function (source) {
WebExtensionChildActor.prototype._allowSource = function (source) {
// Use the source.element to detect the allowed source, if any.
if (source.element) {
let domEl = unwrapDebuggerObjectGlobal(source.element);
return (this.isExtensionWindow(domEl.ownerGlobal) ||
this.isExtensionWindowDescendent(domEl.ownerGlobal));
}
// Fallback to check the uri if there is no source.element associated to the source.
// Retrieve the first component of source.url in the form "url1 -> url2 -> ...".
let url = source.url.split(" -> ").pop();
// Filter out the code introduced by evaluating code in the webconsole.
if (url === "debugger eval code") {
return false;
}
let uri;
// Try to decode the url.
try {
let uri = Services.io.newURI(source.url);
let addonID = mapURIToAddonID(uri);
uri = Services.io.newURI(url);
} catch (err) {
Cu.reportError(`Unexpected invalid url: ${url}`);
return false;
}
// Filter out resource and chrome sources (which are related to the loaded internals).
if (["resource", "chrome", "file"].includes(uri.scheme)) {
return false;
}
try {
let addonID = this.aps.extensionURIToAddonId(uri);
return addonID == this.id;
} catch (e) {
} catch (err) {
// extensionURIToAddonId raises an exception on non-extension URLs.
return false;
}
};
@ -305,11 +332,22 @@ WebExtensionActor.prototype._allowSource = function (source) {
* Return true if the given global is associated with this addon and should be
* added as a debuggee, false otherwise.
*/
WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) {
WebExtensionChildActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) {
const global = unwrapDebuggerObjectGlobal(newGlobal);
if (global instanceof Ci.nsIDOMWindow) {
return global.document.nodePrincipal.addonId == this.id;
// Filter out any global which contains a XUL document.
if (global.document instanceof Ci.nsIDOMXULDocument) {
return false;
}
// Change top level document as a simulated frame switching.
if (global.document.ownerGlobal && this.isExtensionWindow(global)) {
this._onNewExtensionWindow(global.document.ownerGlobal);
}
return global.document.ownerGlobal &&
this.isExtensionWindowDescendent(global.document.ownerGlobal);
}
try {
@ -325,9 +363,12 @@ WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal)
return false;
};
/**
* Override WebExtensionActor requestTypes:
* - redefined `reload`, which should reload the target addon
* (instead of the entire browser as the regular ChromeActor does).
*/
WebExtensionActor.prototype.requestTypes.reload = WebExtensionActor.prototype.onReload;
// Handlers for the messages received from the parent actor.
WebExtensionChildActor.prototype._onParentExit = function (msg) {
if (msg.json.actor !== this.actorID) {
return;
}
this.exit();
};

Просмотреть файл

@ -17,7 +17,6 @@ try {
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { dumpn } = DevToolsUtils;
const { DebuggerServer, ActorPool } = require("devtools/server/main");
const { ContentActor } = require("devtools/server/actors/childtab");
if (!DebuggerServer.initialized) {
DebuggerServer.init();
@ -34,12 +33,22 @@ try {
let mm = msg.target;
let prefix = msg.data.prefix;
let addonId = msg.data.addonId;
let conn = DebuggerServer.connectToParent(prefix, mm);
conn.parentMessageManager = mm;
connections.set(prefix, conn);
let actor = new ContentActor(conn, chromeGlobal, prefix);
let actor;
if (addonId) {
const { WebExtensionChildActor } = require("devtools/server/actors/webextension");
actor = new WebExtensionChildActor(conn, chromeGlobal, prefix, addonId);
} else {
const { ContentActor } = require("devtools/server/actors/childtab");
actor = new ContentActor(conn, chromeGlobal, prefix);
}
let actorPool = new ActorPool(conn);
actorPool.addActor(actor);
conn.addActorPool(actorPool);

Просмотреть файл

@ -1009,7 +1009,7 @@ var DebuggerServer = {
* A promise object that is resolved once the connection is
* established.
*/
connectToChild(connection, frame, onDestroy) {
connectToChild(connection, frame, onDestroy, {addonId} = {}) {
let deferred = SyncPromise.defer();
// Get messageManager from XUL browser (which might be a specialized tunnel for RDM)
@ -1122,6 +1122,9 @@ var DebuggerServer = {
};
let destroy = DevToolsUtils.makeInfallible(function () {
events.off(connection, "closed", destroy);
Services.obs.removeObserver(onMessageManagerClose, "message-manager-close");
// provides hook to actor modules that need to exchange messages
// between e10s parent and child processes
parentModules.forEach(mod => {
@ -1168,8 +1171,6 @@ var DebuggerServer = {
// Cleanup all listeners
untrackMessageManager();
Services.obs.removeObserver(onMessageManagerClose, "message-manager-close");
events.off(connection, "closed", destroy);
});
// Listen for various messages and frame events
@ -1188,7 +1189,7 @@ var DebuggerServer = {
// when user unplug the device or we lose the connection somehow.
events.on(connection, "closed", destroy);
mm.sendAsyncMessage("debug:connect", { prefix });
mm.sendAsyncMessage("debug:connect", { prefix, addonId });
return deferred.promise;
},

Просмотреть файл

@ -24,7 +24,7 @@ support-files =
setup-in-child.js
setup-in-parent.js
webconsole-helpers.js
webextension-helpers.js
[test_animation_actor-lifetime.html]
[test_connection-manager.html]
[test_connectToChild.html]
@ -99,5 +99,7 @@ support-files =
[test_styles-svg.html]
[test_unsafeDereference.html]
[test_webconsole-node-grip.html]
[test_webextension-addon-debugging-connect.html]
[test_webextension-addon-debugging-reload.html]
[test_websocket-server.html]
skip-if = false

Просмотреть файл

@ -0,0 +1,102 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1302702 - Test connect to a webextension addon
-->
<head>
<meta charset="utf-8">
<title>Mozilla Bug</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script src="webextension-helpers.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script type="application/javascript">
"use strict";
async function test_connect_addon(oopMode) {
// Install and start a test webextension.
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
background: function () {
browser.test.log("background script executed");
browser.test.sendMessage("background page ready");
},
});
await extension.startup();
await extension.awaitMessage("background page ready");
// Connect a DebuggerClient.
const transport = DebuggerServer.connectPipe();
const client = new DebuggerClient(transport);
await client.connect();
// List addons and assertions on the expected addon actor.
const {addons} = await client.mainRoot.listAddons();
const addonActor = addons.filter(actor => actor.id === extension.id).pop();
ok(addonActor, "The expected webextension addon actor has been found");
// Connect to the target addon actor and wait for the updated list of frames.
const waitFramesUpdated = waitForFramesUpdated({client});
const addonTarget = await TargetFactory.forRemoteTab({
form: addonActor,
client,
chrome: true,
isTabActor: true,
});
is(addonTarget.form.isOOP, oopMode,
"Got the expected oop mode in the webextension actor form");
const frames = await waitFramesUpdated;
const backgroundPageFrame = frames.filter((frame) => {
return frame.url && frame.url.endsWith("/_generated_background_page.html");
}).pop();
is(backgroundPageFrame.addonID, extension.id, "Got an extension frame");
ok(addonTarget.activeTab, "The addon target has an activeTab");
// When running in oop mode we can explicitly attach the thread without locking
// the main process.
if (oopMode) {
const [, threadFront] = await addonTarget.activeTab
.attachThread(addonTarget.form.threadActor);
ok(threadFront, "Got a threadFront for the target addon");
is(threadFront.paused, true, "The addon threadActor is paused");
await threadFront.resume();
is(threadFront.paused, false, "The addon threadActor has been resumed");
await threadFront.detach();
}
const waitTransportClosed = new Promise(resolve => {
client._transport.once("close", resolve);
});
await addonTarget.destroy();
await client.close();
// Check that if we close the debugging client without uninstalling the addon,
// the webextension debugging actor should release the debug browser.
await waitTransportClosed;
is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 0,
"The debug browser has been released when the RDP connection has been closed");
await extension.unload();
}
add_task(async function test_webextension_addon_debugging_connect_inprocess() {
await setWebExtensionOOPMode(false);
await test_connect_addon(false);
});
add_task(async function test_webextension_addon_debugging_connect_oop() {
await setWebExtensionOOPMode(true);
await test_connect_addon(true);
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,133 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1302702 - Test connect to a webextension addon
-->
<head>
<meta charset="utf-8">
<title>Mozilla Bug</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<script src="webextension-helpers.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script type="application/javascript">
"use strict";
// NOTE: This test installs the webextension addon using the addon manager, so that
// it can be reloaded using the same actor RDP method used by the developer tools.
async function test_reload_addon() {
const addonID = "test-webext-debugging-reload@test.mozilla.com";
const addonFile = generateWebExtensionXPI({
manifest: {
applications: {
gecko: {id: addonID},
},
background: { scripts: ["background.js"] },
},
files: {
"background.js": function () {
console.log("background page loaded");
},
},
});
const {addon} = await promiseInstallFile(addonFile);
await promiseWebExtensionStartup();
let addonTarget = await attachAddon(addonID);
ok(addonTarget, "Got an addon target");
const matchBackgroundPageFrame = (data) => {
if (data.frames) {
let frameFound = data.frames.filter((frame) => {
return frame.url && frame.url.endsWith("_generated_background_page.html");
}).pop();
return !!frameFound;
}
return false;
};
const matchFrameSelected = (data) => {
return "selected" in data;
};
// Test the target addon actor reload behavior.
let waitFramesUpdated = waitForFramesUpdated(addonTarget, matchBackgroundPageFrame);
let collectFrameSelected = collectFrameUpdates(addonTarget, matchFrameSelected);
is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1,
"The expected number of debug browser has been created by the addon actor");
info("Reloading the target addon");
reloadAddon(addonTarget, addonID);
await promiseWebExtensionStartup();
is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1,
"The number of debug browser has not been changed after an addon reload");
let frames = await waitFramesUpdated;
const selectedFrame = collectFrameSelected().pop();
is(selectedFrame.selected, frames[0].id, "The new background page has been selected");
is(addonTarget.url, frames[0].url,
"The addon target has the url of the selected frame");
// Test the target addon actor once reloaded twice.
waitFramesUpdated = waitForFramesUpdated(addonTarget, matchBackgroundPageFrame);
collectFrameSelected = collectFrameUpdates(addonTarget, matchFrameSelected);
info("Reloading the target addon again");
reloadAddon(addonTarget, addonID);
await promiseWebExtensionStartup();
frames = await waitFramesUpdated;
const newSelectedFrame = collectFrameSelected().pop();
ok(newSelectedFrame !== selectedFrame,
"The new selected frame is different from the previous on");
is(newSelectedFrame.selected, frames[0].id,
"The new background page has been selected on the second reload");
is(addonTarget.url, frames[0].url,
"The addon target has the url of the selected frame");
// Expect the target to be automatically closed when the addon
// is finally uninstalled.
let {client} = addonTarget;
let waitDebuggingClientClosed = new Promise(resolve => {
addonTarget.once("close", resolve);
});
let waitShutdown = promiseWebExtensionShutdown();
addon.uninstall();
await waitShutdown;
info("Waiting the addon target to be closed on addon uninstall");
await waitDebuggingClientClosed;
// Debugging client has to be closed explicitly when
// the target has been created as remote.
await client.close();
}
add_task(async function test_webextension_addon_debugging_reload_inprocess() {
await setWebExtensionOOPMode(false);
await test_reload_addon(false);
});
add_task(async function test_webextension_addon_debugging_reload_oop() {
await setWebExtensionOOPMode(true);
await test_reload_addon(true);
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,212 @@
/* exported attachAddon, setWebExtensionOOPMode, waitForFramesUpdated, reloadAddon,
collectFrameUpdates, generateWebExtensionXPI, promiseInstallFile,
promiseAddonByID, promiseWebExtensionStartup, promiseWebExtensionShutdown
*/
"use strict";
const Cu = Components.utils;
const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {DebuggerClient} = require("devtools/shared/client/main");
const {DebuggerServer} = require("devtools/server/main");
const {TargetFactory} = require("devtools/client/framework/target");
const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
const {Extension, Management} = require("resource://gre/modules/Extension.jsm");
const {flushJarCache} = require("resource://gre/modules/ExtensionUtils.jsm");
const {Services} = require("resource://gre/modules/Services.jsm");
loader.lazyImporter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm");
loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
// Initialize a minimal DebuggerServer and connect to the webextension addon actor.
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
SimpleTest.registerCleanupFunction(function () {
DebuggerServer.destroy();
});
}
SimpleTest.registerCleanupFunction(function () {
const {hiddenXULWindow} = ExtensionParent.DebugUtils;
const debugBrowserMapSize = ExtensionParent.DebugUtils.debugBrowserPromises.size;
if (debugBrowserMapSize > 0) {
is(debugBrowserMapSize, 0,
"ExtensionParent DebugUtils debug browsers have not been released");
}
if (hiddenXULWindow) {
ok(false, "ExtensionParent DebugUtils hiddenXULWindow has not been destroyed");
}
});
// Test helpers related to the webextensions debugging RDP actors.
function setWebExtensionOOPMode(oopMode) {
return SpecialPowers.pushPrefEnv({
"set": [
["extensions.webextensions.remote", oopMode],
]
});
}
function waitForFramesUpdated({client}, matchFn) {
return new Promise(resolve => {
const listener = (evt, data) => {
if (typeof matchFn === "function" && !matchFn(data)) {
return;
} else if (!data.frames) {
return;
}
client.removeListener("frameUpdate", listener);
resolve(data.frames);
};
client.addListener("frameUpdate", listener);
});
}
function collectFrameUpdates({client}, matchFn) {
let collected = [];
const listener = (evt, data) => {
if (matchFn(data)) {
collected.push(data);
}
};
client.addListener("frameUpdate", listener);
let unsubscribe = () => {
unsubscribe = null;
client.removeListener("frameUpdate", listener);
return collected;
};
SimpleTest.registerCleanupFunction(function () {
if (unsubscribe) {
unsubscribe();
}
});
return unsubscribe;
}
async function attachAddon(addonId) {
const transport = DebuggerServer.connectPipe();
const client = new DebuggerClient(transport);
await client.connect();
const {addons} = await client.mainRoot.listAddons();
const addonActor = addons.filter(actor => actor.id === addonId).pop();
if (!addonActor) {
client.close();
throw new Error(`No WebExtension Actor found for ${addonId}`);
}
const addonTarget = await TargetFactory.forRemoteTab({
form: addonActor,
client,
chrome: true,
isTabActor: true,
});
return addonTarget;
}
async function reloadAddon({client}, addonId) {
const {addons} = await client.mainRoot.listAddons();
const addonActor = addons.filter(actor => actor.id === addonId).pop();
if (!addonActor) {
client.close();
throw new Error(`No WebExtension Actor found for ${addonId}`);
}
await client.request({
to: addonActor.actor,
type: "reload",
});
}
// Test helpers related to the AddonManager.
function generateWebExtensionXPI(extDetails) {
const addonFile = Extension.generateXPI(extDetails);
flushJarCache(addonFile.path);
Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache",
{path: addonFile.path});
// Remove the file on cleanup if needed.
SimpleTest.registerCleanupFunction(() => {
flushJarCache(addonFile.path);
Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache",
{path: addonFile.path});
if (addonFile.exists()) {
OS.File.remove(addonFile.path);
}
});
return addonFile;
}
function promiseCompleteInstall(install) {
let listener;
return new Promise((resolve, reject) => {
listener = {
onDownloadFailed: reject,
onDownloadCancelled: reject,
onInstallFailed: reject,
onInstallCancelled: reject,
onInstallEnded: resolve,
onInstallPostponed: reject,
};
install.addListener(listener);
install.install();
}).then(() => {
install.removeListener(listener);
return install;
});
}
function promiseInstallFile(file) {
return AddonManager.getInstallForFile(file).then(install => {
if (!install) {
throw new Error(`No AddonInstall created for ${file.path}`);
}
if (install.state != AddonManager.STATE_DOWNLOADED) {
throw new Error(`Expected file to be downloaded for install of ${file.path}`);
}
return promiseCompleteInstall(install);
});
}
function promiseWebExtensionStartup() {
return new Promise(resolve => {
let listener = (evt, extension) => {
Management.off("ready", listener);
resolve(extension);
};
Management.on("ready", listener);
});
}
function promiseWebExtensionShutdown() {
return new Promise(resolve => {
let listener = (event, extension) => {
Management.off("shutdown", listener);
resolve(extension);
};
Management.on("shutdown", listener);
});
}

Просмотреть файл

@ -43,6 +43,7 @@ DevToolsModules(
'timeline.js',
'webaudio.js',
'webextension-inspected-window.js',
'webextension-parent.js',
'webgl.js',
'worker.js'
)

Просмотреть файл

@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {RetVal, generateActorSpec} = require("devtools/shared/protocol");
const webExtensionSpec = generateActorSpec({
typeName: "webExtensionAddon",
methods: {
reload: {
request: { },
response: { addon: RetVal("json") },
},
connect: {
request: { },
response: { form: RetVal("json") },
},
},
});
exports.webExtensionSpec = webExtensionSpec;

Просмотреть файл

@ -161,6 +161,32 @@ var commandsCheckDataChannel = [
var channels = test.pcRemote.dataChannels;
var message = "I am the walrus; Goo goo g'joob";
return test.send(message).then(result => {
is(channels.indexOf(result.channel), channels.length - 1, "Last channel used");
is(result.data, message, "Received message has the correct content.");
});
},
function CREATE_NEGOTIATED_MAXPACKET_LIFE_DATA_CHANNEL(test) {
var options = {
ordered: false,
maxPacketLifeTime: 10
};
return test.createDataChannel(options).then(result => {
var sourceChannel3 = result.local;
var targetChannel3 = result.remote;
is(sourceChannel3.readyState, "open", sourceChannel3 + " is in state: 'open'");
is(targetChannel3.readyState, "open", targetChannel3 + " is in state: 'open'");
is(targetChannel3.binaryType, "blob", targetChannel3 + " is of binary type 'blob'");
});
},
function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL3(test) {
var channels = test.pcRemote.dataChannels;
var message = "Nice to see you working maxPacketLifeTime";
return test.send(message).then(result => {
is(channels.indexOf(result.channel), channels.length - 1, "Last channel used");
is(result.data, message, "Received message has the correct content.");

Просмотреть файл

@ -837,7 +837,21 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
mouseInput.mLocalOrigin, thumbData);
// ConvertScrollbarPoint() got the drag start offset relative to
// the scroll track. Now get it relative to the thumb.
dragStart -= thumbData.mThumbStart;
// ScrollThumbData::mThumbStart stores the offset of the thumb
// relative to the scroll track at the time of the last paint.
// Since that paint, the thumb may have acquired an async transform
// due to async scrolling, so look that up and apply it.
LayerToParentLayerMatrix4x4 thumbTransform;
{
MutexAutoLock lock(mTreeLock);
thumbTransform = ComputeTransformForNode(hitScrollbarNode);
}
// Only consider the translation, since we do not support both
// zooming and scrollbar dragging on any platform.
CSSCoord thumbStart = thumbData.mThumbStart
+ ((thumbData.mDirection == ScrollDirection::HORIZONTAL)
? thumbTransform._41 : thumbTransform._42);
dragStart -= thumbStart;
mInputQueue->ConfirmDragBlock(
dragBlockId, apzc,
AsyncDragMetrics(apzc->GetGuid().mScrollId,
@ -1664,7 +1678,7 @@ APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
already_AddRefed<HitTestingTreeNode>
APZCTreeManager::GetTargetNode(const ScrollableLayerGuid& aGuid,
GuidComparator aComparator)
GuidComparator aComparator) const
{
mTreeLock.AssertCurrentThreadOwns();
RefPtr<HitTestingTreeNode> target = DepthFirstSearchPostOrder<ReverseIterator>(mRootNode.get(),
@ -1836,12 +1850,15 @@ APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode,
// APZCs front-to-back on the screen.
HitTestingTreeNode* resultNode;
HitTestingTreeNode* root = aNode;
std::stack<ParentLayerPoint> hitTestPoints;
hitTestPoints.push(aHitTestPoint);
std::stack<LayerPoint> hitTestPoints;
hitTestPoints.push(ViewAs<LayerPixel>(aHitTestPoint,
PixelCastJustification::MovingDownToChildren));
ForEachNode<ReverseIterator>(root,
[&hitTestPoints](HitTestingTreeNode* aNode) {
if (aNode->IsOutsideClip(hitTestPoints.top())) {
[&hitTestPoints, this](HitTestingTreeNode* aNode) {
ParentLayerPoint hitTestPointForParent = ViewAs<ParentLayerPixel>(hitTestPoints.top(),
PixelCastJustification::MovingDownToChildren);
if (aNode->IsOutsideClip(hitTestPointForParent)) {
// If the point being tested is outside the clip region for this node
// then we don't need to test against this node or any of its children.
// Just skip it and move on.
@ -1851,21 +1868,21 @@ APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode,
}
// First check the subtree rooted at this node, because deeper nodes
// are more "in front".
Maybe<LayerPoint> hitTestPointForChildLayers = aNode->Untransform(hitTestPoints.top());
Maybe<LayerPoint> hitTestPoint = aNode->Untransform(
hitTestPointForParent, ComputeTransformForNode(aNode));
APZCTM_LOG("Transformed ParentLayer point %s to layer %s\n",
Stringify(hitTestPoints.top()).c_str(),
hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil");
if (!hitTestPointForChildLayers) {
Stringify(hitTestPointForParent).c_str(),
hitTestPoint ? Stringify(hitTestPoint.ref()).c_str() : "nil");
if (!hitTestPoint) {
return TraversalFlag::Skip;
}
hitTestPoints.push(ViewAs<ParentLayerPixel>(hitTestPointForChildLayers.ref(),
PixelCastJustification::MovingDownToChildren));
hitTestPoints.push(hitTestPoint.ref());
return TraversalFlag::Continue;
},
[&resultNode, &hitTestPoints, &aOutHitResult](HitTestingTreeNode* aNode) {
hitTestPoints.pop();
HitTestResult hitResult = aNode->HitTest(hitTestPoints.top());
APZCTM_LOG("Testing ParentLayer point %s against node %p\n",
hitTestPoints.pop();
APZCTM_LOG("Testing Layer point %s against node %p\n",
Stringify(hitTestPoints.top()).c_str(), aNode);
if (hitResult != HitTestResult::HitNothing) {
resultNode = aNode;
@ -2202,6 +2219,40 @@ APZCTreeManager::CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomCont
return ancestor.forget();
}
LayerToParentLayerMatrix4x4
APZCTreeManager::ComputeTransformForNode(const HitTestingTreeNode* aNode) const
{
if (AsyncPanZoomController* apzc = aNode->GetApzc()) {
// If the node represents scrollable content, apply the async transform
// from its APZC.
return aNode->GetTransform() *
CompleteAsyncTransform(
apzc->GetCurrentAsyncTransformWithOverscroll(AsyncPanZoomController::NORMAL));
} else if (aNode->IsScrollThumbNode()) {
// If the node represents a scrollbar thumb, compute and apply the
// transformation that will be applied to the thumb in
// AsyncCompositionManager.
ScrollableLayerGuid guid{aNode->GetLayersId(), 0, aNode->GetScrollTargetId()};
if (RefPtr<HitTestingTreeNode> scrollTargetNode = GetTargetNode(guid, &GuidComparatorIgnoringPresShell)) {
AsyncPanZoomController* scrollTargetApzc = scrollTargetNode->GetApzc();
MOZ_ASSERT(scrollTargetApzc);
return scrollTargetApzc->CallWithLastContentPaintMetrics(
[&](const FrameMetrics& aMetrics) {
return AsyncCompositionManager::ComputeTransformForScrollThumb(
aNode->GetTransform() * AsyncTransformMatrix(),
scrollTargetNode->GetTransform().ToUnknownMatrix(),
scrollTargetApzc,
aMetrics,
aNode->GetScrollThumbData(),
scrollTargetNode->IsAncestorOf(aNode),
nullptr);
});
}
}
// Otherwise, the node does not have an async transform.
return aNode->GetTransform() * AsyncTransformMatrix();
}
#if defined(MOZ_WIDGET_ANDROID)
void
APZCTreeManager::InitializeDynamicToolbarAnimator(const int64_t& aRootLayerTreeId)

Просмотреть файл

@ -465,7 +465,7 @@ private:
HitTestingTreeNode* aNextSibling);
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid);
already_AddRefed<HitTestingTreeNode> GetTargetNode(const ScrollableLayerGuid& aGuid,
GuidComparator aComparator);
GuidComparator aComparator) const;
HitTestingTreeNode* FindTargetNode(HitTestingTreeNode* aNode,
const ScrollableLayerGuid& aGuid,
GuidComparator aComparator);
@ -505,6 +505,9 @@ private:
void NotifyScrollbarDragRejected(const ScrollableLayerGuid& aGuid) const;
// Requires the caller to hold mTreeLock.
LayerToParentLayerMatrix4x4 ComputeTransformForNode(const HitTestingTreeNode* aNode) const;
protected:
/* The input queue where input events are held until we know enough to
* figure out where they're going. Protected so gtests can access it.

Просмотреть файл

@ -748,6 +748,20 @@ private:
friend class Axis;
public:
/**
* Invoke |callable|, passing |mLastContentPaintMetrics| as argument,
* while holding the APZC lock required to access |mLastContentPaintMetrics|.
* This allows code outside of an AsyncPanZoomController method implementation
* to access |mLastContentPaintMetrics| without having to make a copy of it.
* Passes through the return value of |callable|.
*/
template <typename Callable>
auto CallWithLastContentPaintMetrics(const Callable& callable) const
-> decltype(callable(mLastContentPaintMetrics)) {
ReentrantMonitorAutoEnter lock(mMonitor);
return callable(mLastContentPaintMetrics);
}
/* ===================================================================
* The functions and members in this section are used to expose

Просмотреть файл

@ -198,6 +198,17 @@ HitTestingTreeNode::GetParent() const
return mParent;
}
bool
HitTestingTreeNode::IsAncestorOf(const HitTestingTreeNode* aOther) const
{
for (const HitTestingTreeNode* cur = aOther; cur; cur = cur->GetParent()) {
if (cur == this) {
return true;
}
}
return false;
}
AsyncPanZoomController*
HitTestingTreeNode::GetApzc() const
{
@ -247,15 +258,10 @@ HitTestingTreeNode::IsOutsideClip(const ParentLayerPoint& aPoint) const
}
Maybe<LayerPoint>
HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint) const
HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint,
const LayerToParentLayerMatrix4x4& aTransform) const
{
// convert into Layer coordinate space
LayerToParentLayerMatrix4x4 transform = mTransform *
CompleteAsyncTransform(
mApzc
? mApzc->GetCurrentAsyncTransformWithOverscroll(AsyncPanZoomController::NORMAL)
: AsyncTransformComponentMatrix());
Maybe<ParentLayerToLayerMatrix4x4> inverse = transform.MaybeInverse();
Maybe<ParentLayerToLayerMatrix4x4> inverse = aTransform.MaybeInverse();
if (inverse) {
return UntransformBy(inverse.ref(), aPoint);
}
@ -263,22 +269,13 @@ HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint) const
}
HitTestResult
HitTestingTreeNode::HitTest(const ParentLayerPoint& aPoint) const
HitTestingTreeNode::HitTest(const LayerPoint& aPoint) const
{
// This should only ever get called if the point is inside the clip region
// for this node.
MOZ_ASSERT(!IsOutsideClip(aPoint));
if (mOverride & EventRegionsOverride::ForceEmptyHitRegion) {
return HitTestResult::HitNothing;
}
// convert into Layer coordinate space
Maybe<LayerPoint> pointInLayerPixels = Untransform(aPoint);
if (!pointInLayerPixels) {
return HitTestResult::HitNothing;
}
auto point = LayerIntPoint::Round(pointInLayerPixels.ref());
auto point = LayerIntPoint::Round(aPoint);
// test against event regions in Layer coordinate space
if (!mEventRegions.mHitRegion.Contains(point.x, point.y)) {
@ -312,6 +309,12 @@ HitTestingTreeNode::GetEventRegionsOverride() const
return mOverride;
}
const CSSTransformMatrix&
HitTestingTreeNode::GetTransform() const
{
return mTransform;
}
void
HitTestingTreeNode::Dump(const char* aPrefix) const
{

Просмотреть файл

@ -74,6 +74,8 @@ public:
HitTestingTreeNode* GetPrevSibling() const;
HitTestingTreeNode* GetParent() const;
bool IsAncestorOf(const HitTestingTreeNode* aOther) const;
/* APZC related methods */
AsyncPanZoomController* GetApzc() const;
@ -105,14 +107,17 @@ public:
void SetFixedPosData(FrameMetrics::ViewID aFixedPosTarget);
FrameMetrics::ViewID GetFixedPosTarget() const;
/* Convert aPoint into the LayerPixel space for the layer corresponding to
/* Convert |aPoint| into the LayerPixel space for the layer corresponding to
* this node. |aTransform| is the complete (content + async) transform for
* this node. */
Maybe<LayerPoint> Untransform(const ParentLayerPoint& aPoint) const;
Maybe<LayerPoint> Untransform(const ParentLayerPoint& aPoint,
const LayerToParentLayerMatrix4x4& aTransform) const;
/* Assuming aPoint is inside the clip region for this node, check which of the
* event region spaces it falls inside. */
HitTestResult HitTest(const ParentLayerPoint& aPoint) const;
HitTestResult HitTest(const LayerPoint& aPoint) const;
/* Returns the mOverride flag. */
EventRegionsOverride GetEventRegionsOverride() const;
const CSSTransformMatrix& GetTransform() const;
/* Debug helpers */
void Dump(const char* aPrefix = "") const;

Просмотреть файл

@ -96,8 +96,9 @@ protected:
static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) {
ParentLayerIntRect compositionBounds = ViewAs<ParentLayerPixel>(
aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
ParentLayerIntRect compositionBounds =
RoundedToInt(aLayer->GetLocalTransformTyped().
TransformBounds(LayerRect(aLayer->GetVisibleRegion().GetBounds())));
ScrollMetadata metadata = BuildScrollMetadata(aScrollId, aScrollableRect,
ParentLayerRect(compositionBounds));
aLayer->SetScrollMetadata(metadata);

Просмотреть файл

@ -278,6 +278,30 @@ TEST_F(APZHitTestingTester, HitTesting2) {
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25)));
}
TEST_F(APZHitTestingTester, HitTesting3) {
const char* layerTreeSyntax = "c(t)";
// LayerID 0 1
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0,0,200,200)),
nsIntRegion(IntRect(0,0,50,50))
};
Matrix4x4 transforms[] = {
Matrix4x4(),
Matrix4x4::Scaling(2, 2, 1)
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, transforms, lm, layers);
// No actual room to scroll
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 50, 50));
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 75));
EXPECT_EQ(ApzcOf(layers[1]), hit.get());
}
TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
CreateComplexMultiLayerTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);

Просмотреть файл

@ -1106,30 +1106,62 @@ static void
ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
const LayerMetricsWrapper& aContent,
bool aScrollbarIsDescendant)
{
AsyncTransformComponentMatrix clipTransform;
LayerToParentLayerMatrix4x4 transform =
AsyncCompositionManager::ComputeTransformForScrollThumb(
aScrollbar->GetLocalTransformTyped(),
aContent.GetTransform(),
aContent.GetApzc(),
aContent.Metrics(),
aScrollbar->GetScrollThumbData(),
aScrollbarIsDescendant,
&clipTransform);
if (aScrollbarIsDescendant) {
// We also need to make a corresponding change on the clip rect of all the
// layers on the ancestor chain from the scrollbar layer up to but not
// including the layer with the async transform. Otherwise the scrollbar
// shifts but gets clipped and so appears to flicker.
for (Layer* ancestor = aScrollbar; ancestor != aContent.GetLayer(); ancestor = ancestor->GetParent()) {
TransformClipRect(ancestor, clipTransform);
}
}
SetShadowTransform(aScrollbar, transform);
}
/* static */ LayerToParentLayerMatrix4x4
AsyncCompositionManager::ComputeTransformForScrollThumb(
const LayerToParentLayerMatrix4x4& aCurrentTransform,
const Matrix4x4& aScrollableContentTransform,
AsyncPanZoomController* aApzc,
const FrameMetrics& aMetrics,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform)
{
// We only apply the transform if the scroll-target layer has non-container
// children (i.e. when it has some possibly-visible content). This is to
// avoid moving scroll-bars in the situation that only a scroll information
// layer has been built for a scroll frame, as this would result in a
// disparity between scrollbars and visible content.
if (aContent.IsScrollInfoLayer()) {
return;
if (aMetrics.IsScrollInfoLayer()) {
return LayerToParentLayerMatrix4x4{};
}
const FrameMetrics& metrics = aContent.Metrics();
AsyncPanZoomController* apzc = aContent.GetApzc();
MOZ_RELEASE_ASSERT(apzc);
MOZ_RELEASE_ASSERT(aApzc);
AsyncTransformComponentMatrix asyncTransform =
apzc->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
aApzc->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
// |asyncTransform| represents the amount by which we have scrolled and
// zoomed since the last paint. Because the scrollbar was sized and positioned based
// on the painted content, we need to adjust it based on asyncTransform so that
// it reflects what the user is actually seeing now.
AsyncTransformComponentMatrix scrollbarTransform;
const ScrollThumbData& thumbData = aScrollbar->GetScrollThumbData();
if (thumbData.mDirection == ScrollDirection::VERTICAL) {
if (aThumbData.mDirection == ScrollDirection::VERTICAL) {
const ParentLayerCoord asyncScrollY = asyncTransform._42;
const float asyncZoomY = asyncTransform._22;
@ -1139,13 +1171,13 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
const float yScale = 1.f / asyncZoomY;
// Note: |metrics.GetZoom()| doesn't yet include the async zoom.
const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().yScale * asyncZoomY);
const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().yScale * asyncZoomY);
// Here we convert the scrollbar thumb ratio into a true unitless ratio by
// dividing out the conversion factor from the scrollframe's parent's space
// to the scrollframe's space.
const float ratio = thumbData.mThumbRatio /
(metrics.GetPresShellResolution() * asyncZoomY);
const float ratio = aThumbData.mThumbRatio /
(aMetrics.GetPresShellResolution() * asyncZoomY);
// The scroll thumb needs to be translated in opposite direction of the
// async scroll. This is because scrolling down, which translates the layer
// content up, should result in moving the scroll thumb down.
@ -1162,26 +1194,26 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
// a change of basis. We have a method to help with that,
// Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code
// cleaner in this case).
const CSSCoord thumbOrigin = (metrics.GetScrollOffset().y * ratio);
const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().y * ratio);
const CSSCoord thumbOriginScaled = thumbOrigin * yScale;
const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom;
yTranslation -= thumbOriginDeltaPL;
if (metrics.IsRootContent()) {
if (aMetrics.IsRootContent()) {
// Scrollbar for the root are painted at the same resolution as the
// content. Since the coordinate space we apply this transform in includes
// the resolution, we need to adjust for it as well here. Note that in
// another metrics.IsRootContent() hunk below we apply a
// resolution-cancelling transform which ensures the scroll thumb isn't
// actually rendered at a larger scale.
yTranslation *= metrics.GetPresShellResolution();
yTranslation *= aMetrics.GetPresShellResolution();
}
scrollbarTransform.PostScale(1.f, yScale, 1.f);
scrollbarTransform.PostTranslate(0, yTranslation, 0);
}
if (thumbData.mDirection == ScrollDirection::HORIZONTAL) {
if (aThumbData.mDirection == ScrollDirection::HORIZONTAL) {
// See detailed comments under the VERTICAL case.
const ParentLayerCoord asyncScrollX = asyncTransform._41;
@ -1189,20 +1221,20 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
const float xScale = 1.f / asyncZoomX;
const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().xScale * asyncZoomX);
const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().xScale * asyncZoomX);
const float ratio = thumbData.mThumbRatio /
(metrics.GetPresShellResolution() * asyncZoomX);
const float ratio = aThumbData.mThumbRatio /
(aMetrics.GetPresShellResolution() * asyncZoomX);
ParentLayerCoord xTranslation = -asyncScrollX * ratio;
const CSSCoord thumbOrigin = (metrics.GetScrollOffset().x * ratio);
const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().x * ratio);
const CSSCoord thumbOriginScaled = thumbOrigin * xScale;
const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom;
xTranslation -= thumbOriginDeltaPL;
if (metrics.IsRootContent()) {
xTranslation *= metrics.GetPresShellResolution();
if (aMetrics.IsRootContent()) {
xTranslation *= aMetrics.GetPresShellResolution();
}
scrollbarTransform.PostScale(xScale, 1.f, 1.f);
@ -1210,7 +1242,7 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
}
LayerToParentLayerMatrix4x4 transform =
aScrollbar->GetLocalTransformTyped() * scrollbarTransform;
aCurrentTransform * scrollbarTransform;
AsyncTransformComponentMatrix compensation;
// If the scrollbar layer is for the root then the content's resolution
@ -1218,11 +1250,11 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
// thumb's size to vary with the zoom (other than its length reflecting the
// fraction of the scrollable length that's in view, which is taken care of
// above), we apply a transform to cancel out this resolution.
if (metrics.IsRootContent()) {
if (aMetrics.IsRootContent()) {
compensation =
AsyncTransformComponentMatrix::Scaling(
metrics.GetPresShellResolution(),
metrics.GetPresShellResolution(),
aMetrics.GetPresShellResolution(),
aMetrics.GetPresShellResolution(),
1.0f).Inverse();
}
// If the scrollbar layer is a child of the content it is a scrollbar for,
@ -1238,9 +1270,9 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
// and then unapplying it after unapplying the async transform.
if (aScrollbarIsDescendant) {
AsyncTransformComponentMatrix overscroll =
apzc->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
aApzc->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
Matrix4x4 asyncUntransform = (asyncTransform * overscroll).Inverse().ToUnknownMatrix();
Matrix4x4 contentTransform = aContent.GetTransform();
Matrix4x4 contentTransform = aScrollableContentTransform;
Matrix4x4 contentUntransform = contentTransform.Inverse();
AsyncTransformComponentMatrix asyncCompensation =
@ -1251,17 +1283,15 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
compensation = compensation * asyncCompensation;
// We also need to make a corresponding change on the clip rect of all the
// layers on the ancestor chain from the scrollbar layer up to but not
// including the layer with the async transform. Otherwise the scrollbar
// shifts but gets clipped and so appears to flicker.
for (Layer* ancestor = aScrollbar; ancestor != aContent.GetLayer(); ancestor = ancestor->GetParent()) {
TransformClipRect(ancestor, asyncCompensation);
// Pass the async compensation out to the caller so that it can use it
// to transform clip transforms as needed.
if (aOutClipTransform) {
*aOutClipTransform = asyncCompensation;
}
}
transform = transform * compensation;
SetShadowTransform(aScrollbar, transform);
return transform;
}
static LayerMetricsWrapper

Просмотреть файл

@ -131,6 +131,38 @@ public:
};
typedef std::map<Layer*, ClipParts> ClipPartsCache;
/**
* Compute the updated shadow transform for a scroll thumb layer that
* reflects async scrolling of the associated scroll frame.
*
* @param aCurrentTransform The current shadow transform on the scroll thumb
* layer, as returned by Layer::GetLocalTransform() or similar.
* @param aScrollableContentTransform The current content transform on the
* scrollable content, as returned by Layer::GetTransform().
* @param aApzc The APZC that scrolls the scroll frame.
* @param aMetrics The metrics associated with the scroll frame, reflecting
* the last paint of the associated content. Note: this metrics should
* NOT reflect async scrolling, i.e. they should be the layer tree's
* copy of the metrics, or APZC's last-content-paint metrics.
* @param aThumbData The scroll thumb data for the the scroll thumb layer.
* @param aScrollbarIsDescendant True iff. the scroll thumb layer is a
* descendant of the layer bearing the scroll frame's metrics.
* @param aOutClipTransform If not null, and |aScrollbarIsDescendant| is true,
* this will be populated with a transform that should be applied to the
* clip rects of all layers between the scroll thumb layer and the ancestor
* layer for the scrollable content.
* @return The new shadow transform for the scroll thumb layer, including
* any pre- or post-scales.
*/
static LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb(
const LayerToParentLayerMatrix4x4& aCurrentTransform,
const gfx::Matrix4x4& aScrollableContentTransform,
AsyncPanZoomController* aApzc,
const FrameMetrics& aMetrics,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform);
private:
// Return true if an AsyncPanZoomController content transform was
// applied for |aLayer|. |*aOutFoundRoot| is set to true on Android only, if

Просмотреть файл

@ -15,8 +15,6 @@
#ifdef XP_WIN
# include <windows.h>
# undef assert
# undef min
# undef max
# undef GetProp
# undef MemoryBarrier
# undef SetProp

Просмотреть файл

@ -737,9 +737,6 @@ if CONFIG['GNU_CXX']:
if CONFIG['CLANG_CXX']:
SOURCES['jsdtoa.cpp'].flags += ['-Wno-implicit-fallthrough']
if CONFIG['OS_ARCH'] == 'WINNT':
DEFINES['NOMINMAX'] = True
# Generate GC statistics phase data.
GENERATED_FILES += ['gc/StatsPhasesGenerated.h']
StatsPhasesGeneratedHeader = GENERATED_FILES['gc/StatsPhasesGenerated.h']

Просмотреть файл

@ -811,6 +811,8 @@ case "$target" in
fi
AC_DEFINE(HAVE__MSIZE)
AC_DEFINE(WIN32_LEAN_AND_MEAN)
dnl See http://support.microsoft.com/kb/143208 to use STL
AC_DEFINE(NOMINMAX)
BIN_SUFFIX='.exe'
MOZ_USER_DIR="Mozilla"

Просмотреть файл

@ -3600,6 +3600,10 @@ PaintedLayerData::AccumulateEventRegions(ContainerState* aState, nsDisplayLayerE
mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion());
}
// Avoid quadratic performance as a result of the region growing to include
// and arbitrarily large number of rects, which can happen on some pages.
mMaybeHitRegion.SimplifyOutward(8);
// Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion
// for quick access in FindPaintedLayerFor().
mScaledHitRegionBounds = aState->ScaleToOutsidePixels(mHitRegion.GetBounds());

Просмотреть файл

@ -1510,6 +1510,11 @@ var BrowserApp = {
var promises = [];
let refObj = {};
if (aShutdown && Object.getOwnPropertyNames(aItems).length > 0) {
let msg = Strings.browser.GetStringFromName("alertShutdownSanitize");
Snackbars.show(msg, Snackbars.LENGTH_INDEFINITE);
}
TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
for (let key in aItems) {

Просмотреть файл

@ -39,6 +39,10 @@ alertSearchEngineAddedToast='%S' has been added as a search engine
alertSearchEngineErrorToast=Couldn't add '%S' as a search engine
alertSearchEngineDuplicateToast='%S' is already one of your search engines
# LOCALIZATION NOTE (alertShutdownSanitize): This text is shown as a snackbar during shutdown if the
# user has enabled "Clear private data on exit".
alertShutdownSanitize=Clearing private data…
alertPrintjobToast=Printing…
downloadCancelPromptTitle1=Abort Download

Просмотреть файл

@ -1963,6 +1963,10 @@ DataChannelConnection::Open(const nsACString& label, const nsACString& protocol,
case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED:
prPolicy = SCTP_PR_SCTP_TTL;
break;
default:
LOG(("ERROR: unsupported channel type: %u", type));
MOZ_ASSERT(false);
return nullptr;
}
if ((prPolicy == SCTP_PR_SCTP_NONE) && (prValue != 0)) {
return nullptr;
@ -1981,7 +1985,7 @@ DataChannelConnection::Open(const nsACString& label, const nsACString& protocol,
aStream,
DataChannel::CONNECTING,
label, protocol,
type, prValue,
prPolicy, prValue,
flags,
aListener, aContext));
if (aExternalNegotiated) {

46
servo/Cargo.lock сгенерированный
Просмотреть файл

@ -194,6 +194,11 @@ name = "bit-vec"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.7.0"
@ -2142,6 +2147,15 @@ dependencies = [
"time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pulldown-cmark"
version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quasi"
version = "0.32.0"
@ -2351,6 +2365,7 @@ dependencies = [
"smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1",
"style_traits 0.0.1",
"swapper 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2407,7 +2422,6 @@ dependencies = [
name = "script_traits"
version = "0.0.1"
dependencies = [
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bluetooth_traits 0.0.1",
"canvas_traits 0.0.1",
"cookie 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2738,6 +2752,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "size_of_test"
version = "0.0.1"
[[package]]
name = "skeptic"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "slab"
version = "0.3.0"
@ -2883,6 +2906,14 @@ dependencies = [
"style_traits 0.0.1",
]
[[package]]
name = "swapper"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"skeptic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.11.11"
@ -2969,6 +3000,14 @@ dependencies = [
"gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempdir"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tendril"
version = "0.2.4"
@ -3396,6 +3435,7 @@ dependencies = [
"checksum bindgen 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ccaf8958532d7e570e905266ee2dc1094c3e5c3c3cfc2c299368747a30a5e654"
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
"checksum bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80b13e2ab064ff3aa0bdbf1eff533f9822dc37899821f5f98c67f263eab51707"
@ -3551,6 +3591,7 @@ dependencies = [
"checksum png 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3cb773e9a557edb568ce9935cf783e3cdcabe06a9449d41b3e5506d88e582c82"
"checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150"
"checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
"checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41"
"checksum quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c45c4854d6d1cf5d531db97c75880feb91c958b0720f4ec1057135fec358b3"
"checksum quasi_codegen 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9e25fa23c044c1803f43ca59c98dac608976dd04ce799411edd58ece776d4"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
@ -3587,12 +3628,14 @@ dependencies = [
"checksum signpost 0.1.0 (git+https://github.com/pcwalton/signpost.git)" = "<none>"
"checksum simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a94d14a2ae1f1f110937de5fb69e494372560181c7e1739a097fcc2cee37ba0"
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
"checksum skeptic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd7d8dc1315094150052d0ab767840376335a98ac66ef313ff911cdf439a5b69"
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
"checksum smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4f8266519bc1d17d0b5b16f6c21295625d562841c708f6376f49028a43e9c11e"
"checksum string_cache 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f55fba06c5e294108f22e8512eb598cb13388a117991e411a8df8f41a1219a75"
"checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7"
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum swapper 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca610b32bb8bfc5e7f705480c3a1edfeb70b6582495d343872c8bee0dcf758c"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ccc9780bf1aa601943988c2876ab22413c01ad1739689aa6af18d0aa0b3f38b"
@ -3601,6 +3644,7 @@ dependencies = [
"checksum syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13ad4762fe52abc9f4008e85c4fb1b1fe3aa91ccb99ff4826a439c7c598e1047"
"checksum syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e0e4dbae163dd98989464c23dd503161b338790640e11537686f2ef0f25c791"
"checksum target_build_utils 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f42dc058080c19c6a58bdd1bf962904ee4f5ef1fe2a81b529f31dacc750c679f"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce04c250d202db8004921e3d3bc95eaa4f2126c6937a428ae39d12d0e38df62"
"checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989"
"checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a"

Просмотреть файл

@ -150,6 +150,7 @@ impl Formattable for ProfilerCategory {
ProfilerCategory::ScriptEnterFullscreen => "Script Enter Fullscreen",
ProfilerCategory::ScriptExitFullscreen => "Script Exit Fullscreen",
ProfilerCategory::ScriptWebVREvent => "Script WebVR Event",
ProfilerCategory::ScriptWorkletEvent => "Script Worklet Event",
ProfilerCategory::ApplicationHeartbeat => "Application Heartbeat",
};
format!("{}{}", padding, name)

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше