зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
34b5af799e
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче