Merge autoland to central, a=merge

MozReview-Commit-ID: 8MgCHGpAZO0
This commit is contained in:
Wes Kocher 2017-03-02 13:09:17 -08:00
Родитель 546a05fec0 3f70992816
Коммит dbc22c750b
88 изменённых файлов: 13121 добавлений и 3148 удалений

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

@ -65,9 +65,6 @@ pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/Sys
// See the SCOPE constants in AddonManager.jsm for values to use here.
pref("extensions.autoDisableScopes", 15);
// Whether or not webextension themes are supported.
pref("extensions.webextensions.themes.enabled", false);
// Add-on content security policies.
pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' https://* moz-extension: blob: filesystem:;");
pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");

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

@ -5740,6 +5740,10 @@ function handleLinkClick(event, href, linkNode) {
}
}
let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
urlSecurityCheck(href, doc.nodePrincipal);
let params = {
charset: doc.characterSet,
@ -5748,6 +5752,7 @@ function handleLinkClick(event, href, linkNode) {
referrerPolicy,
noReferrer: BrowserUtils.linkHasNoReferrer(linkNode),
originPrincipal: doc.nodePrincipal,
frameOuterWindowID,
};
// The new tab/window must use the same userContextId

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

@ -501,10 +501,14 @@ var ClickEventHandler = {
}
}
let frameOuterWindowID = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
let json = { button: event.button, shiftKey: event.shiftKey,
ctrlKey: event.ctrlKey, metaKey: event.metaKey,
altKey: event.altKey, href: null, title: null,
bookmark: false, referrerPolicy,
bookmark: false, frameOuterWindowID, referrerPolicy,
triggeringPrincipal: principal,
originAttributes: principal ? principal.originAttributes : {},
isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)};

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

@ -951,11 +951,17 @@ nsContextMenu.prototype = {
originPrincipal: this.principal,
referrerURI: gContextMenuContentData.documentURIObject,
referrerPolicy: gContextMenuContentData.referrerPolicy,
frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
noReferrer: this.linkHasNoReferrer };
for (let p in extra) {
params[p] = extra[p];
}
if (!this.isRemote) {
params.frameOuterWindowID = this.target.ownerGlobal
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
}
// If we want to change userContextId, we must be sure that we don't
// propagate the referrer.
if ("userContextId" in params &&

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

@ -304,7 +304,30 @@ function openLinkIn(url, where, params) {
features += ",private";
}
Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
const sourceWindow = (w || window);
let win;
if (params.frameOuterWindowID && sourceWindow) {
// Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
// event if it contains the expected frameOuterWindowID params.
// (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
// opening a new window using the keyboard shortcut).
const sourceTabBrowser = sourceWindow.gBrowser.selectedBrowser;
let delayedStartupObserver = aSubject => {
if (aSubject == win) {
Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
Services.obs.notifyObservers({
wrappedJSObject: {
url,
createdTabBrowser: win.gBrowser.selectedBrowser,
sourceTabBrowser,
sourceFrameOuterWindowID: params.frameOuterWindowID,
},
}, "webNavigation-createdNavigationTarget", null);
}
};
Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
}
win = Services.ww.openWindow(sourceWindow, getBrowserURL(), null, features, sa);
return;
}
@ -406,6 +429,21 @@ function openLinkIn(url, where, params) {
triggeringPrincipal: aPrincipal,
});
targetBrowser = tabUsedForLoad.linkedBrowser;
if (params.frameOuterWindowID && w) {
// Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
// event if it contains the expected frameOuterWindowID params.
// (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
// opening a new tab using the keyboard shortcut).
Services.obs.notifyObservers({
wrappedJSObject: {
url,
createdTabBrowser: targetBrowser,
sourceTabBrowser: w.gBrowser.selectedBrowser,
sourceFrameOuterWindowID: params.frameOuterWindowID,
},
}, "webNavigation-createdNavigationTarget", null);
}
break;
}

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

@ -15,5 +15,4 @@ browser.jar:
content/branding/icon32.png (../default32.png)
content/branding/icon128.png (../mozicon128.png)
content/branding/identity-icons-brand.svg
content/branding/silhouette-40.svg
content/branding/aboutDialog.css

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

@ -1,26 +0,0 @@
<?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" viewBox="-45 31 40 40">
<path fill="#ccc" d="M-14.1,54.7c0.7-1.4,1.7-4.4,0.8-6.9c0,0,0,0,0,0.1l0,0c0,0-0.2,0.5-0.4,1.3c0-0.1,0-0.2,0-0.3
c0.1-0.9,0-1.9-0.1-2.9c-0.3-1.5-1.4-2.8-2-3.2c0,0,0.1,0,0.1,0.1c-0.1-0.1-0.1-0.1-0.1-0.1s0,0.1,0.1,0.4c-0.7-1.1-1.6-1.5-1.6-1.5
s0,0.2,0.1,0.5c-2-1.9-4.7-3-7.6-3c-3,0-5.7,1.2-7.8,3.1c0.1,0.1,0.2,0.3,0.4,0.5c0,0,0.8-0.1,1.7-0.1c1.7-1.2,3.6-1.8,5.7-1.8
c2.6,0,5.1,1.1,7,3c-0.2-0.1-0.1,0,0,0.1c-0.6-0.4-1.2-0.8-1.7-0.8c1,0.8,2.6,2.7,2.4,6.2c-0.3-0.6-0.6-1-0.9-1.3
c0.4,3.5,0,4.2-0.2,5.1c0-0.4-0.2-0.7-0.3-0.9c0,0,0,1.1-0.7,2.6c-0.5,1.2-1.1,1.5-1.3,1.5c-0.2,0-0.1-0.2-0.1-0.4
c0,0-0.4,0.2-0.7,0.6c-0.3,0.4-0.6,0.8-0.8,0.6c0.1-0.1,0.2-0.3,0.3-0.4c-0.1,0.1-0.5,0.4-1.2,0.5c-0.3,0-1.6,0.3-3.3-0.6
c0.3,0,0.6-0.1,0.9,0.1c-0.3-0.3-1-0.3-1.5-0.4c-0.5-0.4-1.1-1-1.4-1.4c1.3,0.3,2.8,0.1,3.6-0.5s1.3-1,1.8-0.9
c0.4,0.1,0.7-0.4,0.4-0.8c-0.3-0.4-1.2-1-2.3-0.7c-0.8,0.2-1.8,1.1-3.3,0.2c-1.3-0.8-1.3-1.4-1.3-1.8c0-0.3,0.2-0.7,0.5-0.8
c0.2,0.1,0.3,0.1,0.3,0.1s-0.1-0.1-0.1-0.2l0,0c0.1,0,0.4,0.2,0.6,0.2c0.2,0.1,0.3,0.2,0.3,0.2s0,0,0-0.1c0,0-0.1-0.2-0.3-0.3l0,0
c0.1,0,0.2,0.1,0.4,0.2c0-0.2,0.1-0.4,0.1-0.7c0-0.2,0-0.3-0.1-0.4c-0.1-0.1,0-0.1,0.1,0c0-0.1,0-0.1-0.1-0.2l0,0c0,0,0,0,0-0.1
c0.2-0.3,1.8-1.2,1.9-1.3c0.2-0.1,0.3-0.3,0.4-0.5c0.2-0.1,0.3-0.5,0.3-0.8c0-0.1-0.2-0.3-0.4-0.3c-0.1,0-0.4-0.1-0.6,0l0,0
c-0.3,0-0.7,0-1.2,0s-0.8-0.3-1-0.6c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2c0.2-0.8,0.7-1.5,1.4-2.1c0,0-0.2,0-0.1,0
c0,0,0.3-0.2,0.4-0.2c0.1,0-0.3-0.1-0.6-0.1c-0.5,0.2-0.6,0.2-0.8,0.3c0.1-0.1,0.3-0.2,0.2-0.2c-0.3,0.1-0.7,0.4-1.1,0.6v-0.1
c-0.2,0.1-0.6,0.4-0.7,0.7c0-0.1,0-0.1,0-0.1c-0.1,0-0.2,0.2-0.3,0.3l0,0c-1.1-0.3-2-0.2-2.8,0c-0.2-0.1-0.6-0.5-0.9-1
c0,0,0,0.1-0.1,0.1c-0.1-0.4-0.3-0.9-0.3-1.3v-0.1c0,0-0.1,0.1-0.3,0.3c-0.1,0.2-0.2,0.3-0.2,0.5c0,0.1-0.1,0.2-0.1,0.2v-0.2
c0,0.1-0.1,0.2-0.2,0.3c0,0.2,0,0.3-0.1,0.4l0,0c0,0,0-0.2,0-0.1c-0.1,0.2-0.2,0.5-0.2,0.8c-0.1,0.3-0.1,0.5-0.1,0.8s0,0.7,0,1.2
c0,0.1,0,0.1,0,0.2c-0.3,0.4-0.5,0.7-0.6,0.9c-0.4,0.7-0.7,1.8-1,3.5c0,0,0.2-0.6,0.6-1.3l0,0c-0.3,0.9-0.5,2.3-0.4,4.4
c0-0.1,0.1-0.6,0.2-1.3c0.1,1.4,0.5,3.1,1.5,5c0.8,1.4,1.7,2.4,2.7,3.2c0.2,0.2,0.4,0.3,0.6,0.5c1.3,1,3.3,2.1,5,2.4
c-0.6-0.2-1-0.5-1-0.5s2,0.7,3.5,0.6c-0.5-0.1-0.6-0.3-0.6-0.3s4.2,0.2,6.4-1.5c0.5-0.4,0.8-0.8,0.9-1.2c0.6-0.4,1.3-0.8,2-1.6
c1.2-1.2,1.3-2.1,1.4-3v0.1C-14,55.2-14,54.9-14.1,54.7z"/>
</svg>

До

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

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

@ -15,5 +15,4 @@ browser.jar:
content/branding/icon32.png (../default32.png)
content/branding/icon128.png (../mozicon128.png)
content/branding/identity-icons-brand.svg
content/branding/silhouette-40.svg
content/branding/aboutDialog.css

Разница между файлами не показана из-за своего большого размера Загрузить разницу

До

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

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

@ -14,5 +14,4 @@ browser.jar:
content/branding/icon32.png (../default32.png)
content/branding/icon128.png (../mozicon128.png)
content/branding/identity-icons-brand.svg
content/branding/silhouette-40.svg
content/branding/aboutDialog.css

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

@ -1,26 +0,0 @@
<?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" viewBox="-45 31 40 40">
<path fill="#ccc" d="M-14.1,54.7c0.7-1.4,1.7-4.4,0.8-6.9c0,0,0,0,0,0.1l0,0c0,0-0.2,0.5-0.4,1.3c0-0.1,0-0.2,0-0.3
c0.1-0.9,0-1.9-0.1-2.9c-0.3-1.5-1.4-2.8-2-3.2c0,0,0.1,0,0.1,0.1c-0.1-0.1-0.1-0.1-0.1-0.1s0,0.1,0.1,0.4c-0.7-1.1-1.6-1.5-1.6-1.5
s0,0.2,0.1,0.5c-2-1.9-4.7-3-7.6-3c-3,0-5.7,1.2-7.8,3.1c0.1,0.1,0.2,0.3,0.4,0.5c0,0,0.8-0.1,1.7-0.1c1.7-1.2,3.6-1.8,5.7-1.8
c2.6,0,5.1,1.1,7,3c-0.2-0.1-0.1,0,0,0.1c-0.6-0.4-1.2-0.8-1.7-0.8c1,0.8,2.6,2.7,2.4,6.2c-0.3-0.6-0.6-1-0.9-1.3
c0.4,3.5,0,4.2-0.2,5.1c0-0.4-0.2-0.7-0.3-0.9c0,0,0,1.1-0.7,2.6c-0.5,1.2-1.1,1.5-1.3,1.5c-0.2,0-0.1-0.2-0.1-0.4
c0,0-0.4,0.2-0.7,0.6c-0.3,0.4-0.6,0.8-0.8,0.6c0.1-0.1,0.2-0.3,0.3-0.4c-0.1,0.1-0.5,0.4-1.2,0.5c-0.3,0-1.6,0.3-3.3-0.6
c0.3,0,0.6-0.1,0.9,0.1c-0.3-0.3-1-0.3-1.5-0.4c-0.5-0.4-1.1-1-1.4-1.4c1.3,0.3,2.8,0.1,3.6-0.5s1.3-1,1.8-0.9
c0.4,0.1,0.7-0.4,0.4-0.8c-0.3-0.4-1.2-1-2.3-0.7c-0.8,0.2-1.8,1.1-3.3,0.2c-1.3-0.8-1.3-1.4-1.3-1.8c0-0.3,0.2-0.7,0.5-0.8
c0.2,0.1,0.3,0.1,0.3,0.1s-0.1-0.1-0.1-0.2l0,0c0.1,0,0.4,0.2,0.6,0.2c0.2,0.1,0.3,0.2,0.3,0.2s0,0,0-0.1c0,0-0.1-0.2-0.3-0.3l0,0
c0.1,0,0.2,0.1,0.4,0.2c0-0.2,0.1-0.4,0.1-0.7c0-0.2,0-0.3-0.1-0.4c-0.1-0.1,0-0.1,0.1,0c0-0.1,0-0.1-0.1-0.2l0,0c0,0,0,0,0-0.1
c0.2-0.3,1.8-1.2,1.9-1.3c0.2-0.1,0.3-0.3,0.4-0.5c0.2-0.1,0.3-0.5,0.3-0.8c0-0.1-0.2-0.3-0.4-0.3c-0.1,0-0.4-0.1-0.6,0l0,0
c-0.3,0-0.7,0-1.2,0s-0.8-0.3-1-0.6c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2c0.2-0.8,0.7-1.5,1.4-2.1c0,0-0.2,0-0.1,0
c0,0,0.3-0.2,0.4-0.2c0.1,0-0.3-0.1-0.6-0.1c-0.5,0.2-0.6,0.2-0.8,0.3c0.1-0.1,0.3-0.2,0.2-0.2c-0.3,0.1-0.7,0.4-1.1,0.6v-0.1
c-0.2,0.1-0.6,0.4-0.7,0.7c0-0.1,0-0.1,0-0.1c-0.1,0-0.2,0.2-0.3,0.3l0,0c-1.1-0.3-2-0.2-2.8,0c-0.2-0.1-0.6-0.5-0.9-1
c0,0,0,0.1-0.1,0.1c-0.1-0.4-0.3-0.9-0.3-1.3v-0.1c0,0-0.1,0.1-0.3,0.3c-0.1,0.2-0.2,0.3-0.2,0.5c0,0.1-0.1,0.2-0.1,0.2v-0.2
c0,0.1-0.1,0.2-0.2,0.3c0,0.2,0,0.3-0.1,0.4l0,0c0,0,0-0.2,0-0.1c-0.1,0.2-0.2,0.5-0.2,0.8c-0.1,0.3-0.1,0.5-0.1,0.8s0,0.7,0,1.2
c0,0.1,0,0.1,0,0.2c-0.3,0.4-0.5,0.7-0.6,0.9c-0.4,0.7-0.7,1.8-1,3.5c0,0,0.2-0.6,0.6-1.3l0,0c-0.3,0.9-0.5,2.3-0.4,4.4
c0-0.1,0.1-0.6,0.2-1.3c0.1,1.4,0.5,3.1,1.5,5c0.8,1.4,1.7,2.4,2.7,3.2c0.2,0.2,0.4,0.3,0.6,0.5c1.3,1,3.3,2.1,5,2.4
c-0.6-0.2-1-0.5-1-0.5s2,0.7,3.5,0.6c-0.5-0.1-0.6-0.3-0.6-0.3s4.2,0.2,6.4-1.5c0.5-0.4,0.8-0.8,0.9-1.2c0.6-0.4,1.3-0.8,2-1.6
c1.2-1.2,1.3-2.1,1.4-3v0.1C-14,55.2-14,54.9-14.1,54.7z"/>
</svg>

До

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

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

@ -15,5 +15,4 @@ browser.jar:
content/branding/icon32.png (../default32.png)
content/branding/icon128.png (../mozicon128.png)
content/branding/identity-icons-brand.svg
content/branding/silhouette-40.svg
content/branding/aboutDialog.css

Разница между файлами не показана из-за своего большого размера Загрузить разницу

До

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

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

@ -430,14 +430,6 @@ const DownloadsIndicatorView = {
* progress is not visible if the current progress is unknown.
*/
set percentComplete(aValue) {
// For arrow type only:
// We show portion of the success icon in propotional with the download
// progress as an indicator. the PROGRESS_ICON_EMPTY_HEIGHT_PERCENT and
// PROGRESS_ICON_FULL_HEIGHT_PERCENT correspond to how much portion of the
// icon should be displayed in 0% and 100%.
const PROGRESS_ICON_EMPTY_HEIGHT_PERCENT = 35;
const PROGRESS_ICON_FULL_HEIGHT_PERCENT = 87;
if (!this._operational) {
return this._percentComplete;
}
@ -448,13 +440,13 @@ const DownloadsIndicatorView = {
if (this._percentComplete >= 0) {
this.indicator.setAttribute("progress", "true");
this._progressIcon.style.height = (this._percentComplete *
(PROGRESS_ICON_FULL_HEIGHT_PERCENT -
PROGRESS_ICON_EMPTY_HEIGHT_PERCENT) / 100 +
PROGRESS_ICON_EMPTY_HEIGHT_PERCENT) + "%";
// For arrow type only:
// We set animationDelay to a minus value (0s ~ -100s) to show the
// corresponding frame needed for progress.
this._progressIcon.style.animationDelay = (-this._percentComplete) + "s";
} else {
this.indicator.removeAttribute("progress");
this._progressIcon.style.height = "0";
this._progressIcon.style.animationDelay = "1s";
}
// We have to set the attribute instead of using the property because the
// XBL binding isn't applied if the element is invisible for any reason.

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

@ -15,7 +15,6 @@ category webextension-scripts pageAction chrome://browser/content/ext-pageAction
category webextension-scripts sessions chrome://browser/content/ext-sessions.js
category webextension-scripts sidebarAction chrome://browser/content/ext-sidebarAction.js
category webextension-scripts tabs chrome://browser/content/ext-tabs.js
category webextension-scripts theme chrome://browser/content/ext-theme.js
category webextension-scripts url-overrides chrome://browser/content/ext-url-overrides.js
category webextension-scripts utils chrome://browser/content/ext-utils.js
category webextension-scripts windows chrome://browser/content/ext-windows.js
@ -46,6 +45,5 @@ category webextension-schemas page_action chrome://browser/content/schemas/page_
category webextension-schemas sessions chrome://browser/content/schemas/sessions.json
category webextension-schemas sidebar_action chrome://browser/content/schemas/sidebar_action.json
category webextension-schemas tabs chrome://browser/content/schemas/tabs.json
category webextension-schemas theme chrome://browser/content/schemas/theme.json
category webextension-schemas url_overrides chrome://browser/content/schemas/url_overrides.json
category webextension-schemas windows chrome://browser/content/schemas/windows.json

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

@ -28,7 +28,6 @@ browser.jar:
content/browser/ext-sessions.js
content/browser/ext-sidebarAction.js
content/browser/ext-tabs.js
content/browser/ext-theme.js
content/browser/ext-url-overrides.js
content/browser/ext-utils.js
content/browser/ext-windows.js

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

@ -19,6 +19,5 @@ browser.jar:
content/browser/schemas/sessions.json
content/browser/schemas/sidebar_action.json
content/browser/schemas/tabs.json
content/browser/schemas/theme.json
content/browser/schemas/url_overrides.json
content/browser/schemas/windows.json

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

@ -19,6 +19,9 @@ support-files =
file_dummy.html
file_inspectedwindow_reload_target.sjs
file_serviceWorker.html
webNav_createdTarget.html
webNav_createdTargetSource.html
webNav_createdTargetSource_subframe.html
serviceWorker.js
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
@ -113,9 +116,6 @@ support-files =
[browser_ext_tabs_update.js]
[browser_ext_tabs_zoom.js]
[browser_ext_tabs_update_url.js]
[browser_ext_themes_chromeparity.js]
[browser_ext_themes_dynamic_updates.js]
[browser_ext_themes_lwtsupport.js]
[browser_ext_topwindowid.js]
[browser_ext_url_overrides_all.js]
[browser_ext_url_overrides_home.js]
@ -123,6 +123,8 @@ support-files =
[browser_ext_webRequest.js]
[browser_ext_webNavigation_frameId0.js]
[browser_ext_webNavigation_getFrames.js]
[browser_ext_webNavigation_onCreatedNavigationTarget.js]
[browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js]
[browser_ext_webNavigation_urlbar_transitions.js]
[browser_ext_windows.js]
[browser_ext_windows_create.js]
@ -134,4 +136,4 @@ tags = fullscreen
[browser_ext_windows_size.js]
skip-if = os == 'mac' # Fails when windows are randomly opened in fullscreen mode
[browser_ext_windows_update.js]
tags = fullscreen
tags = fullscreen

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

@ -0,0 +1,285 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
async function background() {
const tabs = await browser.tabs.query({active: true, currentWindow: true});
const sourceTabId = tabs[0].id;
const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
browser.test.sendMessage("webNavOnCreated", msg);
});
browser.webNavigation.onCompleted.addListener(async (msg) => {
// NOTE: checking the url is currently necessary because of Bug 1252129
// ( Filter out webNavigation events related to new window initialization phase).
if (msg.tabId !== sourceTabId && msg.url !== "about:blank") {
await browser.tabs.remove(msg.tabId);
browser.test.sendMessage("webNavOnCompleted", msg);
}
});
browser.tabs.onCreated.addListener((tab) => {
browser.test.sendMessage("tabsOnCreated", tab.id);
});
browser.test.sendMessage("expectedSourceTab", {
sourceTabId, sourceTabFrames,
});
}
async function runTestCase({extension, openNavTarget, expectedWebNavProps}) {
await openNavTarget();
const webNavMsg = await extension.awaitMessage("webNavOnCreated");
const createdTabId = await extension.awaitMessage("tabsOnCreated");
const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
is(webNavMsg.url, url, "Got the expected url property");
is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
}
async function clickContextMenuItem({pageElementSelector, contextMenuItemLabel}) {
const contentAreaContextMenu = await openContextMenu(pageElementSelector);
const item = contentAreaContextMenu.getElementsByAttribute("label", contextMenuItemLabel);
is(item.length, 1, `found contextMenu item for "${contextMenuItemLabel}"`);
item[0].click();
await closeContextMenu();
}
add_task(function* test_on_created_navigation_target_from_mouse_click() {
const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
const extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["webNavigation"],
},
});
yield extension.startup();
const expectedSourceTab = yield extension.awaitMessage("expectedSourceTab");
info("Open link in a new tab using Ctrl-click");
yield runTestCase({
extension,
openNavTarget() {
BrowserTestUtils.synthesizeMouseAtCenter("#test-create-new-tab-from-mouse-click",
{ctrlKey: true, metaKey: true},
tab.linkedBrowser);
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: 0,
url: `${OPENED_PAGE}#new-tab-from-mouse-click`,
},
});
info("Open link in a new window using Shift-click");
yield runTestCase({
extension,
openNavTarget() {
BrowserTestUtils.synthesizeMouseAtCenter("#test-create-new-window-from-mouse-click",
{shiftKey: true},
tab.linkedBrowser);
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: 0,
url: `${OPENED_PAGE}#new-window-from-mouse-click`,
},
});
yield BrowserTestUtils.removeTab(tab);
yield extension.unload();
});
add_task(function* test_on_created_navigation_target_from_context_menu() {
const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
const extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["webNavigation"],
},
});
yield extension.startup();
const expectedSourceTab = yield extension.awaitMessage("expectedSourceTab");
info("Open link in a new tab from the context menu");
yield runTestCase({
extension,
async openNavTarget() {
await clickContextMenuItem({
pageElementSelector: "#test-create-new-tab-from-context-menu",
contextMenuItemLabel: "Open Link in New Tab",
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: 0,
url: `${OPENED_PAGE}#new-tab-from-context-menu`,
},
});
info("Open link in a new window from the context menu");
yield runTestCase({
extension,
async openNavTarget() {
await clickContextMenuItem({
pageElementSelector: "#test-create-new-window-from-context-menu",
contextMenuItemLabel: "Open Link in New Window",
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: 0,
url: `${OPENED_PAGE}#new-window-from-context-menu`,
},
});
yield BrowserTestUtils.removeTab(tab);
yield extension.unload();
});
add_task(function* test_on_created_navigation_target_from_mouse_click_subframe() {
const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
const extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["webNavigation"],
},
});
yield extension.startup();
const expectedSourceTab = yield extension.awaitMessage("expectedSourceTab");
info("Open a subframe link in a new tab using Ctrl-click");
yield runTestCase({
extension,
openNavTarget() {
BrowserTestUtils.synthesizeMouseAtCenter(function() {
// This code runs as a framescript in the child process and it returns the
// target link in the subframe.
return this.content.frames[0].document // eslint-disable-line mozilla/no-cpows-in-tests
.querySelector("#test-create-new-tab-from-mouse-click-subframe");
}, {ctrlKey: true, metaKey: true}, tab.linkedBrowser);
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
url: `${OPENED_PAGE}#new-tab-from-mouse-click-subframe`,
},
});
info("Open a subframe link in a new window using Shift-click");
yield runTestCase({
extension,
openNavTarget() {
BrowserTestUtils.synthesizeMouseAtCenter(function() {
// This code runs as a framescript in the child process and it returns the
// target link in the subframe.
return this.content.frames[0].document // eslint-disable-line mozilla/no-cpows-in-tests
.querySelector("#test-create-new-window-from-mouse-click-subframe");
}, {shiftKey: true}, tab.linkedBrowser);
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
url: `${OPENED_PAGE}#new-window-from-mouse-click-subframe`,
},
});
yield BrowserTestUtils.removeTab(tab);
yield extension.unload();
});
add_task(function* test_on_created_navigation_target_from_context_menu_subframe() {
const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
const extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["webNavigation"],
},
});
yield extension.startup();
const expectedSourceTab = yield extension.awaitMessage("expectedSourceTab");
info("Open a subframe link in a new tab from the context menu");
yield runTestCase({
extension,
async openNavTarget() {
await clickContextMenuItem({
pageElementSelector() {
// This code runs as a framescript in the child process and it returns the
// target link in the subframe.
return this.content.frames[0] // eslint-disable-line mozilla/no-cpows-in-tests
.document.querySelector("#test-create-new-tab-from-context-menu-subframe");
},
contextMenuItemLabel: "Open Link in New Tab",
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
url: `${OPENED_PAGE}#new-tab-from-context-menu-subframe`,
},
});
info("Open a subframe link in a new window from the context menu");
yield runTestCase({
extension,
async openNavTarget() {
await clickContextMenuItem({
pageElementSelector() {
// This code runs as a framescript in the child process and it returns the
// target link in the subframe.
return this.content.frames[0] // eslint-disable-line mozilla/no-cpows-in-tests
.document.querySelector("#test-create-new-window-from-context-menu-subframe");
},
contextMenuItemLabel: "Open Link in New Window",
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
url: `${OPENED_PAGE}#new-window-from-context-menu-subframe`,
},
});
yield BrowserTestUtils.removeTab(tab);
yield extension.unload();
});

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

@ -0,0 +1,169 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
async function background() {
const tabs = await browser.tabs.query({active: true, currentWindow: true});
const sourceTabId = tabs[0].id;
const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
browser.test.sendMessage("webNavOnCreated", msg);
});
browser.webNavigation.onCompleted.addListener(async (msg) => {
// NOTE: checking the url is currently necessary because of Bug 1252129
// ( Filter out webNavigation events related to new window initialization phase).
if (msg.tabId !== sourceTabId && msg.url !== "about:blank") {
await browser.tabs.remove(msg.tabId);
browser.test.sendMessage("webNavOnCompleted", msg);
}
});
browser.tabs.onCreated.addListener((tab) => {
browser.test.sendMessage("tabsOnCreated", tab.id);
});
browser.test.onMessage.addListener(({type, code}) => {
if (type === "execute-contentscript") {
browser.tabs.executeScript(sourceTabId, {code: code});
}
});
browser.test.sendMessage("expectedSourceTab", {
sourceTabId, sourceTabFrames,
});
}
async function runTestCase({extension, openNavTarget, expectedWebNavProps}) {
await openNavTarget();
const webNavMsg = await extension.awaitMessage("webNavOnCreated");
const createdTabId = await extension.awaitMessage("tabsOnCreated");
const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
is(webNavMsg.url, url, "Got the expected url property");
is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
}
add_task(function* test_on_created_navigation_target_from_window_open() {
const tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
gBrowser.selectedTab = tab1;
const extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["webNavigation", "tabs", "<all_urls>"],
},
});
yield extension.startup();
const expectedSourceTab = yield extension.awaitMessage("expectedSourceTab");
info("open an url in a new tab from a window.open call");
yield runTestCase({
extension,
openNavTarget() {
extension.sendMessage({
type: "execute-contentscript",
code: `window.open("${OPENED_PAGE}#new-tab-from-window-open"); true;`,
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: 0,
url: `${OPENED_PAGE}#new-tab-from-window-open`,
},
});
info("open an url in a new window from a window.open call");
yield runTestCase({
extension,
openNavTarget() {
extension.sendMessage({
type: "execute-contentscript",
code: `window.open("${OPENED_PAGE}#new-win-from-window-open", "_blank", "toolbar=0"); true;`,
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: 0,
url: `${OPENED_PAGE}#new-win-from-window-open`,
},
});
yield BrowserTestUtils.removeTab(tab1);
yield extension.unload();
});
add_task(function* test_on_created_navigation_target_from_window_open_subframe() {
const tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
gBrowser.selectedTab = tab1;
const extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["webNavigation", "tabs", "<all_urls>"],
},
});
yield extension.startup();
const expectedSourceTab = yield extension.awaitMessage("expectedSourceTab");
info("open an url in a new tab from subframe window.open call");
yield runTestCase({
extension,
openNavTarget() {
extension.sendMessage({
type: "execute-contentscript",
code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-tab-from-window-open-subframe"); true;`,
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
url: `${OPENED_PAGE}#new-tab-from-window-open-subframe`,
},
});
info("open an url in a new window from subframe window.open call");
yield runTestCase({
extension,
openNavTarget() {
extension.sendMessage({
type: "execute-contentscript",
code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-win-from-window-open-subframe", "_blank", "toolbar=0"); true;`,
});
},
expectedWebNavProps: {
sourceTabId: expectedSourceTab.sourceTabId,
sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
url: `${OPENED_PAGE}#new-win-from-window-open-subframe`,
},
});
yield BrowserTestUtils.removeTab(tab1);
yield extension.unload();
});

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

@ -225,19 +225,19 @@ function closeBrowserAction(extension, win = window) {
return Promise.resolve();
}
function* openContextMenu(selector = "#img1") {
async function openContextMenu(selector = "#img1") {
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
yield BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, gBrowser.selectedBrowser);
yield popupShownPromise;
await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, gBrowser.selectedBrowser);
await popupShownPromise;
return contentAreaContextMenu;
}
function* closeContextMenu() {
async function closeContextMenu() {
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
contentAreaContextMenu.hidePopup();
yield popupHiddenPromise;
await popupHiddenPromise;
}
function* openExtensionContextMenu(selector = "#img1") {

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

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>WebNavigatio onCreatedNavigationTarget target</title>
<meta charset="utf-8">
</head>
<body>
<a id="other-link" href="webNav_createdTarget_source.html">Go back to the source page</a>
</body>
</html>

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

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>WebNavigatio onCreatedNavigationTarget source</title>
<meta charset="utf-8">
</head>
<body>
<ul>
<li>
<a id="test-create-new-tab-from-context-menu"
href="webNav_createdTarget.html#new-tab-from-context-menu">
Open a target page in a new tab from the context menu
</a>
</li>
<li>
<a id="test-create-new-window-from-context-menu"
href="webNav_createdTarget.html#new-window-from-context-menu">
Open a target page in a new window from the context menu
</a>
</li>
<li>
<a id="test-create-new-tab-from-mouse-click"
href="webNav_createdTarget.html#new-tab-from-mouse-click">
Open a target page in a new tab from mouse click
</a>
</li>
<li>
<a id="test-create-new-window-from-mouse-click"
href="webNav_createdTarget.html#new-window-from-mouse-click">
Open a target page in a new window from mouse click
</a>
</li>
</ul>
<iframe src="webNav_createdTargetSource_subframe.html" style="width: 100%; height: 100%;">
</iframe>
</body>
</html>

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

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>WebNavigatio onCreatedNavigationTarget source subframe</title>
<meta charset="utf-8">
</head>
<body>
<ul>
<li>
<a id="test-create-new-tab-from-context-menu-subframe"
href="webNav_createdTarget.html#new-tab-from-context-menu-subframe">
Open a target page in a new tab from the context menu (subframe)
</a>
</li>
<li>
<a id="test-create-new-window-from-context-menu-subframe"
href="webNav_createdTarget.html#new-window-from-context-menu-subframe">
Open a target page in a new window from the context menu (subframe)
</a>
</li>
<li>
<a id="test-create-new-tab-from-mouse-click-subframe"
href="webNav_createdTarget.html#new-tab-from-mouse-click-subframe">
Open a target page in a new tab from mouse click (subframe)
</a>
</li>
<li>
<a id="test-create-new-window-from-mouse-click-subframe"
href="webNav_createdTarget.html#new-window-from-mouse-click-subframe">
Open a target page in a new window from mouse click (subframe)
</a>
</li>
</ul>
</body>
</html>

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

@ -1114,6 +1114,21 @@
this._isHiding = false;
}, 0);
]]></handler>
<!-- This handles clicks on the topmost "Foo Search" header in the
popup (hbox[anonid="searchbar-engine"]). -->
<handler event="click"><![CDATA[
if (event.button == 2) {
// Ignore right clicks.
return;
}
let button = event.originalTarget;
let engine = button.parentNode.engine;
if (!engine) {
return;
}
this.oneOffButtons.handleSearchCommand(event, engine);
]]></handler>
</handlers>
</binding>
@ -2223,7 +2238,7 @@
return; // ignore right clicks.
let button = event.originalTarget;
let engine = button.engine || button.parentNode.engine;
let engine = button.engine;
if (!engine)
return;

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

@ -122,10 +122,19 @@ add_task(function* test_text() {
is(getHeaderText(), "Search for foo with:",
"Header has the correct text when search terms have been entered and the Change Search Settings button is selected.");
promise = promiseEvent(searchPopup, "popuphidden");
info("Closing search panel");
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield promise;
// Click the "Foo Search" header at the top of the popup and make sure it
// loads the search results.
let searchbarEngine =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
"searchbar-engine");
yield synthesizeNativeMouseMove(searchbarEngine);
SimpleTest.executeSoon(() => {
EventUtils.synthesizeMouseAtCenter(searchbarEngine, {});
});
let url = Services.search.currentEngine.getSubmission(textbox.value).uri.spec;
yield promiseTabLoadEvent(gBrowser.selectedTab, url);
// Move the cursor out of the panel area to avoid messing with other tests.
yield synthesizeNativeMouseMove(searchbar);

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

@ -85,6 +85,7 @@ var ContentClick = {
allowMixedContent: json.allowMixedContent,
isContentWindowPrivate: json.isContentWindowPrivate,
originPrincipal: json.originPrincipal,
frameOuterWindowID: json.frameOuterWindowID,
};
// The new tab/window must use the same userContextId.

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

@ -13,9 +13,40 @@
bottom: 0;
width: 100%;
height: 0;
transition: height 0.5s;
/* From javascript side we use animation delay from 0s to -100s to show
* corresponding frames needed for progress.
* animation-delay is set to a positive value to make nothing shown.
*/
animation-play-state: paused;
animation-delay: 1s;
animation-duration: 100s;
animation-timing-function: linear;
animation-name: indicatorArrowProgress;
}
toolbar[brighttext] #downloads-indicator-progress-icon {
background-image: var(--downloads-indicator-image-attention-inverted);
animation-name: indicatorArrowProgressDark;
}
@keyframes indicatorArrowProgress {
0% {
height: 35%;
filter: brightness(1.2);
}
100% {
height: 87%;
filter: brightness(1);
}
}
@keyframes indicatorArrowProgressDark {
0% {
height: 35%;
filter: brightness(0.7);
}
100% {
height: 87%;
filter: brightness(1);
}
}

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

@ -5,9 +5,10 @@
"use strict";
const Services = require("Services");
const FileSaver = require("devtools/client/shared/file-saver");
const JSZip = require("devtools/client/shared/vendor/jszip");
const clipboardHelper = require("devtools/shared/platform/clipboard");
const { HarUtils } = require("./har-utils.js");
const { HarBuilder } = require("./har-builder.js");
const { HarBuilder } = require("./har-builder");
var uid = 1;
@ -61,31 +62,48 @@ const HarExporter = {
* - forceExport {Boolean}: The result HAR file is created even if
* there are no HTTP entries.
*/
save: function (options) {
async save(options) {
// Set default options related to save operation.
options.defaultFileName = Services.prefs.getCharPref(
let defaultFileName = Services.prefs.getCharPref(
"devtools.netmonitor.har.defaultFileName");
options.compress = Services.prefs.getBoolPref(
let compress = Services.prefs.getBoolPref(
"devtools.netmonitor.har.compress");
// Get target file for exported data. Bail out, if the user
// presses cancel.
let file = HarUtils.getTargetFile(options.defaultFileName,
options.jsonp, options.compress);
trace.log("HarExporter.save; " + defaultFileName, options);
if (!file) {
return Promise.resolve();
let data = await this.fetchHarData(options);
let fileName = this.getHarFileName(defaultFileName, options.jsonp, compress);
if (compress) {
data = await JSZip().file(fileName, data).generateAsync({
compression: "DEFLATE",
platform: Services.appinfo.OS === "WINNT" ? "DOS" : "UNIX",
type: "blob",
});
}
trace.log("HarExporter.save; " + options.defaultFileName, options);
fileName = `${fileName}${compress ? ".zip" : ""}`;
let blob = compress ? data : new Blob([data], { type: "application/json" });
return this.fetchHarData(options).then(jsonString => {
if (!HarUtils.saveToFile(file, jsonString, options.compress)) {
let msg = "Failed to save HAR file at: " + options.defaultFileName;
console.error(msg);
}
return jsonString;
});
FileSaver.saveAs(blob, fileName, document);
},
formatDate(date) {
let year = String(date.getFullYear() % 100).padStart(2, "0");
let month = String(date.getMonth() + 1).padStart(2, "0");
let day = String(date.getDate()).padStart(2, "0");
let hour = String(date.getHours()).padStart(2, "0");
let minutes = String(date.getMinutes()).padStart(2, "0");
let seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hour}-${minutes}-${seconds}`;
},
getHarFileName(defaultFileName, jsonp, compress) {
let name = defaultFileName.replace(/%date/g, this.formatDate(new Date()));
name = name.replace(/\:/gm, "-", "");
name = name.replace(/\//gm, "_", "");
return `${name}.${jsonp ? "harp" : "har"}`;
},
/**

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

@ -326,16 +326,14 @@ RequestListContextMenu.prototype = {
* Copy HAR from the network panel content to the clipboard.
*/
copyAllAsHar() {
let options = this.getDefaultHarOptions();
return HarExporter.copy(options);
return HarExporter.copy(this.getDefaultHarOptions());
},
/**
* Save HAR from the network panel content to a file.
*/
saveAllAsHar() {
let options = this.getDefaultHarOptions();
return HarExporter.save(options);
return HarExporter.save(this.getDefaultHarOptions());
},
getDefaultHarOptions() {

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

@ -57,31 +57,13 @@ function ParamsPanel({
// Query String section
if (query) {
object[PARAMS_QUERY_STRING] =
parseQueryString(query)
.reduce((acc, { name, value }) =>
name ? Object.assign(acc, { [name]: value }) : acc
, {});
object[PARAMS_QUERY_STRING] = getProperties(parseQueryString(query));
}
// Form Data section
if (formDataSections && formDataSections.length > 0) {
let sections = formDataSections.filter((str) => /\S/.test(str)).join("&");
object[PARAMS_FORM_DATA] =
parseQueryString(sections)
.reduce((map, obj) => {
let value = map[obj.name];
// Deal with duplicate key case (ex: multiple selection)
if (value) {
if (typeof value !== "object") {
map[obj.name] = [value];
}
map[obj.name].push(obj.value);
} else {
map[obj.name] = obj.value;
}
return map;
}, {});
object[PARAMS_FORM_DATA] = getProperties(parseQueryString(sections));
}
// Request payload section
@ -123,4 +105,28 @@ ParamsPanel.propTypes = {
request: PropTypes.object.isRequired,
};
/**
* Mapping array to dict for TreeView usage.
* Since TreeView only support Object(dict) format.
* This function also deal with duplicate key case
* (for multiple selection and query params with same keys)
*
* @param {Object[]} arr - key-value pair array like query or form params
* @returns {Object} Rep compatible object
*/
function getProperties(arr) {
return arr.reduce((map, obj) => {
let value = map[obj.name];
if (value) {
if (typeof value !== "object") {
map[obj.name] = [value];
}
map[obj.name].push(obj.value);
} else {
map[obj.name] = obj.value;
}
return map;
}, {});
}
module.exports = ParamsPanel;

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

@ -0,0 +1,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env browser */
"use strict";
/**
* HTML5 file saver to provide a standard download interface with a "Save As"
* dialog
*
* @param {object} blob - A blob object will be downloaded
* @param {string} filename - Given a file name which will display in "Save As" dialog
* @param {object} document - Optional. A HTML document for creating a temporary anchor
* for triggering a file download.
*/
function saveAs(blob, filename = "", doc = document) {
let url = URL.createObjectURL(blob);
let a = doc.createElement("a");
doc.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
a.remove();
}
exports.saveAs = saveAs;

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

@ -29,6 +29,7 @@ DevToolsModules(
'DOMHelpers.jsm',
'doorhanger.js',
'enum.js',
'file-saver.js',
'file-watcher-worker.js',
'file-watcher.js',
'getjson.js',
@ -54,8 +55,8 @@ DevToolsModules(
'zoom-keys.js',
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
with Files('components/**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Shared Components')
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
with Files('components/**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Shared Components')

11391
devtools/client/shared/vendor/jszip.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

1
devtools/client/shared/vendor/moz.build поставляемый
Просмотреть файл

@ -7,6 +7,7 @@ modules = []
modules += [
'immutable.js',
'jsol.js',
'jszip.js',
'react-addons-shallow-compare.js',
]

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

@ -1065,9 +1065,6 @@ class Protocol(ipdl.ast.Protocol):
return Type(_actorName(self._ipdlmgrtype().name(), side),
ptr=ptr)
def stateMethod(self):
return ExprVar('state');
def registerMethod(self):
return ExprVar('Register')
@ -2718,12 +2715,6 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
self.cls.addstmts([ meth, refmeth, Whitespace.NL ])
statemethod = MethodDefn(MethodDecl(
p.stateMethod().name,
ret=p.fqStateType()))
statemethod.addstmt(StmtReturn(p.stateVar()))
self.cls.addstmts([ statemethod, Whitespace.NL ])
## OnMessageReceived()/OnCallReceived()
# save these away for use in message handler case stmts

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

@ -753,7 +753,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('m', 'o', 'o', 'f'):
case FOURCC('t', 'r', 'a', 'f'):
case FOURCC('m', 'f', 'r', 'a'):
case FOURCC('u', 'd', 't', 'a'):
case FOURCC('i', 'l', 's', 't'):
case FOURCC('s', 'i', 'n', 'f'):
case FOURCC('s', 'c', 'h', 'i'):

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

@ -4791,6 +4791,8 @@ pref("extensions.webextensions.keepStorageOnUninstall", false);
pref("extensions.webextensions.keepUuidOnUninstall", false);
// Redirect basedomain used by identity api
pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
// Whether or not webextension themes are supported.
pref("extensions.webextensions.themes.enabled", false);
pref("extensions.webextensions.remote", false);
// Report Site Issue button

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

@ -880,10 +880,6 @@ mozilla::PrintfTarget::vprint(const char* fmt, va_list ap)
}
}
// Stuff trailing NUL
if (!emit("\0", 1))
return false;
return true;
}

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

@ -105,7 +105,7 @@ private:
// Used in the implementation of Smprintf et al.
template<typename AllocPolicy>
class MOZ_STACK_CLASS SprintfState final : public mozilla::PrintfTarget, private AllocPolicy
class MOZ_STACK_CLASS SprintfState final : private mozilla::PrintfTarget, private AllocPolicy
{
public:
explicit SprintfState(char* base)
@ -119,6 +119,12 @@ class MOZ_STACK_CLASS SprintfState final : public mozilla::PrintfTarget, private
this->free_(mBase);
}
bool vprint(const char* format, va_list ap_list) {
// The "" here has a single \0 character, which is what we're
// trying to append.
return mozilla::PrintfTarget::vprint(format, ap_list) && append("", 1);
}
char* release() {
char* result = mBase;
mBase = nullptr;

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

@ -1240,11 +1240,11 @@ dependencies = [
[[package]]
name = "immeta"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1676,7 +1676,7 @@ dependencies = [
"flate2 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper_serde 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"immeta 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"immeta 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1692,6 +1692,7 @@ dependencies = [
"serde 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"servo-websocket 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"servo_config 0.0.1",
"servo_url 0.0.1",
"threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1701,7 +1702,6 @@ dependencies = [
"url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
"websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1755,12 +1755,12 @@ dependencies = [
"num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"servo-websocket 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"servo_config 0.0.1",
"servo_url 0.0.1",
"url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
"websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2285,6 +2285,7 @@ dependencies = [
"selectors 0.18.0",
"serde 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"servo-websocket 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"servo_atoms 0.0.1",
"servo_config 0.0.1",
"servo_geometry 0.0.1",
@ -2298,7 +2299,6 @@ dependencies = [
"url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
"websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
"webvr 0.0.1",
"webvr_traits 0.0.1",
"xml5ever 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2530,6 +2530,22 @@ dependencies = [
"x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "servo-websocket"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "servo_atoms"
version = "0.0.1"
@ -3203,22 +3219,6 @@ dependencies = [
"serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "websocket"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "webvr"
version = "0.0.1"
@ -3420,7 +3420,7 @@ dependencies = [
"checksum hyper_serde 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d602a93073c250f49b2e2d931cc1755a5f447824154dc3c711716dee29bd7486"
"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
"checksum image 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "979bad0502082fd60053a490282e87d6c89650942e3a270e0d4c83569c7f5899"
"checksum immeta 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3e76ecb1d64979a91c7fc5b7c0495ef1467e3cbff759044f2b88878a5a845ef7"
"checksum immeta 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0b9260463a221bfe3f02100c56e2d14c050d5ffe7e44a43d0a1b2b1f2b523502"
"checksum inflate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e0062d2dc2f17d2f13750d95316ae8a2ff909af0fda957084f5defd87c43bb"
"checksum io-surface 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10d25285115b9d34be1328fdc5af15d34174472a9f23d1994d2d14a7ec8c537a"
"checksum ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc12beb3f43e226410d7f26a77aec73efbf0c11875a8131adc09f30a8219f22e"
@ -3513,6 +3513,7 @@ dependencies = [
"checksum servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9232032c2e85118c0282c6562c84cab12316e655491ba0a5d1905b2320060d1b"
"checksum servo-glutin 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b682e1eef598db6048b64face7ea79fd55fe70d171cb92d2a44a89db7bdf34"
"checksum servo-skia 0.30000003.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7107296909e71f69a7e8b95becf3efe3e1838e556430b3efc9dc91aea65ddf2"
"checksum servo-websocket 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7445fde9aacb9a1f493652ab02ac0fb7a8bfe1e6cd762f7bd44b839a5d5e4c"
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
"checksum shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fb04126b6fcfd2710fb5b6d18f4207b6c535f2850a7e1a43bcd526d44f30a79a"
"checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
@ -3569,7 +3570,6 @@ dependencies = [
"checksum webdriver 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdc28802daddee94267a657ffeac2593a33881fb7a3a44fedd320b1319efcaf6"
"checksum webrender 0.21.0 (git+https://github.com/servo/webrender)" = "<none>"
"checksum webrender_traits 0.22.0 (git+https://github.com/servo/webrender)" = "<none>"
"checksum websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a1a6ea5ed0367f32eb3d94dcc58859ef4294b5f75ba983dbf56ac314af45d"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04614a58714f3fd4a8b1da4bcae9f031c532d35988c3d39627619248113f8be8"

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

@ -35,13 +35,13 @@ serde_derive = "0.9"
serde_json = "0.9"
servo_config = {path = "../config"}
servo_url = {path = "../url"}
servo-websocket = "0.18"
threadpool = "1.0"
time = "0.1.17"
unicase = "1.4.0"
url = {version = "1.2", features = ["heap_size"]}
uuid = {version = "0.4", features = ["v4"]}
webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
websocket = "0.17"
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
tinyfiledialogs = "2.5.9"

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

@ -25,7 +25,7 @@ serde = "0.9"
serde_derive = "0.9"
servo_config = {path = "../config", features = ["servo"]}
servo_url = {path = "../url", features = ["servo"]}
servo-websocket = "0.18"
url = {version = "1.2", features = ["heap_size"]}
uuid = {version = "0.4", features = ["v4", "serde"]}
webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
websocket = "0.17"

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

@ -80,13 +80,13 @@ servo_config = {path = "../config", features = ["servo"] }
servo_geometry = {path = "../geometry" }
servo_rand = {path = "../rand"}
servo_url = {path = "../url", features = ["servo"] }
servo-websocket = "0.18"
smallvec = "0.1"
style = {path = "../style"}
style_traits = {path = "../style_traits"}
time = "0.1.12"
url = {version = "1.2", features = ["heap_size", "query_encoding"]}
uuid = {version = "0.4", features = ["v4"]}
websocket = "0.17"
xml5ever = {version = "0.4", features = ["unstable"]}
webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
webvr = {path = "../webvr"}

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

@ -21,10 +21,10 @@ rand = [
"phf_generator",
"rayon",
"servo_rand",
"servo-websocket",
"tempdir",
"tempfile",
"uuid",
"websocket",
"ws",
]
num = []

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

@ -16,6 +16,7 @@ config = {
"MOZ_MAKE_COMPLETE_MAR": "1",
'TOOLTOOL_CACHE': '/builds/tooltool_cache',
'TOOLTOOL_HOME': '/builds',
'EN_US_PACKAGE_NAME': 'target.tar.bz2',
},
"ssh_key_dir": "/home/mock_mozilla/.ssh",
"log_name": "single_locale",

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

@ -16,6 +16,7 @@ config = {
"MOZ_MAKE_COMPLETE_MAR": "1",
'TOOLTOOL_CACHE': '/builds/tooltool_cache',
'TOOLTOOL_HOME': '/builds',
'EN_US_PACKAGE_NAME': 'target.tar.bz2',
},
"ssh_key_dir": "/home/mock_mozilla/.ssh",
"log_name": "single_locale",

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

@ -113,20 +113,31 @@ function WebNavigationEventManager(context, eventName) {
let data2 = {
url: data.url,
timeStamp: Date.now(),
frameId: ExtensionManagement.getFrameId(data.windowId),
parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
};
if (eventName == "onErrorOccurred") {
data2.error = data.error;
}
if (data.windowId) {
data2.frameId = ExtensionManagement.getFrameId(data.windowId);
data2.parentFrameId = ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId);
}
if (data.sourceWindowId) {
data2.sourceFrameId = ExtensionManagement.getFrameId(data.sourceWindowId);
}
// Fills in tabId typically.
Object.assign(data2, tabTracker.getBrowserData(data.browser));
if (data2.tabId < 0) {
return;
}
if (data.sourceTabBrowser) {
data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
}
fillTransitionProperties(eventName, data, data2);
fire.async(data2);
@ -166,7 +177,7 @@ extensions.registerSchemaAPI("webNavigation", "addon_parent", context => {
onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
onCreatedNavigationTarget: ignoreEvent(context, "webNavigation.onCreatedNavigationTarget"),
onCreatedNavigationTarget: new WebNavigationEventManager(context, "onCreatedNavigationTarget").api(),
getAllFrames(details) {
let tab = tabManager.get(details.tabId);

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

@ -4,33 +4,34 @@ category webextension-scripts backgroundPage chrome://extensions/content/ext-bac
category webextension-scripts contextualIdentities chrome://extensions/content/ext-contextualIdentities.js
category webextension-scripts cookies chrome://extensions/content/ext-cookies.js
category webextension-scripts downloads chrome://extensions/content/ext-downloads.js
category webextension-scripts extension chrome://extensions/content/ext-extension.js
category webextension-scripts geolocation chrome://extensions/content/ext-geolocation.js
category webextension-scripts management chrome://extensions/content/ext-management.js
category webextension-scripts notifications chrome://extensions/content/ext-notifications.js
category webextension-scripts handlers chrome://extensions/content/ext-protocolHandlers.js
category webextension-scripts i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts idle chrome://extensions/content/ext-idle.js
category webextension-scripts webRequest chrome://extensions/content/ext-webRequest.js
category webextension-scripts webNavigation chrome://extensions/content/ext-webNavigation.js
category webextension-scripts handlers chrome://extensions/content/ext-protocolHandlers.js
category webextension-scripts runtime chrome://extensions/content/ext-runtime.js
category webextension-scripts extension chrome://extensions/content/ext-extension.js
category webextension-scripts storage chrome://extensions/content/ext-storage.js
category webextension-scripts topSites chrome://extensions/content/ext-topSites.js
category webextension-scripts management chrome://extensions/content/ext-management.js
category webextension-scripts notifications chrome://extensions/content/ext-notifications.js
category webextension-scripts privacy chrome://extensions/content/ext-privacy.js
category webextension-scripts runtime chrome://extensions/content/ext-runtime.js
category webextension-scripts storage chrome://extensions/content/ext-storage.js
category webextension-scripts theme chrome://extensions/content/ext-theme.js
category webextension-scripts topSites chrome://extensions/content/ext-topSites.js
category webextension-scripts webNavigation chrome://extensions/content/ext-webNavigation.js
category webextension-scripts webRequest chrome://extensions/content/ext-webRequest.js
# scripts specific for content process.
category webextension-scripts-content extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-content i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts-content runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-content test chrome://extensions/content/ext-c-test.js
category webextension-scripts-content storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-content test chrome://extensions/content/ext-c-test.js
# scripts specific for devtools extension contexts.
category webextension-scripts-devtools extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-devtools i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts-devtools runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-devtools test chrome://extensions/content/ext-c-test.js
category webextension-scripts-devtools storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-devtools test chrome://extensions/content/ext-c-test.js
# scripts that must run in the same process as addon code.
category webextension-scripts-addon backgroundPage chrome://extensions/content/ext-c-backgroundPage.js
@ -40,8 +41,8 @@ category webextension-scripts-addon i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts-addon identity chrome://extensions/content/ext-c-identity.js
#endif
category webextension-scripts-addon runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-addon test chrome://extensions/content/ext-c-test.js
category webextension-scripts-addon storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-addon test chrome://extensions/content/ext-c-test.js
# schemas
category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json
@ -64,6 +65,7 @@ category webextension-schemas privacy chrome://extensions/content/schemas/privac
category webextension-schemas runtime chrome://extensions/content/schemas/runtime.json
category webextension-schemas storage chrome://extensions/content/schemas/storage.json
category webextension-schemas test chrome://extensions/content/schemas/test.json
category webextension-schemas theme chrome://extensions/content/schemas/theme.json
category webextension-schemas top_sites chrome://extensions/content/schemas/top_sites.json
category webextension-schemas types chrome://extensions/content/schemas/types.json
category webextension-schemas web_navigation chrome://extensions/content/schemas/web_navigation.json

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

@ -10,19 +10,22 @@ toolkit.jar:
content/extensions/ext-contextualIdentities.js
content/extensions/ext-cookies.js
content/extensions/ext-downloads.js
content/extensions/ext-extension.js
content/extensions/ext-geolocation.js
content/extensions/ext-management.js
content/extensions/ext-notifications.js
content/extensions/ext-i18n.js
content/extensions/ext-idle.js
content/extensions/ext-webRequest.js
content/extensions/ext-webNavigation.js
content/extensions/ext-management.js
content/extensions/ext-notifications.js
content/extensions/ext-privacy.js
content/extensions/ext-protocolHandlers.js
content/extensions/ext-runtime.js
content/extensions/ext-extension.js
content/extensions/ext-storage.js
content/extensions/ext-theme.js
content/extensions/ext-topSites.js
content/extensions/ext-privacy.js
content/extensions/ext-webRequest.js
content/extensions/ext-webNavigation.js
# Below is a separate group using the naming convention ext-c-*.js that run
# in the child process.
content/extensions/ext-c-backgroundPage.js
content/extensions/ext-c-extension.js
#ifndef ANDROID

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

@ -37,6 +37,10 @@ DIRS += ['schemas']
JAR_MANIFESTS += ['jar.mn']
BROWSER_CHROME_MANIFESTS += [
'test/browser/browser.ini',
]
MOCHITEST_MANIFESTS += [
'test/mochitest/mochitest-remote.ini',
'test/mochitest/mochitest.ini'

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

@ -26,6 +26,7 @@ toolkit.jar:
content/extensions/schemas/runtime.json
content/extensions/schemas/storage.json
content/extensions/schemas/test.json
content/extensions/schemas/theme.json
content/extensions/schemas/top_sites.json
content/extensions/schemas/types.json
content/extensions/schemas/web_navigation.json

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

@ -284,7 +284,6 @@
},
{
"name": "onCreatedNavigationTarget",
"unsupported": true,
"type": "function",
"description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
"parameters": [

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

@ -0,0 +1,18 @@
"use strict";
module.exports = { // eslint-disable-line no-undef
"extends": "../../../../../testing/mochitest/mochitest.eslintrc.js",
"env": {
"webextensions": true,
},
"globals": {
"ExtensionTestUtils": false,
"XPCOMUtils": true,
},
"rules": {
"no-shadow": 0,
},
};

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

@ -0,0 +1,3 @@
[browser_ext_themes_chromeparity.js]
[browser_ext_themes_dynamic_updates.js]
[browser_ext_themes_lwtsupport.js]

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

@ -377,8 +377,9 @@ this.ReaderMode = {
},
_blockedHosts: [
"mail.google.com",
"amazon.com",
"github.com",
"mail.google.com",
"pinterest.com",
"reddit.com",
"twitter.com",

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

@ -20,6 +20,7 @@
#include "nsDocShell.h"
#include "nsGlobalWindow.h"
#include "nsHashPropertyBag.h"
#include "nsIBaseWindow.h"
#include "nsIBrowserDOMWindow.h"
#include "nsIDocShell.h"
@ -1209,6 +1210,29 @@ nsWindowWatcher::OpenWindowInternal(mozIDOMWindowProxy* aParent,
// userContextId.
MOZ_ASSERT(CheckUserContextCompatibility(newDocShell));
// If this tab or window has been opened by a window.open call, we have to provide
// all the data needed to send a webNavigation.onCreatedNavigationTarget event.
if (aCalledFromJS && parentDocShell && newDocShellItem) {
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
if (obsSvc) {
RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
if (uriToLoad) {
// The url notified in the webNavigation.onCreatedNavigationTarget event.
props->SetPropertyAsACString(NS_LITERAL_STRING("url"),
uriToLoad->GetSpecOrDefault());
}
props->SetPropertyAsInterface(NS_LITERAL_STRING("sourceTabDocShell"), parentDocShell);
props->SetPropertyAsInterface(NS_LITERAL_STRING("createdTabDocShell"), newDocShellItem);
obsSvc->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
"webNavigation-createdNavigationTarget-from-js", nullptr);
}
}
if (uriToLoad && aNavigate) {
newDocShell->LoadURI(
uriToLoad,

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

@ -1,19 +1,5 @@
const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_multipleAudio.html";
function* wait_for_tab_playing_event(tab, expectPlaying) {
if (tab.soundPlaying == expectPlaying) {
ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
} else {
yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
if (event.detail.changed.indexOf("soundplaying") >= 0) {
is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
return true;
}
return false;
});
}
}
function play_audio_from_invisible_tab() {
return new Promise(resolve => {
var autoPlay = content.document.getElementById("autoplay");
@ -73,13 +59,13 @@ add_task(function* cross_tabs_audio_competing() {
let tab1 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser,
"about:blank");
tab1.linkedBrowser.loadURI(PAGE);
yield wait_for_tab_playing_event(tab1, true);
yield waitForTabPlayingEvent(tab1, true);
info("- open tab 2 in foreground -");
let tab2 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser,
"about:blank");
tab2.linkedBrowser.loadURI(PAGE);
yield wait_for_tab_playing_event(tab1, false);
yield waitForTabPlayingEvent(tab1, false);
info("- open tab 3 in foreground -");
let tab3 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser,
@ -102,7 +88,7 @@ add_task(function* within_one_tab_audio_competing() {
let tab = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser,
"about:blank");
tab.linkedBrowser.loadURI(PAGE);
yield wait_for_tab_playing_event(tab, true);
yield waitForTabPlayingEvent(tab, true);
info("- play audio2 in the same tab -");
yield ContentTask.spawn(tab.linkedBrowser, null,

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

@ -7,20 +7,6 @@ var SuspendedType = {
SUSPENDED_PAUSE_DISPOSABLE : 3
};
function* wait_for_tab_playing_event(tab, expectPlaying) {
if (tab.soundPlaying == expectPlaying) {
ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
} else {
yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
if (event.detail.changed.indexOf("soundplaying") >= 0) {
is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
return true;
}
return false;
});
}
}
function check_audio_suspended(suspendedType) {
var autoPlay = content.document.getElementById("autoplay");
if (!autoPlay) {
@ -61,7 +47,7 @@ add_task(function* block_autoplay_media() {
yield BrowserTestUtils.switchTab(window.gBrowser, tab1);
info("- media should be unblocked because the tab was visited -");
yield wait_for_tab_playing_event(tab1, true);
yield waitForTabPlayingEvent(tab1, true);
yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.NONE_SUSPENDED,
check_audio_suspended);
@ -69,12 +55,12 @@ add_task(function* block_autoplay_media() {
let tab3 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser,
"about:blank");
info("- should still play media from tab1 -");
yield wait_for_tab_playing_event(tab1, true);
yield waitForTabPlayingEvent(tab1, true);
yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.NONE_SUSPENDED,
check_audio_suspended);
info("- should still block media from tab2 -");
yield wait_for_tab_playing_event(tab2, false);
yield waitForTabPlayingEvent(tab2, false);
yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.SUSPENDED_BLOCK,
check_audio_suspended);

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

@ -64,14 +64,15 @@ add_task(function* block_autoplay_media() {
info("- the tab1 should not be blocked -");
yield waitForTabBlockEvent(tab1, false);
info("- select tab2 as foreground tab, and tab2's media should be playing -");
info("- select tab2 as foreground tab, and the tab2 should not be blocked -");
yield BrowserTestUtils.switchTab(window.gBrowser, tab2);
yield waitForTabBlockEvent(tab2, false);
info("- tab2's media should be playing -");
yield waitForTabPlayingEvent(tab2, true);
yield ContentTask.spawn(tab2.linkedBrowser, false,
check_audio_pause_state);
info("- the tab2 should not be blocked -");
yield waitForTabBlockEvent(tab2, false);
info("- check tab2's media suspend type -");
yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.NONE_SUSPENDED,
check_audio_suspended);

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

@ -7,21 +7,6 @@ var SuspendedType = {
SUSPENDED_PAUSE_DISPOSABLE : 3
};
function* wait_for_tab_playing_event(tab, expectPlaying) {
if (tab.soundPlaying == expectPlaying) {
ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
} else {
info("Playing state doens't match, wait for attributes changes.");
yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
if (event.detail.changed.indexOf("soundplaying") >= 0) {
is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
return true;
}
return false;
});
}
}
function disable_non_test_mouse(disable) {
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
@ -97,7 +82,7 @@ add_task(function* unblock_icon_should_disapear_after_resume_tab() {
yield waitForTabBlockEvent(tab, false);
info("- should not display sound indicator icon -");
yield wait_for_tab_playing_event(tab, false);
yield waitForTabPlayingEvent(tab, false);
info("- remove tab -");
yield BrowserTestUtils.removeTab(tab);
@ -127,7 +112,7 @@ add_task(function* should_not_show_sound_indicator_after_resume_tab() {
yield waitForTabBlockEvent(tab, false);
info("- should not display sound indicator icon -");
yield wait_for_tab_playing_event(tab, false);
yield waitForTabPlayingEvent(tab, false);
info("- remove tab -");
yield BrowserTestUtils.removeTab(tab);

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

@ -4,7 +4,7 @@
<meta content="utf-8" http-equiv="encoding">
</head>
<body>
<audio id="testAudio" src="audio.ogg"></audio>
<audio id="testAudio" src="audio.ogg" loop></audio>
<script type="text/javascript">
var audio = document.getElementById("testAudio");

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

@ -49,3 +49,21 @@ function* waitForTabBlockEvent(tab, expectBlocked) {
});
}
}
/**
* Used to check whether the tab has soundplaying attribute.
*/
function* waitForTabPlayingEvent(tab, expectPlaying) {
if (tab.soundPlaying == expectPlaying) {
ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
} else {
info("Playing state doens't match, wait for attributes changes.");
yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
if (event.detail.changed.indexOf("soundplaying") >= 0) {
is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
return true;
}
return false;
});
}
}

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

@ -21,9 +21,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
// e.g. nsNavHistory::CheckIsRecentEvent, but with a lower threshold value).
const RECENT_DATA_THRESHOLD = 5 * 1000000;
// TODO:
// onCreatedNavigationTarget
var Manager = {
// Map[string -> Map[listener -> URLFilter]]
listeners: new Map(),
@ -32,13 +29,22 @@ var Manager = {
// Collect recent tab transition data in a WeakMap:
// browser -> tabTransitionData
this.recentTabTransitionData = new WeakMap();
// Collect the pending created navigation target events that still have to
// pair the message received from the source tab to the one received from
// the new tab.
this.createdNavigationTargetByOuterWindowId = new Map();
Services.obs.addObserver(this, "autocomplete-did-enter-text", true);
Services.obs.addObserver(this, "webNavigation-createdNavigationTarget", false);
Services.mm.addMessageListener("Content:Click", this);
Services.mm.addMessageListener("Extension:DOMContentLoaded", this);
Services.mm.addMessageListener("Extension:StateChange", this);
Services.mm.addMessageListener("Extension:DocumentChange", this);
Services.mm.addMessageListener("Extension:HistoryChange", this);
Services.mm.addMessageListener("Extension:CreatedNavigationTarget", this);
Services.mm.loadFrameScript("resource://gre/modules/WebNavigationContent.js", true);
},
@ -46,16 +52,20 @@ var Manager = {
uninit() {
// Stop collecting recent tab transition data and reset the WeakMap.
Services.obs.removeObserver(this, "autocomplete-did-enter-text");
this.recentTabTransitionData = new WeakMap();
Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget");
Services.mm.removeMessageListener("Content:Click", this);
Services.mm.removeMessageListener("Extension:StateChange", this);
Services.mm.removeMessageListener("Extension:DocumentChange", this);
Services.mm.removeMessageListener("Extension:HistoryChange", this);
Services.mm.removeMessageListener("Extension:DOMContentLoaded", this);
Services.mm.removeMessageListener("Extension:CreatedNavigationTarget", this);
Services.mm.removeDelayedFrameScript("resource://gre/modules/WebNavigationContent.js");
Services.mm.broadcastAsyncMessage("Extension:DisableWebNavigation");
this.recentTabTransitionData = new WeakMap();
this.createdNavigationTargetByOuterWindowId.clear();
},
addListener(type, listener, filters) {
@ -92,16 +102,33 @@ var Manager = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
/**
* Observe autocomplete-did-enter-text topic to track the user interaction with
* the awesome bar.
* Observe autocomplete-did-enter-text (to track the user interaction with the awesomebar)
* and webNavigation-createdNavigationTarget (to fire the onCreatedNavigationTarget
* related to windows or tabs opened from the main process) topics.
*
* @param {nsIAutoCompleteInput} subject
* @param {nsIAutoCompleteInput|Object} subject
* @param {string} topic
* @param {string} data
* @param {string|undefined} data
*/
observe: function(subject, topic, data) {
if (topic == "autocomplete-did-enter-text") {
this.onURLBarAutoCompletion(subject);
} else if (topic == "webNavigation-createdNavigationTarget") {
// The observed notification is coming from privileged JavaScript components running
// in the main process (e.g. when a new tab or window is opened using the context menu
// or Ctrl/Shift + click on a link).
const {
createdTabBrowser,
url,
sourceFrameOuterWindowID,
sourceTabBrowser,
} = subject.wrappedJSObject;
this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
sourceTabBrowser,
sourceWindowId: sourceFrameOuterWindowID,
url,
});
}
},
@ -260,6 +287,10 @@ var Manager = {
case "Content:Click":
this.onContentClick(target, data);
break;
case "Extension:CreatedNavigationTarget":
this.onCreatedNavigationTarget(target, data);
break;
}
},
@ -274,6 +305,37 @@ var Manager = {
}
},
onCreatedNavigationTarget(browser, data) {
const {isSourceTab, createdWindowId, sourceWindowId, url} = data;
// We are going to potentially received two message manager messages for a single
// onCreatedNavigationTarget event that is happening in the child process,
// we are going to use the generate uuid to pair them together.
const pairedMessage = this.createdNavigationTargetByOuterWindowId.get(createdWindowId);
if (!pairedMessage) {
this.createdNavigationTargetByOuterWindowId.set(createdWindowId, {browser, data});
return;
}
this.createdNavigationTargetByOuterWindowId.delete(createdWindowId);
let sourceTabBrowser;
let createdTabBrowser;
if (isSourceTab) {
sourceTabBrowser = browser;
createdTabBrowser = pairedMessage.browser;
} else {
sourceTabBrowser = pairedMessage.browser;
createdTabBrowser = browser;
}
this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
sourceTabBrowser, sourceWindowId, url,
});
},
onStateChange(browser, data) {
let stateFlags = data.stateFlags;
if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
@ -357,7 +419,7 @@ const EVENTS = [
"onErrorOccurred",
"onReferenceFragmentUpdated",
"onHistoryStateUpdated",
// "onCreatedNavigationTarget",
"onCreatedNavigationTarget",
];
var WebNavigation = {};

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

@ -23,6 +23,55 @@ addMessageListener("Extension:DisableWebNavigation", () => {
removeEventListener("DOMContentLoaded", loadListener);
});
var CreatedNavigationTargetListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
init() {
Services.obs.addObserver(this, "webNavigation-createdNavigationTarget-from-js", false);
},
uninit() {
Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget-from-js");
},
observe(subject, topic, data) {
if (!(subject instanceof Ci.nsIPropertyBag2)) {
return;
}
let props = subject.QueryInterface(Ci.nsIPropertyBag2);
const createdDocShell = props.getPropertyAsInterface("createdTabDocShell", Ci.nsIDocShell);
const sourceDocShell = props.getPropertyAsInterface("sourceTabDocShell", Ci.nsIDocShell);
const isSourceTabDescendant = WebNavigationFrames.isDescendantDocShell(sourceDocShell, docShell);
if (docShell !== createdDocShell && docShell !== sourceDocShell &&
!isSourceTabDescendant) {
// if the createdNavigationTarget is not related to this docShell
// (this docShell is not the newly created docShell, it is not the source docShell,
// and the source docShell is not a descendant of it)
// there is nothing to do here and return early.
return;
}
const isSourceTab = docShell === sourceDocShell || isSourceTabDescendant;
const sourceWindowId = WebNavigationFrames.getDocShellWindowId(sourceDocShell);
const createdWindowId = WebNavigationFrames.getDocShellWindowId(createdDocShell);
let url;
if (props.hasKey("url")) {
url = props.getPropertyAsACString("url");
}
sendAsyncMessage("Extension:CreatedNavigationTarget", {
url,
sourceWindowId,
createdWindowId,
isSourceTab,
});
},
};
var FormSubmitListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsIFormSubmitObserver,
@ -256,11 +305,13 @@ var WebProgressListener = {
var disabled = false;
WebProgressListener.init();
FormSubmitListener.init();
CreatedNavigationTargetListener.init();
addEventListener("unload", () => {
if (!disabled) {
disabled = true;
WebProgressListener.uninit();
FormSubmitListener.uninit();
CreatedNavigationTargetListener.uninit();
}
});
addMessageListener("Extension:DisableWebNavigation", () => {
@ -268,5 +319,6 @@ addMessageListener("Extension:DisableWebNavigation", () => {
disabled = true;
WebProgressListener.uninit();
FormSubmitListener.uninit();
CreatedNavigationTargetListener.uninit();
}
});

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

@ -20,6 +20,17 @@ function getParentWindowId(window) {
return getWindowId(window.parent);
}
function getDocShellWindowId(docShell) {
if (!docShell) {
return undefined;
}
return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
}
/**
* Retrieve the DOMWindow associated to the docShell passed as parameter.
*
@ -118,8 +129,14 @@ function findDocShell(frameId, rootDocShell) {
return null;
}
function isDescendantDocShell(targetDocShell, rootDocShell) {
return (rootDocShell === targetDocShell.sameTypeRootTreeItem
.QueryInterface(Ci.nsIDocShell));
}
var WebNavigationFrames = {
iterateDocShellTree,
isDescendantDocShell,
findDocShell,
@ -139,4 +156,5 @@ var WebNavigationFrames = {
getWindowId,
getParentWindowId,
getDocShellWindowId,
};

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

@ -19,6 +19,7 @@ const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect";
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
const ADDON_TYPE = "theme";
const ADDON_TYPE_WEBEXT = "webextension-theme";
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
@ -354,7 +355,7 @@ this.LightweightThemeManager = {
* restart
*/
addonChanged(aId, aType, aPendingRestart) {
if (aType != ADDON_TYPE)
if (aType != ADDON_TYPE && aType != ADDON_TYPE_WEBEXT)
return;
let id = _getInternalID(aId);

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

@ -951,7 +951,7 @@ Object.defineProperty(this, "isAddonPartOfE10SRollout", {
return true;
}
if (policy.webextensions && aAddon.type == "webextension") {
if (policy.webextensions && (aAddon.type == "webextension" || aAddon.type == "webextension-theme")) {
return true;
}

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

@ -242,8 +242,9 @@ if (!AppConstants.RELEASE_OR_BETA)
// Some add-on types that we track internally are presented as other types
// externally
const TYPE_ALIASES = {
"webextension": "extension",
"apiextension": "extension",
"webextension": "extension",
"webextension-theme": "theme",
};
const CHROME_TYPES = new Set([
@ -253,18 +254,20 @@ const CHROME_TYPES = new Set([
]);
const RESTARTLESS_TYPES = new Set([
"webextension",
"apiextension",
"dictionary",
"experiment",
"locale",
"apiextension",
"webextension",
"webextension-theme",
]);
const SIGNED_TYPES = new Set([
"webextension",
"apiextension",
"extension",
"experiment",
"apiextension",
"webextension",
"webextension-theme",
]);
// This is a random number array that can be used as "salt" when generating
@ -400,6 +403,31 @@ function addonMap(addons) {
return new Map(addons.map(a => [a.id, a]));
}
/**
* Helper function that determines whether an addon of a certain type is a
* WebExtension.
*
* @param {String} type
* @return {Boolean}
*/
function isWebExtension(type) {
return type == "webextension" || type == "webextension-theme";
}
var gThemeAliases = null;
/**
* Helper function that determines whether an addon of a certain type is a
* theme.
*
* @param {String} type
* @return {Boolean}
*/
function isTheme(type) {
if (!gThemeAliases)
gThemeAliases = getAllAliasesForTypes(["theme"]);
return gThemeAliases.includes(type);
}
/**
* Sets permissions on a file
*
@ -736,6 +764,9 @@ function isUsableAddon(aAddon) {
if (mustSign(aAddon.type) && !aAddon.isCorrectlySigned) {
logger.warn(`Add-on ${aAddon.id} is not correctly signed.`);
if (Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false)) {
logger.warn(`Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`);
}
return false;
}
@ -933,6 +964,7 @@ var loadManifestFromWebManifest = Task.async(function*(aUri) {
let extension = new ExtensionData(uri);
let manifest = yield extension.readManifest();
let theme = !!manifest.theme;
// Read the list of available locales, and pre-load messages for
// all locales.
@ -956,7 +988,7 @@ var loadManifestFromWebManifest = Task.async(function*(aUri) {
let addon = new AddonInternal();
addon.id = bss.id;
addon.version = manifest.version;
addon.type = "webextension";
addon.type = "webextension" + (theme ? "-theme" : "");
addon.unpack = false;
addon.strictCompatibility = true;
addon.bootstrap = true;
@ -1033,7 +1065,8 @@ var loadManifestFromWebManifest = Task.async(function*(aUri) {
}];
addon.targetPlatforms = [];
addon.userDisabled = false;
// Themes are disabled by default, except when they're installed from a web page.
addon.userDisabled = theme;
addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
return addon;
@ -1295,7 +1328,7 @@ let loadManifestFromRDF = Task.async(function*(aUri, aStream) {
// A theme's userDisabled value is true if the theme is not the selected skin
// or if there is an active lightweight theme. We ignore whether softblocking
// is in effect since it would change the active theme.
if (addon.type == "theme") {
if (isTheme(addon.type)) {
addon.userDisabled = !!LightweightThemeManager.currentTheme ||
addon.internalName != XPIProvider.selectedSkin;
} else if (addon.type == "experiment") {
@ -4095,6 +4128,10 @@ this.XPIProvider = {
false);
AddonManagerPrivate.callAddonListeners("onInstalled", addon.wrapper);
// Notify providers that a new theme has been enabled.
if (isTheme(addon.type))
AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false);
return addon.wrapper;
}),
@ -4258,7 +4295,7 @@ this.XPIProvider = {
*/
addonChanged(aId, aType, aPendingRestart) {
// We only care about themes in this provider
if (aType != "theme")
if (!isTheme(aType))
return;
if (!aId) {
@ -4271,14 +4308,19 @@ this.XPIProvider = {
// currently selected theme
let previousTheme = null;
let newSkin = this.defaultSkin;
let addons = XPIDatabase.getAddonsByType("theme");
let addons = XPIDatabase.getAddonsByType("theme", "webextension-theme");
for (let theme of addons) {
if (!theme.visible)
return;
if (theme.id == aId)
let isChangedAddon = (theme.id == aId);
if (isWebExtension(theme.type)) {
if (!isChangedAddon)
this.updateAddonDisabledState(theme, true, undefined, aPendingRestart);
} else if (isChangedAddon) {
newSkin = theme.internalName;
else if (theme.userDisabled == false && !theme.pendingUninstall)
} else if (theme.userDisabled == false && !theme.pendingUninstall) {
previousTheme = theme;
}
}
if (aPendingRestart) {
@ -4304,7 +4346,7 @@ this.XPIProvider = {
// Mark the previous theme as disabled. This won't cause recursion since
// only enabled calls notifyAddonChanged.
if (previousTheme)
this.updateAddonDisabledState(previousTheme, true);
this.updateAddonDisabledState(previousTheme, true, undefined, aPendingRestart);
},
/**
@ -4440,8 +4482,9 @@ this.XPIProvider = {
*/
isBlockingE10s(aAddon) {
if (aAddon.type != "extension" &&
aAddon.type != "theme" &&
aAddon.type != "webextension" &&
aAddon.type != "theme")
aAddon.type != "webextension-theme")
return false;
// The hotfix is exempt
@ -4513,7 +4556,13 @@ this.XPIProvider = {
if (aAddon.active)
return false;
if (aAddon.type == "theme") {
if (isTheme(aAddon.type)) {
if (isWebExtension(aAddon.type)) {
// Enabling a WebExtension type theme requires a restart ONLY when the
// theme-to-be-disabled requires a restart.
let theme = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
return !theme || this.disableRequiresRestart(theme);
}
// If dynamic theme switching is enabled then switching themes does not
// require a restart
if (Preferences.get(PREF_EM_DSS_ENABLED))
@ -4728,7 +4777,7 @@ this.XPIProvider = {
let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
if (aType == "dictionary")
uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
else if (aType == "webextension")
else if (isWebExtension(aType))
uri = "resource://gre/modules/addons/WebExtensionBootstrap.js"
else if (aType == "apiextension")
uri = "resource://gre/modules/addons/APIExtensionBootstrap.js"
@ -4942,13 +4991,16 @@ this.XPIProvider = {
* @param aSoftDisabled
* Value for the softDisabled property. If undefined the value will
* not change. If true this will force userDisabled to be true
* @param aPendingRestart
* If the addon is updated whilst the disabled state of another non-
* restartless addon is also set, we need to carry that forward.
* @return a tri-state indicating the action taken for the add-on:
* - undefined: The add-on did not change state
* - true: The add-on because disabled
* - false: The add-on became enabled
* @throws if addon is not a DBAddonInternal
*/
updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled) {
updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled, aPendingRestart = false) {
if (!(aAddon.inDatabase))
throw new Error("Can only update addon states for installed addons.");
if (aUserDisabled !== undefined && aSoftDisabled !== undefined) {
@ -5010,7 +5062,7 @@ this.XPIProvider = {
AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
} else {
if (isDisabled) {
var needsRestart = this.disableRequiresRestart(aAddon);
var needsRestart = aPendingRestart || this.disableRequiresRestart(aAddon);
AddonManagerPrivate.callAddonListeners("onDisabling", wrapper,
needsRestart);
} else {
@ -5067,7 +5119,7 @@ this.XPIProvider = {
}
// Notify any other providers that a new theme has been enabled
if (aAddon.type == "theme" && !isDisabled)
if (isTheme(aAddon.type) && !isDisabled)
AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, needsRestart);
return isDisabled;
@ -5215,7 +5267,7 @@ this.XPIProvider = {
}
// Notify any other providers that a new theme has been enabled
if (aAddon.type == "theme" && aAddon.active)
if (isTheme(aAddon.type) && aAddon.active)
AddonManagerPrivate.notifyAddonChanged(null, aAddon.type, requiresRestart);
},
@ -5254,7 +5306,7 @@ this.XPIProvider = {
}
// Notify any other providers that this theme is now enabled again.
if (aAddon.type == "theme" && aAddon.active)
if (isTheme(aAddon.type) && aAddon.active)
AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
}
};
@ -5528,7 +5580,7 @@ class AddonInstall {
`Refusing to upgrade addon ${this.existingAddon.id} to different ID ${this.addon.id}`]);
}
if (this.existingAddon.type == "webextension" && this.addon.type != "webextension") {
if (isWebExtension(this.existingAddon.type) && !isWebExtension(this.addon.type)) {
zipreader.close();
return Promise.reject([AddonManager.ERROR_UNEXPECTED_ADDON_TYPE,
"WebExtensions may not be upated to other extension types"]);
@ -5820,6 +5872,10 @@ class AddonInstall {
}
XPIProvider.setTelemetry(this.addon.id, "unpacked", installedUnpacked);
recordAddonTelemetry(this.addon);
// Notify providers that a new theme has been enabled.
if (isTheme(this.addon.type) && this.addon.active)
AddonManagerPrivate.notifyAddonChanged(this.addon.id, this.addon.type, requiresRestart);
}
}).bind(this)).then(null, (e) => {
logger.warn(`Failed to install ${this.file.path} from ${this.sourceURI.spec} to ${stagedAddon.path}`, e);
@ -7211,7 +7267,7 @@ AddonWrapper.prototype = {
},
get isWebExtension() {
return addonFor(this).type == "webextension";
return isWebExtension(addonFor(this).type);
},
get temporarilyInstalled() {
@ -7335,7 +7391,7 @@ AddonWrapper.prototype = {
return repositoryScreenshots;
}
if (addon.type == "theme" && this.hasResource("preview.png")) {
if (isTheme(addon.type) && this.hasResource("preview.png")) {
let url = this.getResourceURI("preview.png").spec;
return [new AddonManagerPrivate.AddonScreenshot(url)];
}
@ -7475,11 +7531,13 @@ AddonWrapper.prototype = {
}
if (addon.inDatabase) {
if (addon.type == "theme" && val) {
let theme = isTheme(addon.type)
if (theme && val) {
if (addon.internalName == XPIProvider.defaultSkin)
throw new Error("Cannot disable the default theme");
XPIProvider.enableDefaultTheme();
} else {
}
if (!(theme && val) || isWebExtension(addon.type)) {
// hidden and system add-ons should not be user disasbled,
// as there is no UI to re-enable them.
if (this.hidden) {
@ -7504,10 +7562,12 @@ AddonWrapper.prototype = {
if (addon.inDatabase) {
// When softDisabling a theme just enable the active theme
if (addon.type == "theme" && val && !addon.userDisabled) {
if (isTheme(addon.type) && val && !addon.userDisabled) {
if (addon.internalName == XPIProvider.defaultSkin)
throw new Error("Cannot disable the default theme");
XPIProvider.enableDefaultTheme();
if (isWebExtension(addon.type))
XPIProvider.updateAddonDisabledState(addon, undefined, val);
} else {
XPIProvider.updateAddonDisabledState(addon, undefined, val);
}

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

@ -1133,22 +1133,24 @@ this.XPIDatabase = {
},
/**
* Synchronously gets all add-ons of a particular type.
* Synchronously gets all add-ons of a particular type(s).
*
* @param aType
* The type of add-on to retrieve
* @param aType, aType2, ...
* The type(s) of add-on to retrieve
* @return an array of DBAddonInternals
*/
getAddonsByType(aType) {
getAddonsByType(...aTypes) {
if (!this.addonDB) {
// jank-tastic! Must synchronously load DB if the theme switches from
// an XPI theme to a lightweight theme before the DB has loaded,
// because we're called from sync XPIProvider.addonChanged
logger.warn("Synchronous load of XPI database due to getAddonsByType(" + aType + ")");
logger.warn("Synchronous load of XPI database due to getAddonsByType([" +
aTypes.join(", ") + "])");
AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_byType", XPIProvider.runPhase);
this.syncLoadDB(true);
}
return _filterDB(this.addonDB, aAddon => (aAddon.type == aType));
return _filterDB(this.addonDB, aAddon => aTypes.includes(aAddon.type));
},
/**

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

@ -1054,6 +1054,30 @@ const EXTENSIONS_DB = "extensions.json";
var gExtensionsJSON = gProfD.clone();
gExtensionsJSON.append(EXTENSIONS_DB);
function promiseWebExtensionStartup() {
const {Management} = Components.utils.import("resource://gre/modules/Extension.jsm", {});
return new Promise(resolve => {
let listener = (evt, extension) => {
Management.off("ready", listener);
resolve(extension);
};
Management.on("ready", listener);
});
}
function promiseInstallWebExtension(aData) {
let addonFile = createTempWebExtensionFile(aData);
return promiseInstallAllFiles([addonFile]).then(installs => {
Services.obs.notifyObservers(addonFile, "flush-cache-entry", null);
// Since themes are disabled by default, it won't start up.
if ("theme" in aData.manifest)
return installs[0].addon;
return promiseWebExtensionStartup();
});
}
// By default use strict compatibility
Services.prefs.setBoolPref("extensions.strictCompatibility", true);

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

@ -384,8 +384,7 @@ function run_test_5() {
"onEnabling"
],
"theme2@tests.mozilla.org": [
["onDisabling", false],
"onDisabled"
"onDisabling"
]
});
@ -395,11 +394,10 @@ function run_test_5() {
prepare_test({
"2@personas.mozilla.org": [
["onOperationCancelled", true]
"onOperationCancelled"
],
"theme2@tests.mozilla.org": [
["onEnabling", false],
"onEnabled"
"onOperationCancelled"
]
});
@ -446,8 +444,7 @@ function run_test_6() {
"onEnabling",
],
"theme2@tests.mozilla.org": [
["onDisabling", false],
"onDisabled"
"onDisabling"
]
});
@ -462,8 +459,7 @@ function run_test_6() {
"onOperationCancelled",
],
"theme2@tests.mozilla.org": [
["onEnabling", false],
"onEnabled"
"onOperationCancelled"
]
});
@ -476,8 +472,7 @@ function run_test_6() {
"onEnabling",
],
"theme2@tests.mozilla.org": [
["onDisabling", false],
"onDisabled"
"onDisabling"
]
});
@ -488,9 +483,9 @@ function run_test_6() {
do_check_false(p2.isActive);
do_check_false(p2.userDisabled);
do_check_true(hasFlag(AddonManager.PENDING_ENABLE, p2.pendingOperations));
do_check_false(t2.isActive);
do_check_true(t2.isActive);
do_check_true(t2.userDisabled);
do_check_false(hasFlag(AddonManager.PENDING_DISABLE, t2.pendingOperations));
do_check_true(hasFlag(AddonManager.PENDING_DISABLE, t2.pendingOperations));
do_check_false(gLWThemeChanged);
do_execute_soon(check_test_6);

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

@ -307,8 +307,40 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_true(addon.isWebExtension);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
// test reloading a webextension with the same name, but a different type.
webext.remove(false);
webext = createTempWebExtensionFile({
manifest: {
version: "6.0",
name: "Test WebExtension 1 (temporary)",
applications: {
gecko: {
id: ID
}
},
theme: { images: { headerURL: "https://example.com/example.png" } }
}
});
yield Promise.all([
AddonManager.installTemporaryAddon(webext),
promiseAddonStartup(),
]);
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_eq(addon.version, "6.0");
do_check_eq(addon.name, "Test WebExtension 1 (temporary)");
do_check_true(addon.isCompatible);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
// This is what we're really interested in:
do_check_eq(addon.type, "theme");
do_check_true(addon.isWebExtension);
restartManager();
BootstrapMonitor.checkAddonInstalled(ID, "1.0");

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

@ -351,6 +351,73 @@ add_task(function* canUndoUninstallDisabled() {
yield promiseRestartManager();
});
add_task(function* uninstallWebExtensionOffersUndo() {
let { id: addonId } = yield promiseInstallWebExtension({
manifest: {
"author": "Some author",
manifest_version: 2,
name: "Web Extension Name",
version: "1.0",
theme: { images: { headerURL: "https://example.com/example.png" } },
}
});
let [ t1, d ] = yield promiseAddonsByIDs([addonId, "default@tests.mozilla.org"]);
Assert.ok(t1, "Addon should be there");
Assert.ok(!t1.isActive);
Assert.ok(t1.userDisabled);
Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE);
Assert.ok(d, "Addon should be there");
Assert.ok(d.isActive);
Assert.ok(!d.userDisabled);
Assert.equal(d.pendingOperations, AddonManager.PENDING_NONE);
Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
prepare_test({ [addonId]: [ "onUninstalling" ] });
t1.uninstall(true);
ensure_test_completed();
Assert.ok(!t1.isActive);
Assert.ok(t1.userDisabled);
Assert.ok(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL));
Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
prepare_test({
[addonId]: [
"onOperationCancelled"
]
});
t1.cancelUninstall();
ensure_test_completed();
Assert.ok(!t1.isActive);
Assert.ok(t1.userDisabled);
Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE);
yield promiseRestartManager();
[ t1, d ] = yield promiseAddonsByIDs([addonId, "default@tests.mozilla.org"]);
Assert.ok(d);
Assert.ok(d.isActive);
Assert.ok(!d.userDisabled);
Assert.equal(d.pendingOperations, AddonManager.PENDING_NONE);
Assert.ok(t1);
Assert.ok(!t1.isActive);
Assert.ok(t1.userDisabled);
Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE);
Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0");
t1.uninstall();
yield promiseRestartManager();
});
// Tests that uninstalling an enabled lightweight theme offers the option to undo
add_task(function* uninstallLWTOffersUndo() {
// skipped since lightweight themes don't support undoable uninstall yet

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

@ -14,34 +14,14 @@ profileDir.append("extensions");
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
startupManager();
const { GlobalManager, Management } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
function promiseAddonStartup() {
return new Promise(resolve => {
let listener = (evt, extension) => {
Management.off("ready", listener);
resolve(extension);
};
Management.on("ready", listener);
});
}
function promiseInstallWebExtension(aData) {
let addonFile = createTempWebExtensionFile(aData);
return promiseInstallAllFiles([addonFile]).then(() => {
Services.obs.notifyObservers(addonFile, "flush-cache-entry", null);
return promiseAddonStartup();
});
}
const { GlobalManager } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
add_task(function*() {
equal(GlobalManager.extensionMap.size, 0);
yield Promise.all([
promiseInstallAllFiles([do_get_addon("webextension_1")], true),
promiseAddonStartup()
promiseWebExtensionStartup()
]);
equal(GlobalManager.extensionMap.size, 1);
@ -79,7 +59,7 @@ add_task(function*() {
equal(GlobalManager.extensionMap.size, 0);
startupManager();
yield promiseAddonStartup();
yield promiseWebExtensionStartup();
equal(GlobalManager.extensionMap.size, 1);
ok(GlobalManager.extensionMap.has(ID));
@ -108,7 +88,7 @@ add_task(function*() {
equal(GlobalManager.extensionMap.size, 0);
addon.userDisabled = false;
yield promiseAddonStartup();
yield promiseWebExtensionStartup();
equal(GlobalManager.extensionMap.size, 1);
ok(GlobalManager.extensionMap.has(ID));
@ -135,7 +115,7 @@ add_task(function*() {
}, profileDir);
startupManager();
yield promiseAddonStartup();
yield promiseWebExtensionStartup();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
@ -160,7 +140,7 @@ add_task(function* test_manifest_localization() {
const extensionId = "webextension3@tests.mozilla.org";
yield promiseInstallAllFiles([do_get_addon("webextension_3")], true);
yield promiseAddonStartup();
yield promiseWebExtensionStartup();
let addon = yield promiseAddonByID(extensionId);
addon.userDisabled = true;
@ -418,3 +398,48 @@ add_task(function* authorNotString() {
addon.uninstall();
}
});
add_task(function* testThemeExtension() {
let addon = yield promiseInstallWebExtension({
manifest: {
"author": "Some author",
manifest_version: 2,
name: "Web Extension Name",
version: "1.0",
theme: { images: { headerURL: "https://example.com/example.png" } },
}
});
addon = yield promiseAddonByID(addon.id);
do_check_neq(addon, null);
do_check_eq(addon.creator, "Some author");
do_check_eq(addon.version, "1.0");
do_check_eq(addon.name, "Web Extension Name");
do_check_true(addon.isCompatible);
do_check_false(addon.appDisabled);
do_check_false(addon.isActive);
do_check_true(addon.userDisabled);
do_check_false(addon.isSystem);
do_check_eq(addon.type, "theme");
do_check_true(addon.isWebExtension);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
addon.uninstall();
// Also test one without a proper 'theme' section.
addon = yield promiseInstallWebExtension({
manifest: {
"author": "Some author",
manifest_version: 2,
name: "Web Extension Name",
version: "1.0",
theme: null,
}
});
addon = yield promiseAddonByID(addon.id);
do_check_eq(addon.type, "extension");
do_check_true(addon.isWebExtension);
addon.uninstall();
});

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

@ -24,27 +24,12 @@ function promiseAddonStartup() {
});
}
// Test simple icon set parsing
add_task(function*() {
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
},
icons: {
16: "icon16.png",
32: "icon32.png",
48: "icon48.png",
64: "icon64.png"
}
}, profileDir);
function* testSimpleIconsetParsing(manifest) {
yield promiseWriteWebManifestForExtension(manifest, profileDir);
yield promiseRestartManager();
yield promiseAddonStartup();
if (!manifest.theme)
yield promiseAddonStartup();
let uri = do_get_addon_root_uri(profileDir, ID);
@ -76,7 +61,8 @@ add_task(function*() {
// check if icons are persisted through a restart
yield promiseRestartManager();
yield promiseAddonStartup();
if (!manifest.theme)
yield promiseAddonStartup();
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
@ -86,30 +72,14 @@ add_task(function*() {
addon.uninstall();
yield promiseRestartManager();
});
}
// Test AddonManager.getPreferredIconURL for retina screen sizes
add_task(function*() {
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
},
icons: {
32: "icon32.png",
48: "icon48.png",
64: "icon64.png",
128: "icon128.png",
256: "icon256.png"
}
}, profileDir);
function* testRetinaIconsetParsing(manifest) {
yield promiseWriteWebManifestForExtension(manifest, profileDir);
yield promiseRestartManager();
yield promiseAddonStartup();
if (!manifest.theme)
yield promiseAddonStartup();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
@ -132,23 +102,14 @@ add_task(function*() {
addon.uninstall();
yield promiseRestartManager();
});
}
// Handles no icons gracefully
add_task(function*() {
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
}
}, profileDir);
function* testNoIconsParsing(manifest) {
yield promiseWriteWebManifestForExtension(manifest, profileDir);
yield promiseRestartManager();
yield promiseAddonStartup();
if (!manifest.theme)
yield promiseAddonStartup();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
@ -163,4 +124,109 @@ add_task(function*() {
addon.uninstall();
yield promiseRestartManager();
}
// Test simple icon set parsing
add_task(function*() {
yield* testSimpleIconsetParsing({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
},
icons: {
16: "icon16.png",
32: "icon32.png",
48: "icon48.png",
64: "icon64.png"
}
});
// Now for theme-type extensions too.
yield* testSimpleIconsetParsing({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
},
icons: {
16: "icon16.png",
32: "icon32.png",
48: "icon48.png",
64: "icon64.png"
},
theme: { images: { headerURL: "https://example.com/example.png" } }
});
});
// Test AddonManager.getPreferredIconURL for retina screen sizes
add_task(function*() {
yield* testRetinaIconsetParsing({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
},
icons: {
32: "icon32.png",
48: "icon48.png",
64: "icon64.png",
128: "icon128.png",
256: "icon256.png"
}
});
yield* testRetinaIconsetParsing({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
},
icons: {
32: "icon32.png",
48: "icon48.png",
64: "icon64.png",
128: "icon128.png",
256: "icon256.png"
},
theme: { images: { headerURL: "https://example.com/example.png" } }
});
});
// Handles no icons gracefully
add_task(function*() {
yield* testNoIconsParsing({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
}
});
yield* testNoIconsParsing({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
},
theme: { images: { headerURL: "https://example.com/example.png" } }
});
});

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

@ -0,0 +1,236 @@
"use strict";
/**
* This file contains test for 'theme' type WebExtension addons. Tests focus mostly
* on interoperability between the different theme formats (XUL and LWT) and
* Addon Manager integration.
*
* Coverage may overlap with other tests in this folder.
*/
const {LightweightThemeManager} = AM_Cu.import("resource://gre/modules/LightweightThemeManager.jsm", {});
const THEME_IDS = ["theme1@tests.mozilla.org", "theme3@tests.mozilla.org",
"theme2@personas.mozilla.org", "default@tests.mozilla.org"];
const REQUIRE_RESTART = { [THEME_IDS[0]]: 1 };
const DEFAULT_THEME = THEME_IDS[3];
const profileDir = gProfD.clone();
profileDir.append("extensions");
// We remember the last/ currently active theme for tracking events.
var gActiveTheme = null;
add_task(function* setup_to_default_browserish_state() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
writeInstallRDFForExtension({
id: THEME_IDS[0],
version: "1.0",
name: "Test 1",
type: 4,
skinnable: true,
internalName: "theme1/1.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "2"
}]
}, profileDir);
yield promiseWriteWebManifestForExtension({
author: "Some author",
manifest_version: 2,
name: "Web Extension Name",
version: "1.0",
theme: { images: { headerURL: "https://example.com/example.png" } },
applications: {
gecko: {
id: THEME_IDS[1]
}
}
}, profileDir);
// We need a default theme for some of these things to work but we have hidden
// the one in the application directory.
writeInstallRDFForExtension({
id: DEFAULT_THEME,
version: "1.0",
name: "Default",
internalName: "classic/1.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "2"
}]
}, profileDir);
startupManager();
// We can add an LWT only after the Addon Manager was started.
LightweightThemeManager.currentTheme = {
id: THEME_IDS[2].substr(0, THEME_IDS[2].indexOf("@")),
version: "1",
name: "Bling",
description: "SO MUCH BLING!",
author: "Pixel Pusher",
homepageURL: "http://mochi.test:8888/data/index.html",
headerURL: "http://mochi.test:8888/data/header.png",
previewURL: "http://mochi.test:8888/data/preview.png",
iconURL: "http://mochi.test:8888/data/icon.png",
textcolor: Math.random().toString(),
accentcolor: Math.random().toString()
};
let [ t1, t2, t3, d ] = yield promiseAddonsByIDs(THEME_IDS);
Assert.ok(t1, "Theme addon should exist");
Assert.ok(t2, "Theme addon should exist");
Assert.ok(t3, "Theme addon should exist");
Assert.ok(d, "Theme addon should exist");
t1.userDisabled = t2.userDisabled = t3.userDisabled = true;
Assert.ok(!t1.isActive, "Theme should be disabled");
Assert.ok(!t2.isActive, "Theme should be disabled");
Assert.ok(!t3.isActive, "Theme should be disabled");
Assert.ok(d.isActive, "Default theme should be active");
yield promiseRestartManager();
[ t1, t2, t3, d ] = yield promiseAddonsByIDs(THEME_IDS);
Assert.ok(!t1.isActive, "Theme should still be disabled");
Assert.ok(!t2.isActive, "Theme should still be disabled");
Assert.ok(!t3.isActive, "Theme should still be disabled");
Assert.ok(d.isActive, "Default theme should still be active");
gActiveTheme = d.id;
});
/**
* Set the `userDisabled` property of one specific theme and check if the theme
* switching works as expected by checking the state of all installed themes.
*
* @param {String} which ID of the addon to set the `userDisabled` property on
* @param {Boolean} disabled Flag value to switch to
*/
function* setDisabledStateAndCheck(which, disabled = false) {
if (disabled)
Assert.equal(which, gActiveTheme, "Only the active theme can be disabled");
let themeToDisable = disabled ? which : gActiveTheme;
let themeToEnable = disabled ? DEFAULT_THEME : which;
let expectRestart = !!(REQUIRE_RESTART[themeToDisable] || REQUIRE_RESTART[themeToEnable]);
let expectedStates = {
[themeToDisable]: true,
[themeToEnable]: false
};
let expectedEvents = {
[themeToDisable]: [ [ "onDisabling", expectRestart ] ],
[themeToEnable]: [ [ "onEnabling", expectRestart ] ]
};
if (!expectRestart) {
expectedEvents[themeToDisable].push([ "onDisabled", false ]);
expectedEvents[themeToEnable].push([ "onEnabled", false ]);
}
// Set the state of the theme to change.
let theme = yield promiseAddonByID(which);
prepare_test(expectedEvents);
theme.userDisabled = disabled;
let isDisabled;
for (theme of yield promiseAddonsByIDs(THEME_IDS)) {
isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
Assert.equal(theme.userDisabled, isDisabled,
`Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
// Some themes need a restart to get their act together.
if (expectRestart && (theme.id == themeToEnable || theme.id == themeToDisable)) {
let expectedFlag = theme.id == themeToEnable ? AddonManager.PENDING_ENABLE : AddonManager.PENDING_DISABLE;
Assert.ok(hasFlag(theme.pendingOperations, expectedFlag),
"When expecting a restart, the pending operation flags should match");
} else {
Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE,
"There should be no pending operations when no restart is expected");
Assert.equal(theme.isActive, !isDisabled,
`Theme '${theme.id} should be ${isDisabled ? "in" : ""}active`);
}
}
yield promiseRestartManager();
// All should still be good after a restart of the Addon Manager.
for (theme of yield promiseAddonsByIDs(THEME_IDS)) {
isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
Assert.equal(theme.userDisabled, isDisabled,
`Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
Assert.equal(theme.isActive, !isDisabled,
`Theme '${theme.id}' should be ${isDisabled ? "in" : ""}active`);
Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE,
"There should be no pending operations left");
if (!isDisabled)
gActiveTheme = theme.id;
}
ensure_test_completed();
}
add_task(function* test_dss_themes() {
// Enable the complete theme.
yield* setDisabledStateAndCheck(THEME_IDS[0]);
// Disabling the complete theme should revert to the default theme.
yield* setDisabledStateAndCheck(THEME_IDS[0], true);
// Enable it again.
yield* setDisabledStateAndCheck(THEME_IDS[0]);
// Enabling a WebExtension theme should disable the active theme.
yield* setDisabledStateAndCheck(THEME_IDS[1]);
// Switching back should disable the WebExtension theme.
yield* setDisabledStateAndCheck(THEME_IDS[0]);
});
add_task(function* test_WebExtension_themes() {
// Enable the WebExtension theme.
yield* setDisabledStateAndCheck(THEME_IDS[1]);
// Disabling WebExtension should revert to the default theme.
yield* setDisabledStateAndCheck(THEME_IDS[1], true);
// Enable it again.
yield* setDisabledStateAndCheck(THEME_IDS[1]);
// Enabling an LWT should disable the active theme.
yield* setDisabledStateAndCheck(THEME_IDS[2]);
// Switching back should disable the LWT.
yield* setDisabledStateAndCheck(THEME_IDS[1]);
});
add_task(function* test_LWTs() {
// Start with enabling an LWT.
yield* setDisabledStateAndCheck(THEME_IDS[2]);
// Disabling LWT should revert to the default theme.
yield* setDisabledStateAndCheck(THEME_IDS[2], true);
// Enable it again.
yield* setDisabledStateAndCheck(THEME_IDS[2]);
// Enabling a WebExtension theme should disable the active theme.
yield* setDisabledStateAndCheck(THEME_IDS[1]);
// Switching back should disable the LWT.
yield* setDisabledStateAndCheck(THEME_IDS[2]);
});
add_task(function* test_default_theme() {
// Explicitly enable the default theme.
yield* setDisabledStateAndCheck(DEFAULT_THEME);
// Swith to the WebExtension theme.
yield* setDisabledStateAndCheck(THEME_IDS[1]);
// Enable it again.
yield* setDisabledStateAndCheck(DEFAULT_THEME);
});

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

@ -8,5 +8,7 @@ tags = addons
[test_webextension_paths.js]
tags = webextensions
[test_webextension_theme.js]
tags = webextensions
[include:xpcshell-shared.ini]

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

@ -47,7 +47,6 @@ toolbaritem > menubar {
toolbarseparator {
-moz-appearance: separator;
margin : 0;
border: 0;
min-width: 2px;
}

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

@ -12,9 +12,6 @@
toolbox {
-moz-appearance: toolbox;
background-color: -moz-Dialog;
border-top: 2px solid;
-moz-border-top-colors: ThreeDShadow ThreeDHighlight;
}
/* ::::: toolbar & menubar ::::: */
@ -26,14 +23,10 @@ toolbar, menubar {
toolbar {
min-width: 1px;
min-height: 19px;
border-top: 1px solid ThreeDHighlight;
border-bottom: 1px solid ThreeDShadow;
}
toolbar:first-child, menubar {
min-width: 1px;
border-bottom: 1px solid ThreeDShadow;
border-top: 0px !important;
}
/* ::::: lightweight theme ::::: */
@ -42,20 +35,12 @@ menubar:-moz-lwtheme,
toolbox:-moz-lwtheme,
toolbar:-moz-lwtheme {
-moz-appearance: none;
background: none;
border-color: transparent;
}
/* ::::: toolbar decorations ::::: */
toolbarseparator {
-moz-appearance: separator;
border-top: 2px solid transparent;
border-bottom: 2px solid transparent;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
-moz-border-left-colors : transparent transparent ThreeDShadow;
-moz-border-right-colors : transparent transparent ThreeDHighlight;
}
toolbarspacer {

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

@ -3131,6 +3131,23 @@ NativeKey::GetFollowingCharMessage(MSG& aCharMsg)
return true;
}
// When found message's wParam is 0 and its scancode is 0xFF, we may remove
// usual char message actually. In such case, we should use the removed
// char message.
if (IsCharMessage(removedMsg) && !nextKeyMsg.wParam &&
WinUtils::GetScanCode(nextKeyMsg.lParam) == 0xFF) {
aCharMsg = removedMsg;
MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
"remove a char message, but the removed message was changed from "
"the found message but the found message was odd, so, ignoring the "
"odd found message and respecting the removed message, aCharMsg=%s, "
"nextKeyMsg=%s, kFoundCharMsg=%s",
this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(),
ToString(kFoundCharMsg).get()));
return true;
}
// NOTE: Although, we don't know when this case occurs, the scan code value
// in lParam may be changed from 0 to something. The changed value
// is different from the scan code of handling keydown message.

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

@ -265,7 +265,7 @@ GetAssertBehavior()
return gAssertBehavior;
}
struct FixedBuffer : public mozilla::PrintfTarget
struct FixedBuffer final : public mozilla::PrintfTarget
{
FixedBuffer() : curlen(0)
{
@ -285,11 +285,6 @@ FixedBuffer::append(const char* aBuf, size_t aLen)
return true;
}
// strip the trailing null, we add it again later
if (aBuf[aLen - 1] == '\0') {
--aLen;
}
if (curlen + aLen >= sizeof(buffer)) {
aLen = sizeof(buffer) - curlen - 1;
}

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

@ -931,11 +931,6 @@ struct MOZ_STACK_CLASS PrintfAppend_CharT : public mozilla::PrintfTarget
return true;
}
// Printf sends us the final null terminator even though we don't want it
if (aStr[aLen - 1] == '\0') {
--aLen;
}
mString->AppendASCII(aStr, aLen);
return true;
}