Merge central to inbound. a=merge

This commit is contained in:
Cosmin Sabou 2018-06-21 04:19:13 +03:00
Родитель 9879c8e5a2 4c18cd4036
Коммит c913a59748
248 изменённых файлов: 3177 добавлений и 1907 удалений

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

@ -215,7 +215,7 @@ var StarUI = {
},
_bookmarkPopupInitialized: false,
async showEditBookmarkPopup(aNode, aAnchorElement, aPosition, aIsNewBookmark, aUrl) {
async showEditBookmarkPopup(aNode, aAnchorElement, aPosition, aIsNewBookmark, aUrl, aIsCurrentBrowser = true) {
// Slow double-clicks (not true double-clicks) shouldn't
// cause the panel to flicker.
if (this.panel.state == "showing" ||
@ -227,15 +227,15 @@ var StarUI = {
this._itemGuids = null;
if (this._bookmarkPopupInitialized) {
await this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl);
await this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl, aIsCurrentBrowser);
return;
}
this._bookmarkPopupInitialized = true;
await this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl);
await this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl, aIsCurrentBrowser);
},
async _doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl) {
async _doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl, aIsCurrentBrowser) {
if (this.panel.state != "closed")
return;
@ -271,6 +271,8 @@ var StarUI = {
gNavigatorBundle.getString("editBookmark.removeBookmarks.accesskey"));
}
this._setIconAndPreviewImage(aIsCurrentBrowser);
this.beginBatch();
if (aAnchorElement && aAnchorElement.closest("#urlbar")) {
@ -290,16 +292,40 @@ var StarUI = {
}
target.addEventListener("popupshown", function(event) {
fn();
}, {"capture": true, "once": true});
}, {capture: true, once: true});
};
gEditItemOverlay.initPanel({ node: aNode,
onPanelReady,
hiddenRows: ["location",
"loadInSidebar", "keyword"],
focusedElement: "preferred"});
this.panel.openPopup(aAnchorElement, aPosition);
},
_setIconAndPreviewImage(aIsCurrentBrowser) {
let faviconImage = this._element("editBookmarkPanelFavicon");
faviconImage.removeAttribute("iconloadingprincipal");
faviconImage.removeAttribute("src");
document.mozSetImageElement("editBookmarkPanelImageCanvas", null);
if (!aIsCurrentBrowser) {
return;
}
let tab = gBrowser.selectedTab;
if (tab.hasAttribute("image") && !tab.hasAttribute("busy")) {
faviconImage.setAttribute("iconloadingprincipal",
tab.getAttribute("iconloadingprincipal"));
faviconImage.setAttribute("src", tab.getAttribute("image"));
}
let canvas = PageThumbs.createCanvas(window);
PageThumbs.captureToCanvas(gBrowser.selectedBrowser, canvas);
document.mozSetImageElement("editBookmarkPanelImageCanvas", canvas);
},
panelShown:
function SU_panelShown(aEvent) {
if (aEvent.target == this.panel) {
@ -397,6 +423,7 @@ var PlacesCommandHook = {
async bookmarkPage(aBrowser, aShowEditUI, aUrl = null, aTitle = null) {
// If aUrl is provided, we want to bookmark that url rather than the
// the current page
let isCurrentBrowser = !aUrl;
let url = aUrl ? new URL(aUrl) : new URL(aBrowser.currentURI.spec);
let info = await PlacesUtils.bookmarks.fetch({ url });
let isNewBookmark = !info;
@ -452,13 +479,13 @@ var PlacesCommandHook = {
let anchor = BookmarkingUI.anchor;
if (anchor) {
await StarUI.showEditBookmarkPopup(node, anchor, "bottomcenter topright",
isNewBookmark, url);
isNewBookmark, url, isCurrentBrowser);
return;
}
// Fall back to showing the panel over the content area.
await StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark,
url);
url, isCurrentBrowser);
},
/**

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

@ -59,7 +59,7 @@ var gSafeBrowsing = {
getReportURL(name, info) {
let reportInfo = info;
if (!reportInfo) {
let pageUri = gBrowser.currentURI.clone();
let pageUri = gBrowser.currentURI;
// Remove the query to avoid including potentially sensitive data
if (pageUri instanceof Ci.nsIURL) {

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

@ -7893,13 +7893,15 @@ var MenuTouchModeObserver = {
var TabContextMenu = {
contextTab: null,
_updateToggleMuteMenuItem(aTab, aConditionFn) {
_updateToggleMuteMenuItems(aTab, aConditionFn) {
["muted", "soundplaying"].forEach(attr => {
if (!aConditionFn || aConditionFn(attr)) {
if (aTab.hasAttribute(attr)) {
aTab.toggleMuteMenuItem.setAttribute(attr, "true");
aTab.toggleMultiSelectMuteMenuItem.setAttribute(attr, "true");
} else {
aTab.toggleMuteMenuItem.removeAttribute(attr);
aTab.toggleMultiSelectMuteMenuItem.removeAttribute(attr);
}
}
});
@ -7908,6 +7910,7 @@ var TabContextMenu = {
this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
aPopupMenu.triggerNode : gBrowser.selectedTab;
let disabled = gBrowser.tabs.length == 1;
let multiselectionContext = this.contextTab.multiselected;
var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
for (let menuItem of menuItems)
@ -7942,9 +7945,8 @@ var TabContextMenu = {
document.getElementById("context_closeOtherTabs").disabled = unpinnedTabsToClose < 1;
// Only one of close_tab/close_selected_tabs should be visible
let hasMultiSelectedTabs = !!gBrowser.multiSelectedTabsCount;
document.getElementById("context_closeTab").hidden = hasMultiSelectedTabs;
document.getElementById("context_closeSelectedTabs").hidden = !hasMultiSelectedTabs;
document.getElementById("context_closeTab").hidden = multiselectionContext;
document.getElementById("context_closeSelectedTabs").hidden = !multiselectionContext;
// Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible.
let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs");
@ -7952,8 +7954,14 @@ var TabContextMenu = {
if (!bookmarkAllTabs.hidden)
PlacesCommandHook.updateBookmarkAllTabsCommand();
// Adjust the state of the toggle mute menu item.
let toggleMute = document.getElementById("context_toggleMuteTab");
let toggleMultiSelectMute = document.getElementById("context_toggleMuteSelectedTabs");
// Only one of mute_unmute_tab/mute_unmute_selected_tabs should be visible
toggleMute.hidden = multiselectionContext;
toggleMultiSelectMute.hidden = !multiselectionContext;
// Adjust the state of the toggle mute menu item.
if (this.contextTab.hasAttribute("activemedia-blocked")) {
toggleMute.label = gNavigatorBundle.getString("playTab.label");
toggleMute.accessKey = gNavigatorBundle.getString("playTab.accesskey");
@ -7965,8 +7973,21 @@ var TabContextMenu = {
toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey");
}
// Adjust the state of the toggle mute menu item for multi-selected tabs.
if (this.contextTab.hasAttribute("activemedia-blocked")) {
toggleMultiSelectMute.label = gNavigatorBundle.getString("playTabs.label");
toggleMultiSelectMute.accessKey = gNavigatorBundle.getString("playTabs.accesskey");
} else if (this.contextTab.hasAttribute("muted")) {
toggleMultiSelectMute.label = gNavigatorBundle.getString("unmuteSelectedTabs.label");
toggleMultiSelectMute.accessKey = gNavigatorBundle.getString("unmuteSelectedTabs.accesskey");
} else {
toggleMultiSelectMute.label = gNavigatorBundle.getString("muteSelectedTabs.label");
toggleMultiSelectMute.accessKey = gNavigatorBundle.getString("muteSelectedTabs.accesskey");
}
this.contextTab.toggleMuteMenuItem = toggleMute;
this._updateToggleMuteMenuItem(this.contextTab);
this.contextTab.toggleMultiSelectMuteMenuItem = toggleMultiSelectMute;
this._updateToggleMuteMenuItems(this.contextTab);
this.contextTab.addEventListener("TabAttrModified", this);
aPopupMenu.addEventListener("popuphiding", this);
@ -7983,7 +8004,7 @@ var TabContextMenu = {
break;
case "TabAttrModified":
let tab = aEvent.target;
this._updateToggleMuteMenuItem(tab,
this._updateToggleMuteMenuItems(tab,
attr => aEvent.detail.changed.includes(attr));
break;
}

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

@ -100,6 +100,8 @@
<menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
<menuitem id="context_toggleMuteTab" oncommand="TabContextMenu.contextTab.toggleMuteAudio();"/>
<menuitem id="context_toggleMuteSelectedTabs" hidden="true"
oncommand="gBrowser.toggleMuteAudioOnMultiSelectedTabs(TabContextMenu.contextTab);"/>
<menuseparator/>
<menuitem id="context_pinTab" label="&pinTab.label;"
accesskey="&pinTab.accesskey;"
@ -232,6 +234,10 @@
<box class="panel-header">
<label id="editBookmarkPanelTitle"/>
</box>
<box>
<html:img id="editBookmarkPanelFavicon"/>
</box>
<box id="editBookmarkPanelImage"/>
#include ../../components/places/content/editBookmarkPanel.inc.xul
<hbox id="editBookmarkPanelBottomButtons" pack="end">
#ifndef XP_UNIX

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

@ -99,7 +99,7 @@ with Files("test/urlbar/**"):
BUG_COMPONENT = ("Firefox", "Address Bar")
with Files("test/webextensions/**"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: Untriaged")
BUG_COMPONENT = ("WebExtensions", "Untriaged")
with Files("test/webrtc/**"):
BUG_COMPONENT = ("Core", "WebRTC")
@ -159,7 +159,7 @@ with Files("tabbrowser*"):
BUG_COMPONENT = ("Firefox", "Tabbed Browser")
with Files("webext-panels*"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: Frontend")
BUG_COMPONENT = ("WebExtensions", "Frontend")
with Files("webrtcIndicator*"):
BUG_COMPONENT = ("Firefox", "Device Permissions")

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

@ -3704,6 +3704,30 @@ window._gBrowser = {
this._lastMultiSelectedTabRef = Cu.getWeakReference(aTab);
},
toggleMuteAudioOnMultiSelectedTabs(aTab) {
const selectedTabs = ChromeUtils.nondeterministicGetWeakSetKeys(this._multiSelectedTabsSet)
.filter(tab => tab.isConnected);
let tabsToToggle;
if (aTab.activeMediaBlocked) {
tabsToToggle = selectedTabs.filter(tab =>
tab.activeMediaBlocked || tab.linkedBrowser.audioMuted
);
} else {
let tabMuted = aTab.linkedBrowser.audioMuted;
tabsToToggle = selectedTabs.filter(tab =>
// When a user is looking to mute selected tabs, then media-blocked tabs
// should not be toggled. Otherwise those media-blocked tabs are going into a
// playing and unmuted state.
tab.linkedBrowser.audioMuted == tabMuted && !tab.activeMediaBlocked ||
tab.activeMediaBlocked && tabMuted
);
}
for (let tab of tabsToToggle) {
tab.toggleMuteAudio();
}
},
activateBrowserForPrintPreview(aBrowser) {
this._printPreviewBrowsers.add(aBrowser);
if (this._switcher) {

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

@ -2015,7 +2015,7 @@
}
const overCloseButton = event.originalTarget.getAttribute("anonid") == "close-button";
if (gBrowser.multiSelectedTabsCount > 0 && !overCloseButton) {
if (gBrowser.multiSelectedTabsCount > 0 && !overCloseButton && !this._overPlayingIcon) {
// Tabs were previously multi-selected and user clicks on a tab
// without holding Ctrl/Cmd Key
@ -2028,7 +2028,11 @@
}
if (this._overPlayingIcon) {
this.toggleMuteAudio();
if (this.multiselected) {
gBrowser.toggleMuteAudioOnMultiSelectedTabs(this);
} else {
this.toggleMuteAudio();
}
return;
}

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

@ -50,3 +50,7 @@ skip-if = (debug && os == 'mac') || (debug && os == 'linux' && bits == 64) #Bug
[browser_multiselect_tabs_close.js]
[browser_multiselect_tabs_positional_attrs.js]
[browser_multiselect_tabs_close_using_shortcuts.js]
[browser_multiselect_tabs_mute_unmute.js]
support-files =
../general/audio.ogg
../general/file_mediaPlayback.html

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

@ -69,11 +69,6 @@ add_task(async function usingTabContextMenu() {
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
// Check the context menu with zero multiselected tabs
updateTabContextMenu(tab4);
is(menuItemCloseTab.hidden, false, "Close Tab is visible");
is(menuItemCloseSelectedTabs.hidden, true, "Close Selected Tabs is hidden");
await triggerClickOn(tab1, { ctrlKey: true });
await triggerClickOn(tab2, { ctrlKey: true });
@ -83,8 +78,13 @@ add_task(async function usingTabContextMenu() {
ok(!tab4.multiselected, "Tab4 is not multiselected");
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
// Check the context menu with two multiselected tabs
// Check the context menu with a non-multiselected tab
updateTabContextMenu(tab4);
is(menuItemCloseTab.hidden, false, "Close Tab is visible");
is(menuItemCloseSelectedTabs.hidden, true, "Close Selected Tabs is hidden");
// Check the context menu with a multiselected tab
updateTabContextMenu(tab2);
is(menuItemCloseTab.hidden, true, "Close Tab is hidden");
is(menuItemCloseSelectedTabs.hidden, false, "Close Selected Tabs is visible");

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

@ -0,0 +1,389 @@
const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
const PAGE = "https://example.com/browser/browser/base/content/test/general/file_mediaPlayback.html";
async function wait_for_tab_playing_event(tab, expectPlaying) {
if (tab.soundPlaying == expectPlaying) {
ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
return true;
}
return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
if (event.detail.changed.includes("soundplaying")) {
is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
return true;
}
return false;
});
}
async function waitForTabMuteStateChangeEvent(tab) {
return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
for (let attr of ["activemedia-blocked", "muted", "soundplaying"]) {
if (event.detail.changed.includes(attr)) {
return true;
}
}
return false;
});
}
async function is_audio_playing(tab) {
let browser = tab.linkedBrowser;
let isPlaying = await ContentTask.spawn(browser, {}, async function() {
let audio = content.document.querySelector("audio");
return !audio.paused;
});
return isPlaying;
}
async function play(tab) {
let browser = tab.linkedBrowser;
await ContentTask.spawn(browser, {}, async function() {
let audio = content.document.querySelector("audio");
audio.play();
});
// If the tab has already been muted, it means the tab won't get soundplaying,
// so we don't need to check this attribute.
if (browser.audioMuted) {
return;
}
await waitForTabMuteStateChangeEvent(tab);
}
function disable_non_test_mouse(disable) {
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
utils.disableNonTestMouseEvents(disable);
}
function hover_icon(icon, tooltip) {
disable_non_test_mouse(true);
let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
EventUtils.synthesizeMouse(icon, 1, 1, {type: "mouseover"});
EventUtils.synthesizeMouse(icon, 2, 2, {type: "mousemove"});
EventUtils.synthesizeMouse(icon, 3, 3, {type: "mousemove"});
EventUtils.synthesizeMouse(icon, 4, 4, {type: "mousemove"});
return popupShownPromise;
}
function leave_icon(icon) {
EventUtils.synthesizeMouse(icon, 0, 0, {type: "mouseout"});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
disable_non_test_mouse(false);
}
// The set of tabs which have ever had their mute state changed.
// Used to determine whether the tab should have a muteReason value.
let everMutedTabs = new WeakSet();
function get_wait_for_mute_promise(tab, expectMuted) {
return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => {
if (event.detail.changed.includes("muted") || event.detail.changed.includes("activemedia-blocked")) {
is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted");
is(tab.muted, expectMuted, "The tab muted property " + (expectMuted ? "" : "not ") + "be true");
if (expectMuted || everMutedTabs.has(tab)) {
everMutedTabs.add(tab);
is(tab.muteReason, null, "The tab should have a null muteReason value");
} else {
is(tab.muteReason, undefined, "The tab should have an undefined muteReason value");
}
return true;
}
return false;
});
}
async function test_mute_tab(tab, icon, expectMuted) {
let mutedPromise = waitForTabMuteStateChangeEvent(tab);
let activeTab = gBrowser.selectedTab;
let tooltip = document.getElementById("tabbrowser-tab-tooltip");
await hover_icon(icon, tooltip);
EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
leave_icon(icon);
is(gBrowser.selectedTab, activeTab, "Clicking on mute should not change the currently selected tab");
// If the audio is playing, we should check whether clicking on icon affects
// the media element's playing state.
let isAudioPlaying = await is_audio_playing(tab);
if (isAudioPlaying) {
await wait_for_tab_playing_event(tab, !expectMuted);
}
return mutedPromise;
}
function muted(tab) {
return tab.linkedBrowser.audioMuted;
}
function activeMediaBlocked(tab) {
return tab.activeMediaBlocked;
}
async function addMediaTab() {
const tab = BrowserTestUtils.addTab(gBrowser, PAGE, { skipAnimation: true });
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
return tab;
}
add_task(async function setPref() {
await SpecialPowers.pushPrefEnv({
set: [[PREF_MULTISELECT_TABS, true]]
});
});
add_task(async function muteTabs_usingButton() {
let tab0 = await addMediaTab();
let tab1 = await addMediaTab();
let tab2 = await addMediaTab();
let tab3 = await addMediaTab();
let tab4 = await addMediaTab();
let tabs = [tab0, tab1, tab2, tab3, tab4];
await BrowserTestUtils.switchTab(gBrowser, tab0);
await play(tab0);
await play(tab1);
await play(tab2);
// Multiselecting tab1, tab2 and tab3
await BrowserTestUtils.switchTab(gBrowser, tab1);
await triggerClickOn(tab3, { shiftKey: true });
is(gBrowser.multiSelectedTabsCount, 3, "Three multiselected tabs");
ok(!tab0.multiselected, "Tab0 is not multiselected");
ok(!tab4.multiselected, "Tab4 is not multiselected");
// tab1,tab2 and tab3 should be multiselected.
for (let i = 1; i <= 3; i++) {
ok(tabs[i].multiselected, "Tab" + i + " is multiselected");
}
// All five tabs are unmuted
for (let i = 0; i < 5; i++) {
ok(!muted(tabs[i]), "Tab" + i + " is not muted");
}
// Mute tab0 which is not multiselected, thus other tabs muted state should not be affected
let tab0MuteAudioBtn = document.getAnonymousElementByAttribute(tab0, "anonid", "soundplaying-icon");
await test_mute_tab(tab0, tab0MuteAudioBtn, true);
ok(muted(tab0), "Tab0 is muted");
for (let i = 1; i <= 4; i++) {
ok(!muted(tabs[i]), "Tab" + i + " is not muted");
}
// Now we multiselect tab0
await triggerClickOn(tab0, { ctrlKey: true });
// tab0, tab1, tab2, tab3 are multiselected
for (let i = 0; i <= 3; i++) {
ok(tabs[i].multiselected, "tab" + i + " is multiselected");
}
ok(!tab4.multiselected, "tab4 is not multiselected");
// Check mute state
ok(muted(tab0), "Tab0 is still muted");
ok(!muted(tab1) && !activeMediaBlocked(tab1), "Tab1 is not muted");
ok(activeMediaBlocked(tab2), "Tab2 is media-blocked");
ok(!muted(tab3) && !activeMediaBlocked(tab3), "Tab3 is not muted and not activemedia-blocked");
ok(!muted(tab4) && !activeMediaBlocked(tab4), "Tab4 is not muted and not activemedia-blocked");
// Mute tab1 which is mutliselected, thus other multiselected tabs should be affected too
// in the following way:
// a) muted tabs (tab0) will remain muted.
// b) unmuted tabs (tab1, tab3) will become muted.
// b) media-blocked tabs (tab2) will remain media-blocked.
// However tab4 (unmuted) which is not multiselected should not be affected.
let tab1MuteAudioBtn = document.getAnonymousElementByAttribute(tab1, "anonid", "soundplaying-icon");
await test_mute_tab(tab1, tab1MuteAudioBtn, true);
// Check mute state
ok(muted(tab0), "Tab0 is still muted");
ok(muted(tab1), "Tab1 is muted");
ok(activeMediaBlocked(tab2), "Tab2 is still media-blocked");
ok(muted(tab3), "Tab3 is now muted");
ok(!muted(tab4) && !activeMediaBlocked(tab4), "Tab4 is not muted and not activemedia-blocked");
for (let tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
});
add_task(async function unmuteTabs_usingButton() {
let tab0 = await addMediaTab();
let tab1 = await addMediaTab();
let tab2 = await addMediaTab();
let tab3 = await addMediaTab();
let tab4 = await addMediaTab();
let tabs = [tab0, tab1, tab2, tab3, tab4];
await BrowserTestUtils.switchTab(gBrowser, tab0);
await play(tab0);
await play(tab1);
await play(tab2);
// Mute tab3 and tab4
tab3.toggleMuteAudio();
tab4.toggleMuteAudio();
// Multiselecting tab0, tab1, tab2 and tab3
await triggerClickOn(tab3, { shiftKey: true });
// Check mutliselection
for (let i = 0; i <= 3; i++) {
ok(tabs[i].multiselected, "tab" + i + " is multiselected");
}
ok(!tab4.multiselected, "tab4 is not multiselected");
// Check tabs mute state
ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is not muted and not media-blocked");
ok(activeMediaBlocked(tab1), "Tab1 is media-blocked");
ok(activeMediaBlocked(tab2), "Tab2 is media-blocked");
ok(muted(tab3), "Tab3 is muted");
ok(muted(tab4), "Tab4 is muted");
is(gBrowser.selectedTab, tab0, "Tab0 is active");
// unmute tab0 which is mutliselected, thus other multiselected tabs should be affected too
// in the following way:
// a) muted tabs (tab3) will become unmuted.
// b) unmuted tabs (tab0) will remain unmuted.
// b) media-blocked tabs (tab1, tab2) will get playing. (media not blocked anymore)
// However tab4 (muted) which is not multiselected should not be affected.
let tab3MuteAudioBtn = document.getAnonymousElementByAttribute(tab3, "anonid", "soundplaying-icon");
await test_mute_tab(tab3, tab3MuteAudioBtn, false);
ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is unmuted and not media-blocked");
ok(!muted(tab1) && !activeMediaBlocked(tab1), "Tab1 is unmuted and not media-blocked");
ok(!muted(tab2) && !activeMediaBlocked(tab2), "Tab2 is unmuted and not media-blocked");
ok(!muted(tab3) && !activeMediaBlocked(tab3), "Tab3 is unmuted and not media-blocked");
ok(muted(tab4), "Tab4 is muted");
is(gBrowser.selectedTab, tab0, "Tab0 is active");
for (let tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
});
add_task(async function playTabs_usingButton() {
let tab0 = await addMediaTab();
let tab1 = await addMediaTab();
let tab2 = await addMediaTab();
let tab3 = await addMediaTab();
let tab4 = await addMediaTab();
let tabs = [tab0, tab1, tab2, tab3, tab4];
await BrowserTestUtils.switchTab(gBrowser, tab0);
await play(tab0);
await play(tab1);
await play(tab2);
// Multiselecting tab0, tab1, tab2 and tab3.
await triggerClickOn(tab3, { shiftKey: true });
// Mute tab0 and tab4
tab0.toggleMuteAudio();
tab4.toggleMuteAudio();
// Check mutliselection
for (let i = 0; i <= 3; i++) {
ok(tabs[i].multiselected, "tab" + i + " is multiselected");
}
ok(!tab4.multiselected, "tab4 is not multiselected");
// Check mute state
ok(muted(tab0), "Tab0 is muted");
ok(activeMediaBlocked(tab1), "Tab1 is media-blocked");
ok(activeMediaBlocked(tab2), "Tab2 is media-blocked");
ok(!muted(tab3) && !activeMediaBlocked(tab3), "Tab3 is not muted and not activemedia-blocked");
ok(muted(tab4), "Tab4 is muted");
is(gBrowser.selectedTab, tab0, "Tab0 is active");
// play tab2 which is mutliselected, thus other multiselected tabs should be affected too
// in the following way:
// a) muted tabs (tab0) will become unmuted.
// b) unmuted tabs (tab3) will remain unmuted.
// b) media-blocked tabs (tab1, tab2) will get playing. (media not blocked anymore)
// However tab4 (muted) which is not multiselected should not be affected.
let tab2MuteAudioBtn = document.getAnonymousElementByAttribute(tab2, "anonid", "soundplaying-icon");
await test_mute_tab(tab2, tab2MuteAudioBtn, false);
ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is unmuted and not activemedia-blocked");
ok(!muted(tab1) && !activeMediaBlocked(tab1), "Tab1 is unmuted and not activemedia-blocked");
ok(!muted(tab2) && !activeMediaBlocked(tab2), "Tab2 is unmuted and not activemedia-blocked");
ok(!muted(tab3) && !activeMediaBlocked(tab3), "Tab3 is unmuted and not activemedia-blocked");
ok(muted(tab4), "Tab4 is muted");
is(gBrowser.selectedTab, tab0, "Tab0 is active");
for (let tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
});
add_task(async function checkTabContextMenu() {
let tab0 = await addMediaTab();
let tab1 = await addMediaTab();
let tab2 = await addMediaTab();
let tab3 = await addMediaTab();
let tabs = [tab0, tab1, tab2, tab3];
let menuItemToggleMuteTab = document.getElementById("context_toggleMuteTab");
let menuItemToggleMuteSelectedTabs = document.getElementById("context_toggleMuteSelectedTabs");
await play(tab0);
tab0.toggleMuteAudio();
await play(tab1);
tab2.toggleMuteAudio();
// Mutliselect tab0, tab1, tab2.
await triggerClickOn(tab0, { ctrlKey: true });
await triggerClickOn(tab1, { ctrlKey: true });
await triggerClickOn(tab2, { ctrlKey: true });
// Check mutliselected tabs
for (let i = 0; i <= 2; i++) {
ok(tabs[i].multiselected, "Tab" + i + " is multi-selected");
}
ok(!tab3.multiselected, "Tab3 is not multiselected");
// Check mute state for tabs
ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is not muted and not activemedia-blocked");
ok(activeMediaBlocked(tab1), "Tab1 is media-blocked");
ok(muted(tab2), "Tab2 is muted");
ok(!muted(tab3, "Tab3 is not muted"));
let labels = ["Mute Tabs", "Play Tabs", "Unmute Tabs"];
for (let i = 0; i <= 2; i++) {
updateTabContextMenu(tabs[i]);
ok(menuItemToggleMuteTab.hidden,
"toggleMuteAudio menu for one tab is hidden - contextTab" + i);
ok(!menuItemToggleMuteSelectedTabs.hidden,
"toggleMuteAudio menu for selected tab is not hidden - contextTab" + i);
is(menuItemToggleMuteSelectedTabs.label, labels[i], labels[i] + " should be shown");
}
updateTabContextMenu(tab3);
ok(!menuItemToggleMuteTab.hidden, "toggleMuteAudio menu for one tab is not hidden");
ok(menuItemToggleMuteSelectedTabs.hidden, "toggleMuteAudio menu for selected tab is hidden");
for (let tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
});

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

@ -1378,7 +1378,6 @@ CustomizeMode.prototype = {
tbb.addEventListener("focus", previewTheme);
tbb.addEventListener("mouseover", previewTheme);
tbb.addEventListener("blur", resetPreview);
tbb.addEventListener("mouseout", resetPreview);
return tbb;
}
@ -1419,6 +1418,23 @@ CustomizeMode.prototype = {
panel.insertBefore(button, recommendedLabel);
}
function panelMouseOut(e) {
// mouseout events bubble, so we get mouseout events for the buttons
// in the panel. Here, we only care when the mouse actually leaves
// the panel. For some reason event.target might not be the panel
// even when the mouse *is* leaving the panel, so we check
// explicitOriginalTarget instead.
if (e.explicitOriginalTarget == panel) {
resetPreview();
}
}
panel.addEventListener("mouseout", panelMouseOut);
panel.addEventListener("popuphidden", () => {
panel.removeEventListener("mouseout", panelMouseOut);
resetPreview();
}, {once: true});
let lwthemePrefs = Services.prefs.getBranch("lightweightThemes.");
let recommendedThemes = lwthemePrefs.getStringPref("recommendedThemes");
recommendedThemes = JSON.parse(recommendedThemes);

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

@ -5,7 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: Untriaged")
BUG_COMPONENT = ("WebExtensions", "Untriaged")
JAR_MANIFESTS += ['jar.mn']

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

@ -956,7 +956,7 @@ var gEditItemOverlay = {
// representing element.
let menupopup = this._folderMenuList.menupopup;
for (let menuitem of menupopup.childNodes) {
if ("folderId" in menuitem && menuitem.folderId == aItemId) {
if ("folderGuid" in menuitem && menuitem.folderGuid == aGuid) {
menuitem.label = aNewTitle;
break;
}
@ -965,7 +965,7 @@ var gEditItemOverlay = {
// We need to also update title of recent folders.
if (this._recentFolders) {
for (let folder of this._recentFolders) {
if (folder.folderId == aItemId) {
if (folder.folderGuid == aGuid) {
folder.title = aNewTitle;
break;
}

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

@ -39,6 +39,7 @@ skip-if = (verify && debug && (os == 'win' || os == 'mac'))
[browser_bookmarkProperties_editFolder.js]
[browser_bookmarkProperties_editTagContainer.js]
[browser_bookmarkProperties_no_user_actions.js]
[browser_bookmarkProperties_newFolder.js]
[browser_bookmarkProperties_readOnlyRoot.js]
[browser_bookmarkProperties_remember_folders.js]
[browser_bookmarksProperties.js]

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

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const TEST_URL = "about:robots";
const bookmarkPanel = document.getElementById("editBookmarkPanel");
let folders;
add_task(async function setup() {
await PlacesUtils.bookmarks.eraseEverything();
bookmarkPanel.setAttribute("animate", false);
let oldTimeout = StarUI._autoCloseTimeout;
// Make the timeout something big, so it doesn't iteract badly with tests.
StarUI._autoCloseTimeout = 6000000;
let tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_URL,
waitForStateStop: true
});
registerCleanupFunction(async () => {
StarUI._autoCloseTimeout = oldTimeout;
BrowserTestUtils.removeTab(tab);
bookmarkPanel.removeAttribute("animate");
await PlacesUtils.bookmarks.eraseEverything();
});
});
add_task(async function test_newFolder() {
await clickBookmarkStar();
// Open folder selector.
document.getElementById("editBMPanel_foldersExpander").click();
let folderTree = document.getElementById("editBMPanel_folderTree");
// Create new folder.
let newFolderButton = document.getElementById("editBMPanel_newFolderButton");
newFolderButton.click();
let newFolderGuid;
let newFolderObserver = PlacesTestUtils.waitForNotification("onItemAdded",
(id, parentId, index, type, uri, title, dateAdded, guid) => {
newFolderGuid = guid;
return type == PlacesUtils.bookmarks.TYPE_FOLDER;
});
let menulist = document.getElementById("editBMPanel_folderMenuList");
await newFolderObserver;
// Wait for the folder to be created and for editing to start.
await BrowserTestUtils.waitForCondition(() => folderTree.hasAttribute("editing"),
"Should be in edit mode for the new folder");
Assert.equal(menulist.selectedItem.label, newFolderButton.label,
"Should have the new folder selected by default");
let renameObserver = PlacesTestUtils.waitForNotification("onItemChanged",
(id, property, isAnno, aNewValue) => property == "title" && aNewValue == "f");
// Enter a new name.
EventUtils.synthesizeKey("f", {}, window);
EventUtils.synthesizeKey("VK_RETURN", {}, window);
await renameObserver;
await BrowserTestUtils.waitForCondition(() => !folderTree.hasAttribute("editing"),
"Should have stopped editing the new folder");
Assert.equal(menulist.selectedItem.label, "f",
"Should have the new folder title");
let bookmark = await PlacesUtils.bookmarks.fetch({url: TEST_URL});
Assert.equal(bookmark.parentGuid, newFolderGuid,
"The bookmark should be parented by the new folder");
await hideBookmarksPanel();
});

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

@ -11,19 +11,6 @@
const bookmarkPanel = document.getElementById("editBookmarkPanel");
let folders;
async function clickBookmarkStar() {
let shownPromise = promisePopupShown(bookmarkPanel);
BookmarkingUI.star.click();
await shownPromise;
}
async function hideBookmarksPanel() {
let hiddenPromise = promisePopupHidden(bookmarkPanel);
// Confirm and close the dialog.
document.getElementById("editBookmarkPanelDoneButton").click();
await hiddenPromise;
}
async function openPopupAndSelectFolder(guid, newBookmark = false) {
await clickBookmarkStar();

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

@ -404,3 +404,18 @@ function getToolbarNodeForItemGuid(itemGuid) {
}
return null;
}
// Open the bookmarks Star UI by clicking the star button on the address bar.
async function clickBookmarkStar() {
let shownPromise = promisePopupShown(document.getElementById("editBookmarkPanel"));
BookmarkingUI.star.click();
await shownPromise;
}
// Close the bookmarks Star UI by clicking the "Done" button.
async function hideBookmarksPanel() {
let hiddenPromise = promisePopupHidden(document.getElementById("editBookmarkPanel"));
// Confirm and close the dialog.
document.getElementById("editBookmarkPanelDoneButton").click();
await hiddenPromise;
}

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

@ -887,6 +887,13 @@ unmuteTab.accesskey = m
playTab.label = Play Tab
playTab.accesskey = l
muteSelectedTabs.label = Mute Tabs
muteSelectedTabs.accesskey = u
unmuteSelectedTabs.label = Unmute Tabs
unmuteSelectedTabs.accesskey = b
playTabs.label = Play Tabs
playTabs.accesskey = y
# LOCALIZATION NOTE (certErrorDetails*.label): These are text strings that
# appear in the about:certerror page, so that the user can copy and send them to
# the server administrators for troubleshooting.

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

@ -19,7 +19,7 @@ function getSiteBlockedErrorDetails(docShell) {
if (classifiedChannel) {
let httpChannel = docShell.failedChannel.QueryInterface(Ci.nsIHttpChannel);
let reportUri = httpChannel.URI.clone();
let reportUri = httpChannel.URI;
// Remove the query to avoid leaking sensitive data
if (reportUri instanceof Ci.nsIURL) {

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

@ -805,7 +805,7 @@ class ContextMenu {
// nsDocumentViewer::GetInImage. Make sure to update both if this is
// changed.
if (context.target instanceof Ci.nsIImageLoadingContent &&
context.target.currentRequestFinalURI) {
(context.target.currentRequestFinalURI || context.target.currentURI)) {
context.onImage = true;
context.imageInfo = {
@ -829,8 +829,9 @@ class ContextMenu {
// The actual URL the image was loaded from (after redirects) is the
// currentRequestFinalURI. We should use that as the URL for purposes of
// deciding on the filename.
context.mediaURL = context.target.currentRequestFinalURI.spec;
// deciding on the filename, if it is present. It might not be present
// if images are blocked.
context.mediaURL = (context.target.currentRequestFinalURI || context.target.currentURI).spec;
const descURL = context.target.getAttribute("longdesc");

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

@ -65,7 +65,7 @@ with Files("ContentWebRTC.jsm"):
BUG_COMPONENT = ("Firefox", "Device Permissions")
with Files("ExtensionsUI.jsm"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: General")
BUG_COMPONENT = ("WebExtensions", "General")
with Files("LaterRun.jsm"):
BUG_COMPONENT = ("Firefox", "Tours")
@ -107,7 +107,7 @@ with Files("TabsPopup.jsm"):
BUG_COMPONENT = ("Firefox", "Tabbed Browser")
with Files("ThemeVariableMap.jsm"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: Themes")
BUG_COMPONENT = ("WebExtensions", "Themes")
with Files("TransientPrefs.jsm"):
BUG_COMPONENT = ("Firefox", "Preferences")

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

@ -8,6 +8,28 @@
padding: 0;
}
html|img#editBookmarkPanelFavicon[src] {
box-sizing: content-box;
width: 32px;
height: 32px;
padding: 5px;
background-color: #F9F9FA;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.1);
border-radius: 6px;
position: relative;
margin-top: 10px;
margin-inline-start: 10px;
margin-bottom: -52px; /* margin-top + paddings + height */
}
#editBookmarkPanelImage {
border-bottom: 1px solid var(--panel-separator-color);
height: 150px;
background-image: -moz-element(#editBookmarkPanelImageCanvas);
background-repeat: no-repeat;
background-size: cover;
}
#editBookmarkPanelRows {
padding: var(--arrowpanel-padding);
}

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

@ -11,7 +11,7 @@ with Files("controlCenter/**"):
BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
with Files("devtools/**"):
BUG_COMPONENT = ("Firefox", "Developer Tools")
BUG_COMPONENT = ("DevTools", "General")
with Files("permissionPrompts/**"):
BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")

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

@ -281,7 +281,7 @@ NullPrincipalURI::SetUserPass(const nsACString& aUserPass)
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsresult
NullPrincipalURI::Clone(nsIURI** _newURI)
{
nsCOMPtr<nsIURI> uri = new NullPrincipalURI(*this);

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

@ -56,6 +56,7 @@ private:
nsAutoCStringN<NSID_LENGTH> mPath;
nsresult Clone(nsIURI** aURI);
nsresult SetSpecInternal(const nsACString &input);
nsresult SetScheme(const nsACString &input);
nsresult SetUserPass(const nsACString &input);

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

@ -43,7 +43,6 @@ module.exports = {
"files": [
"client/framework/**",
"client/scratchpad/**",
"client/shared/AppCacheUtils.jsm",
"client/webide/**",
],
"rules": {

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

@ -14,5 +14,5 @@ BROWSER_CHROME_MANIFESTS += [
]
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: about:debugging')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'about:debugging')

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

@ -23,4 +23,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Accessibility')
BUG_COMPONENT = ('DevTools', 'Accessibility Tools')

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

@ -9,5 +9,5 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Canvas Debugger')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Canvas Debugger')

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

@ -19,5 +19,5 @@ BROWSER_CHROME_MANIFESTS += [
'test/mochitest/browser2.ini'
]
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Debugger')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Debugger')

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

@ -740,14 +740,14 @@ AddonDebugger.prototype = {
return groups;
}),
_onMessage: function (event) {
if (typeof(event.data) !== "string") {
_onMessage: function(event) {
if (!event.data) {
return;
}
let json = JSON.parse(event.data);
switch (json.name) {
const msg = event.data;
switch (msg.name) {
case "toolbox-title":
this.title = json.data.value;
this.title = msg.data.value;
break;
}
}

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

@ -13,5 +13,5 @@ DevToolsModules(
'dom-panel.js',
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: DOM')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'DOM')

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

@ -33,4 +33,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Framework')
BUG_COMPONENT = ('DevTools', 'Framework')

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

@ -5,53 +5,41 @@
const TEST_URL = "data:text/html,test custom host";
function test() {
const {Toolbox} = require("devtools/client/framework/toolbox");
add_task(async function() {
const { Toolbox } = require("devtools/client/framework/toolbox");
let toolbox, iframe, target;
window.addEventListener("message", onMessage);
iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
addTab(TEST_URL).then(function(tab) {
target = TargetFactory.forTab(tab);
const options = {customIframe: iframe};
gDevTools.showToolbox(target, null, Toolbox.HostType.CUSTOM, options)
.then(testCustomHost, console.error)
.catch(console.error);
const messageReceived = new Promise(resolve => {
function onMessage(event) {
if (!event.data) {
return;
}
const msg = event.data;
info(`onMessage: ${JSON.stringify(msg)}`);
switch (msg.name) {
case "toolbox-close":
ok(true, "Got the `toolbox-close` message");
window.removeEventListener("message", onMessage);
resolve();
break;
}
}
window.addEventListener("message", onMessage);
});
function onMessage(event) {
if (typeof (event.data) !== "string") {
return;
}
info("onMessage: " + event.data);
const json = JSON.parse(event.data);
if (json.name == "toolbox-close") {
ok("Got the `toolbox-close` message");
window.removeEventListener("message", onMessage);
cleanup();
}
}
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
function testCustomHost(t) {
toolbox = t;
is(toolbox.win.top, window, "Toolbox is included in browser.xul");
is(toolbox.doc, iframe.contentDocument, "Toolbox is in the custom iframe");
executeSoon(() => gBrowser.removeCurrentTab());
}
const tab = await addTab(TEST_URL);
let target = TargetFactory.forTab(tab);
const options = { customIframe: iframe };
let toolbox = await gDevTools.showToolbox(target, null, Toolbox.HostType.CUSTOM, options);
function cleanup() {
iframe.remove();
is(toolbox.win.top, window, "Toolbox is included in browser.xul");
is(toolbox.doc, iframe.contentDocument, "Toolbox is in the custom iframe");
// Even if we received "toolbox-close", the toolbox may still be destroying
// toolbox.destroy() returns a singleton promise that ensures
// everything is cleaned up before proceeding.
toolbox.destroy().then(() => {
toolbox = iframe = target = null;
finish();
});
}
}
iframe.remove();
await toolbox.destroy();
await messageReceived;
iframe = toolbox = target = null;
});

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

@ -104,20 +104,21 @@ ToolboxHostManager.prototype = {
if (!event.data) {
return;
}
const msg = event.data;
// Toolbox document is still chrome and disallow identifying message
// origin via event.source as it is null. So use a custom id.
if (event.data.frameId != this.frameId) {
if (msg.frameId != this.frameId) {
return;
}
switch (event.data.name) {
switch (msg.name) {
case "switch-host":
this.switchHost(event.data.hostType);
this.switchHost(msg.hostType);
break;
case "raise-host":
this.host.raise();
break;
case "set-host-title":
this.host.setTitle(event.data.title);
this.host.setTitle(msg.title);
break;
}
},

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

@ -335,11 +335,12 @@ CustomHost.prototype = {
if (!topWindow) {
return;
}
const json = {name: "toolbox-" + msg, uid: this.uid};
if (data) {
json.data = data;
}
topWindow.postMessage(JSON.stringify(json), "*");
const message = {
name: "toolbox-" + msg,
uid: this.uid,
data,
};
topWindow.postMessage(message, "*");
},
/**

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

@ -229,18 +229,17 @@ function onUnload() {
}
function onMessage(event) {
try {
const json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-raise":
raise();
break;
case "toolbox-title":
setTitle(json.data.value);
break;
}
} catch (e) {
console.error(e);
if (!event.data) {
return;
}
const msg = event.data;
switch (msg.name) {
case "toolbox-raise":
raise();
break;
case "toolbox-title":
setTitle(msg.data.value);
break;
}
}

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

@ -17,4 +17,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Animation Inspector')
BUG_COMPONENT = ('DevTools', 'Animation Inspector')

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

@ -326,10 +326,7 @@ class AnimationInspector {
// sice the scrubber position is related the currentTime.
// Also, don't update the state of removed animations since React components
// may refer to the same instance still.
await this.updateAnimations(animations);
// Get rid of animations that were removed during async updateAnimations().
animations = animations.filter(animation => !!animation.state.type);
animations = await this.updateAnimations(animations);
this.updateState(animations.concat(addedAnimations));
}
@ -403,12 +400,12 @@ class AnimationInspector {
return;
}
const { animations } = this.state;
let animations = this.state.animations;
this.isCurrentTimeSet = true;
try {
await this.doSetCurrentTimes(currentTime);
await this.updateAnimations(animations);
animations = await this.updateAnimations(animations);
} catch (e) {
// Expected if we've already been destroyed or other node have been selected
// in the meantime.
@ -419,12 +416,12 @@ class AnimationInspector {
this.isCurrentTimeSet = false;
if (shouldRefresh) {
this.updateState([...animations]);
this.updateState(animations);
}
}
async setAnimationsPlaybackRate(playbackRate) {
const animations = this.state.animations;
let animations = this.state.animations;
// "changed" event on each animation will fire respectively when the playback
// rate changed. Since for each occurrence of event, change of UI is urged.
// To avoid this, disable the listeners once in order to not capture the event.
@ -432,7 +429,7 @@ class AnimationInspector {
try {
await this.animationsFront.setPlaybackRates(animations, playbackRate);
await this.updateAnimations(animations);
animations = await this.updateAnimations(animations);
} catch (e) {
// Expected if we've already been destroyed or other node have been selected
// in the meantime.
@ -442,7 +439,7 @@ class AnimationInspector {
this.setAnimationStateChangedListenerEnabled(true);
}
await this.updateState([...animations]);
await this.updateState(animations);
}
async setAnimationsPlayState(doPlay) {
@ -451,7 +448,7 @@ class AnimationInspector {
await this.inspector.target.actorHasMethod("animations", "pauseSome");
}
const { animations, timeScale } = this.state;
let { animations, timeScale } = this.state;
try {
if (doPlay && animations.every(animation =>
@ -474,7 +471,7 @@ class AnimationInspector {
await this.animationsFront.pauseAll();
}
await this.updateAnimations(animations);
animations = await this.updateAnimations(animations);
} catch (e) {
// Expected if we've already been destroyed or other node have been selected
// in the meantime.
@ -482,7 +479,7 @@ class AnimationInspector {
return;
}
await this.updateState([...animations]);
await this.updateState(animations);
}
/**
@ -629,31 +626,29 @@ class AnimationInspector {
done();
}
updateAnimations(animations) {
if (!animations.length) {
return Promise.resolve();
}
async updateAnimations(animations) {
let error = null;
return new Promise((resolve, reject) => {
let count = 0;
let error = null;
for (const animation of animations) {
const promises = animations.map(animation => {
return new Promise(resolve => {
animation.refreshState().catch(e => {
error = e;
}).finally(() => {
count += 1;
if (count === animations.length) {
if (error) {
reject(error);
} else {
resolve();
}
}
resolve();
});
}
});
});
await Promise.all(promises);
if (error) {
throw new Error(error);
}
// Even when removal animation on inspected document, updateAnimations
// might be called before onAnimationsMutation due to the async timing.
// Return the animations as result of updateAnimations after getting rid of
// the animations since they should not display.
return animations.filter(anim => !!anim.state.type);
}
updateState(animations) {

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

@ -10,7 +10,7 @@ add_task(async function() {
await addTab(URL_ROOT + "doc_simple_animation.html");
await removeAnimatedElementsExcept(
[".compositor-all", ".compositor-notall", ".no-compositor"]);
const { inspector, panel } = await openAnimationInspector();
const { animationInspector, inspector, panel } = await openAnimationInspector();
info("Select a test node we know has an animation running on the compositor");
await selectNodeAndWaitForAnimations(".compositor-all", inspector);
@ -42,6 +42,33 @@ add_task(async function() {
ok(hasTooltip(summaryGraphEl,
ANIMATION_L10N.getStr("player.somePropertiesOnCompositorTooltip")),
"The element has the right tooltip content");
info("Check compositor sign after pausing");
await clickOnPauseResumeButton(animationInspector, panel);
ok(!summaryGraphEl.classList.contains("compositor"),
"The element should not have the compositor css class after pausing");
info("Check compositor sign after resuming");
await clickOnPauseResumeButton(animationInspector, panel);
ok(summaryGraphEl.classList.contains("compositor"),
"The element should have the compositor css class after resuming");
info("Check compositor sign after rewind");
await clickOnRewindButton(animationInspector, panel);
ok(!summaryGraphEl.classList.contains("compositor"),
"The element should not have the compositor css class after rewinding");
await clickOnPauseResumeButton(animationInspector, panel);
ok(summaryGraphEl.classList.contains("compositor"),
"The element should have the compositor css class after resuming");
info("Check compositor sign after setting the current time");
await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.5);
ok(!summaryGraphEl.classList.contains("compositor"),
"The element should not have the compositor css class " +
"after setting the current time");
await clickOnPauseResumeButton(animationInspector, panel);
ok(summaryGraphEl.classList.contains("compositor"),
"The element should have the compositor css class after resuming");
});
function hasTooltip(summaryGraphEl, expected) {

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

@ -43,10 +43,11 @@ module.exports = {
};
},
updateFontEditor(fonts, properties = {}) {
updateFontEditor(fonts, families = { used: [], notUsed: [] }, properties = {}) {
return {
type: UPDATE_EDITOR_STATE,
fonts,
families,
properties,
};
},

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

@ -79,8 +79,32 @@ class FontEditor extends PureComponent {
});
});
}
/**
* Render font family, font name, and metadata for all fonts used on selected node.
*
* @param {Array} fonts
* Fonts used on selected node.
* @param {Function} onToggleFontHighlight
* Callback to trigger in-context highlighting of text that uses a font.
* @return {DOMNode}
*/
renderFontFamily(fonts, onToggleFontHighlight) {
if (!fonts.length) {
return null;
}
const fontList = dom.ul(
{
className: "fonts-list"
},
fonts.map(font => {
return dom.li(
{},
FontMeta({ font, onToggleFontHighlight })
);
})
);
renderFontFamily(font, onToggleFontHighlight) {
return dom.label(
{
className: "font-control font-control-family",
@ -95,27 +119,27 @@ class FontEditor extends PureComponent {
{
className: "font-control-box",
},
FontMeta({ font, onToggleFontHighlight })
fontList
)
);
}
renderFontSize(value) {
return FontSize({
return value && FontSize({
onChange: this.props.onPropertyChange,
value,
});
}
renderFontStyle(value) {
return FontStyle({
return value && FontStyle({
onChange: this.props.onPropertyChange,
value,
});
}
renderFontWeight(value) {
return FontWeight({
return value && FontWeight({
onChange: this.props.onPropertyChange,
value,
});
@ -180,26 +204,40 @@ class FontEditor extends PureComponent {
);
}
renderWarning() {
return dom.div(
{
className: "devtools-sidepanel-no-result"
},
getStr("fontinspector.noFontsOnSelectedElement")
);
}
render() {
const { fontEditor, onToggleFontHighlight } = this.props;
const { fonts, axes, instance, properties } = fontEditor;
const usedFonts = fonts.filter(font => font.used);
// If no used fonts were found, pick the first available font.
// Else, pick the first used font regardless of how many there are.
const font = usedFonts.length === 0 ? fonts[0] : usedFonts[0];
// Pick the first font to show editor controls regardless of how many fonts are used.
const font = fonts[0];
const hasFontAxes = font && font.variationAxes;
const hasFontInstances = font && font.variationInstances.length > 0;
const hasFontInstances = font && font.variationInstances
&& font.variationInstances.length > 0;
const hasSlantOrItalicAxis = hasFontAxes && font.variationAxes.find(axis => {
return axis.tag === "slnt" || axis.tag === "ital";
});
const hasWeightAxis = hasFontAxes && font.variationAxes.find(axis => {
return axis.tag === "wght";
});
// Check for falsy font-weight value (undefined or empty string).
const hasWeight = properties["font-weight"] != null;
return dom.div(
{},
{
id: "font-editor"
},
// Render empty state message for nodes that don't have font properties.
!hasWeight && this.renderWarning(),
// Always render UI for font family, format and font file URL.
this.renderFontFamily(font, onToggleFontHighlight),
this.renderFontFamily(fonts, onToggleFontHighlight),
// Render UI for font variation instances if they are defined.
hasFontInstances && this.renderInstances(font.variationInstances, instance),
// Always render UI for font size.

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

@ -7,6 +7,7 @@
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const Services = require("Services");
const Accordion = createFactory(require("devtools/client/inspector/layout/components/Accordion"));
const FontList = createFactory(require("./FontList"));
@ -14,6 +15,8 @@ const FontList = createFactory(require("./FontList"));
const { getStr } = require("../utils/l10n");
const Types = require("../types");
const PREF_FONT_EDITOR = "devtools.inspector.fonteditor.enabled";
class FontOverview extends PureComponent {
static get propTypes() {
return {
@ -32,6 +35,12 @@ class FontOverview extends PureComponent {
}
renderElementFonts() {
// Do not show element fonts if the font editor is enabled.
// It handles this differently. Rendering twice is not desired.
if (Services.prefs.getBoolPref(PREF_FONT_EDITOR)) {
return null;
}
const {
fontData,
fontOptions,

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

@ -38,14 +38,12 @@ class FontsApp extends PureComponent {
onToggleFontHighlight,
} = this.props;
const hasFonts = fontEditor.fonts.length > 0;
return dom.div(
{
className: "theme-sidebar inspector-tabpanel",
id: "sidebar-panel-fontinspector"
},
hasFonts && FontEditor({
FontEditor({
fontEditor,
onInstanceChange,
onPropertyChange,

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

@ -74,8 +74,8 @@ class FontInspector {
this.onNewNode = this.onNewNode.bind(this);
this.onPreviewFonts = this.onPreviewFonts.bind(this);
this.onPropertyChange = this.onPropertyChange.bind(this);
this.onToggleFontHighlight = this.onToggleFontHighlight.bind(this);
this.onRuleUpdated = this.onRuleUpdated.bind(this);
this.onToggleFontHighlight = this.onToggleFontHighlight.bind(this);
this.onThemeChanged = this.onThemeChanged.bind(this);
this.update = this.update.bind(this);
this.updateFontVariationSettings = this.updateFontVariationSettings.bind(this);
@ -108,7 +108,6 @@ class FontInspector {
this.inspector.selection.on("new-node-front", this.onNewNode);
// @see ToolSidebar.onSidebarTabSelected()
this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
this.ruleView.on("property-value-updated", this.onRuleUpdated);
// Listen for theme changes as the color of the previews depend on the theme
gDevTools.on("theme-switched", this.onThemeChanged);
@ -152,6 +151,51 @@ class FontInspector {
this.writers = null;
}
/**
* Get a subset of fonts used on a node whose font family names are found in the
* node's CSS font-family property value. The fonts will be sorted in the order their
* family names are declared in CSS font-family.
*
* Fonts returned by this.getFontsForNode() contain, among others, these attributes:
* - CSSFamilyName: a string of the font's family name (ex: "Times");
* - CSSGeneric: a string of the generic font family (ex: "serif", "sans-serif") if
* the font was resolved from a generic font family keyword, like serif, instead of
* an explicit font famly, like "Times". If the font is resolved from an
* explicit font family, CSSGeneric is null.
*
* For example:
* font-family: "Avenir", serif;
*
* If fonts from both families are used, it will yield:
* { CSSFamilyName: "Avenir", CSSGeneric: null, ... },
* { CSSFamilyName: "Times", CSSGeneric: "serif", ... },
*
* @param {Array} fonts
* Fonts used on a node got from a call to this.getFontsForNode().
* @param {Array} fontFamilies
* Strings of font families from a node's CSS font-family property value.
* @return {Array}
* Subset of `fonts` whose font family names appear in `fontFamilies`.
*/
filterFontsUsed(fonts = [], fontFamilies = []) {
return fontFamilies.reduce((acc, family) => {
const match = fonts.find(font => {
const generic = typeof font.CSSGeneric === "string"
? font.CSSGeneric.toLowerCase()
: font.CSSGeneric;
return generic === family.toLowerCase()
|| font.CSSFamilyName.toLowerCase() === family.toLowerCase();
});
if (match) {
acc.push(match);
}
return acc;
}, []);
}
/**
* Get all expected CSS font properties and values from the node's matching rules and
* fallback to computed style.
@ -162,9 +206,12 @@ class FontInspector {
const KEYWORD_VALUES = ["initial", "inherit", "unset", "none"];
const properties = {};
// First, get all expected font properties from computed styles.
// First, get all expected font properties from computed styles, if available.
for (const prop of FONT_PROPERTIES) {
properties[prop] = this.nodeComputedStyle[prop].value;
properties[prop] =
(this.nodeComputedStyle[prop] && this.nodeComputedStyle[prop].value)
? this.nodeComputedStyle[prop].value
: "";
}
// Then, replace with enabled font properties found on any of the rules that apply.
@ -342,13 +389,42 @@ class FontInspector {
return this.writers.get(name);
}
/**
* Given a list of font families, return an object that groups them into sets of used
* and not used if they match families of fonts from the given list of fonts used on a
* node.
*
* @See this.filterFontsUsed() for an explanation of CSSFamilyName and CSSGeneric.
*
* @param {Array} fontsUsed
* Fonts used on a node.
* @param {Array} fontFamilies
* Strings of font families
* @return {Object}
*/
groupFontFamilies(fontsUsed = [], fontFamilies = []) {
const families = {};
// Font family names declared and used.
families.used = fontsUsed.map(font =>
font.CSSGeneric ? font.CSSGeneric : font.CSSFamilyName
);
const familiesUsedLowercase = families.used.map(family => family.toLowerCase());
// Font family names declared but not used.
families.notUsed = fontFamilies
.map(family => family.toLowerCase())
.filter(family => !familiesUsedLowercase.includes(family));
return families;
}
/**
* Check if the font inspector panel is visible.
*
* @return {Boolean}
*/
isPanelVisible() {
return this.inspector.sidebar &&
return this.inspector &&
this.inspector.sidebar &&
this.inspector.sidebar.getCurrentTabID() === "fontinspector";
}
/**
@ -357,7 +433,8 @@ class FontInspector {
* @return {Boolean}
*/
isSelectedNodeValid() {
return this.inspector.selection.nodeFront &&
return this.inspector &&
this.inspector.selection.nodeFront &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode();
}
@ -568,44 +645,51 @@ class FontInspector {
const node = this.inspector.selection.nodeFront;
const fonts = await this.getFontsForNode(node, options);
if (!fonts.length) {
this.store.dispatch(resetFontEditor());
return;
}
// Get computed styles for the selected node, but filter by CSS font properties.
this.nodeComputedStyle = await this.pageStyle.getComputed(node, {
filterProperties: FONT_PROPERTIES
});
// Clear any references to writer methods because the node's
if (!this.nodeComputedStyle) {
this.store.dispatch(resetFontEditor());
return;
}
// Clear any references to writer methods and CSS declarations because the node's
// styles may have changed since the last font editor refresh.
this.writers.clear();
// Select the node's inline style as the rule where to write property value changes.
this.selectedRule =
this.ruleView.rules.find(rule => rule.domRule.type === ELEMENT_STYLE);
const fontEditor = this.store.getState().fontEditor;
const properties = this.getFontProperties();
// Names of fonts declared in font-family property without quotes and space trimmed.
const declaredFontNames =
properties["font-family"].split(",").map(font => font.replace(/\"+/g, "").trim());
// Mark available fonts as used if their names appears in the font-family declaration.
// TODO: sort used fonts in order of font-family declaration.
for (const font of fonts) {
font.used = declaredFontNames.includes(font.CSSFamilyName);
}
const familiesDeclared =
properties["font-family"].split(",")
.map(font => font.replace(/["']+/g, "").trim());
// Subset of fonts used on the node whose family names exist in CSS font-family.
let fontsUsed = this.filterFontsUsed(fonts, familiesDeclared);
// Object with font families groupped by used and not used.
const families = this.groupFontFamilies(fontsUsed, familiesDeclared);
// Assign writer methods to each axis defined in font-variation-settings.
const axes = parseFontVariationAxes(properties["font-variation-settings"]);
Object.keys(axes).map(axis => {
this.writers.set(axis, this.getWriterForAxis(axis));
});
// Update the font editor state only if property values differ from the ones in store.
// This can happen when a user makes manual changes in the Rule view.
if (JSON.stringify(properties) !== JSON.stringify(fontEditor.properties)) {
this.store.dispatch(updateFontEditor(fonts, properties));
// Pick fonts from descendants if no declared fonts were used on this node.
if (!fontsUsed.length && fonts.length) {
const otherVarFonts = fonts.filter(font => {
return (font.variationAxes && font.variationAxes.length);
});
// Prefer picking variable fonts if any were found on descendants of this node.
// The FontEditor component will render UI for the first font in the list.
fontsUsed = otherVarFonts.length ? otherVarFonts : fonts;
}
this.store.dispatch(updateFontEditor(fontsUsed, families, properties));
this.inspector.emit("fonteditor-updated");
}
/**

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

@ -21,7 +21,15 @@ const INITIAL_STATE = {
axes: {},
// Copy of the most recent axes values. Used to revert from a named instance.
customInstanceValues: [],
// Fonts applicable to selected element.
// Font families declared on the selected element
families: {
// Names of font families used
used: [],
// Names of font families declared but not used
notUsed: []
},
// Fonts whose family names are declared in CSS font-family and used
// on the selected element.
fonts: [],
// Current selected font variation instance.
instance: {
@ -70,7 +78,7 @@ const reducers = {
return newState;
},
[UPDATE_EDITOR_STATE](state, { fonts, properties }) {
[UPDATE_EDITOR_STATE](state, { fonts, families, properties }) {
const axes = parseFontVariationAxes(properties["font-variation-settings"]);
// If not defined in font-variation-settings, setup "wght" axis with the value of
@ -90,7 +98,7 @@ const reducers = {
axes.wdth = match[1];
}
return { ...state, axes, fonts, properties };
return { ...state, axes, fonts, families, properties };
},
[UPDATE_PROPERTY_VALUE](state, { property, value }) {

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

@ -21,7 +21,8 @@
src: url(ostrich-black.ttf);
}
body{
font-family:Arial;
/* Arial doesn't exist on Linux. Liberation Sans is the default sans-serif there. */
font-family:Arial, "Liberation Sans";
font-size: 36px;
}
div {

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

@ -1,34 +1,15 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* global getURL */
"use strict";
requestLongerTimeout(2);
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
const FONTS = [{
name: "Ostrich Sans Medium",
remote: true,
url: URL_ROOT + "ostrich-regular.ttf",
cssName: "bar"
}, {
name: "Ostrich Sans Black",
remote: true,
url: URL_ROOT + "ostrich-black.ttf",
cssName: "bar"
}, {
name: "Ostrich Sans Black",
remote: true,
url: URL_ROOT + "ostrich-black.ttf",
cssName: "bar"
}, {
name: "Ostrich Sans Medium",
remote: true,
url: URL_ROOT + "ostrich-regular.ttf",
cssName: "barnormal"
}];
add_task(async function() {
await pushPref("devtools.inspector.fonteditor.enabled", true);
const { inspector, view } = await openFontInspectorForURL(TEST_URI);
ok(!!view, "Font inspector document is alive.");
@ -42,40 +23,33 @@ function isRemote(fontLi) {
return fontLi.querySelector(".font-origin").classList.contains("remote");
}
function testBodyFonts(inspector, viewDoc) {
async function testBodyFonts(inspector, viewDoc) {
await selectNode("body", inspector);
const lis = getUsedFontsEls(viewDoc);
is(lis.length, 5, "Found 5 fonts");
for (let i = 0; i < FONTS.length; i++) {
const li = lis[i];
const font = FONTS[i];
is(getName(li), font.name, `font ${i} right font name`);
is(isRemote(li), font.remote, `font ${i} remote value correct`);
is(li.querySelector(".font-origin").textContent, font.url, `font ${i} url correct`);
}
// test that the bold and regular fonts have different previews
const regSrc = lis[0].querySelector(".font-preview").src;
const boldSrc = lis[1].querySelector(".font-preview").src;
isnot(regSrc, boldSrc, "preview for bold font is different from regular");
// test system font
const localFontName = getName(lis[4]);
const localFontName = getName(lis[0]);
// On Linux test machines, the Arial font doesn't exist.
// The fallback is "Liberation Sans"
is(lis.length, 1, "Found 1 font on BODY");
ok((localFontName == "Arial") || (localFontName == "Liberation Sans"),
"local font right font name");
ok(!isRemote(lis[4]), "local font is local");
ok(!isRemote(lis[0]), "local font is local");
}
async function testDivFonts(inspector, viewDoc) {
const updated = inspector.once("fontinspector-updated");
await selectNode("div", inspector);
await updated;
const font = {
name: "Ostrich Sans Medium",
remote: true,
url: URL_ROOT + "ostrich-regular.ttf",
};
const lis = getUsedFontsEls(viewDoc);
const li = lis[0];
is(lis.length, 1, "Found 1 font on DIV");
is(getName(lis[0]), "Ostrich Sans Medium", "The DIV font has the right name");
is(getName(li), font.name, "The DIV font has the right name");
is(isRemote(li), font.remote, `font remote value correct`);
is(getURL(li), font.url, `font url correct`);
}

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

@ -10,8 +10,10 @@
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
add_task(async function() {
const { view } = await openFontInspectorForURL(TEST_URI);
await pushPref("devtools.inspector.fonteditor.enabled", true);
const { view, inspector } = await openFontInspectorForURL(TEST_URI);
const viewDoc = view.document;
await selectNode("div", inspector);
const fontEl = getUsedFontsEls(viewDoc)[0];
const linkEl = fontEl.querySelector(".font-origin");

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

@ -10,8 +10,11 @@
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
add_task(async function() {
const {view} = await openFontInspectorForURL(TEST_URI);
await pushPref("devtools.inspector.fonteditor.enabled", true);
const { view, inspector } = await openFontInspectorForURL(TEST_URI);
const viewDoc = view.document;
await selectNode("div", inspector);
await expandOtherFontsAccordion(viewDoc);
const previews = viewDoc.querySelectorAll("#font-container .font-preview");
const initialPreviews = [...previews].map(p => p.src);

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

@ -9,11 +9,14 @@
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
add_task(async function() {
const { view } = await openFontInspectorForURL(TEST_URI);
await pushPref("devtools.inspector.fonteditor.enabled", true);
const { view, inspector } = await openFontInspectorForURL(TEST_URI);
const viewDoc = view.document;
await selectNode("div", inspector);
await expandOtherFontsAccordion(viewDoc);
info("Checking that the css font-face rule is collapsed by default");
let fontEl = getUsedFontsEls(viewDoc)[0];
let fontEl = getOtherFontsEls(viewDoc)[0];
let codeEl = fontEl.querySelector(".font-css-code");
is(codeEl.textContent, "@font-face {}", "The font-face rule is collapsed");
@ -21,7 +24,8 @@ add_task(async function() {
let onExpanded = BrowserTestUtils.waitForCondition(() => {
return codeEl.textContent === `@font-face {
font-family: bar;
src: url("bad/font/name.ttf"), url("ostrich-regular.ttf") format("truetype");
src: url("ostrich-black.ttf");
font-weight: bold;
}`;
}, "Waiting for the font-face rule 1");
@ -32,14 +36,14 @@ add_task(async function() {
ok(true, "Font-face rule is now expanded");
info("Expanding another rule by clicking on the [...] icon instead");
fontEl = getUsedFontsEls(viewDoc)[1];
fontEl = getOtherFontsEls(viewDoc)[1];
codeEl = fontEl.querySelector(".font-css-code");
onExpanded = BrowserTestUtils.waitForCondition(() => {
return codeEl.textContent === `@font-face {
font-family: bar;
src: url("ostrich-black.ttf");
font-weight: bold;
font-weight: 800;
}`;
}, "Waiting for the font-face rule 2");

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

@ -11,6 +11,7 @@
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
add_task(async function() {
await pushPref("devtools.inspector.fonteditor.enabled", true);
const { inspector, view } = await openFontInspectorForURL(TEST_URI);
const viewDoc = view.document;

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

@ -8,6 +8,7 @@
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
add_task(async function() {
await pushPref("devtools.inspector.fonteditor.enabled", true);
// Enable the feature, since it's off by default for now.
await pushPref("devtools.inspector.fonthighlighter.enabled", true);
@ -40,7 +41,7 @@ add_task(async function() {
`${expectedSelectionChangeEvents[i]} selectionchange events detected on mouseover`);
// Simulating a mouse out event on the font name and expecting a selectionchange.
const otherEl = fontEls[i].querySelector(".font-preview");
const otherEl = fontEls[i].querySelector(".font-origin");
onEvents = waitForNSelectionEvents(tab, 1);
EventUtils.synthesizeMouse(otherEl, 2, 2, {type: "mouseover"}, viewDoc.defaultView);
await onEvents;

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

@ -18,23 +18,27 @@ registerCleanupFunction(() => {
});
add_task(async function() {
await pushPref("devtools.inspector.fonteditor.enabled", true);
const { inspector, view } = await openFontInspectorForURL(TEST_URI);
const { document: doc } = view;
const viewDoc = view.document;
await selectNode(".normal-text", inspector);
await expandOtherFontsAccordion(viewDoc);
const otherFontsEls = getOtherFontsEls(viewDoc);
const fontEl = otherFontsEls[0];
// Store the original preview URI for later comparison.
const originalURI = doc.querySelector("#font-container .font-preview").src;
const originalURI = fontEl.querySelector(".font-preview").src;
const newTheme = originalTheme === "light" ? "dark" : "light";
info(`Original theme was '${originalTheme}'.`);
await setThemeAndWaitForUpdate(newTheme, inspector);
isnot(doc.querySelector("#font-container .font-preview").src, originalURI,
isnot(fontEl.querySelector(".font-preview").src, originalURI,
"The preview image changed with the theme.");
await setThemeAndWaitForUpdate(originalTheme, inspector);
is(doc.querySelector("#font-container .font-preview").src, originalURI,
is(fontEl.querySelector(".font-preview").src, originalURI,
"The preview image is correct after the original theme was restored.");
});

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

@ -24,9 +24,10 @@ registerCleanupFunction(() => {
*/
var _selectNode = selectNode;
selectNode = async function(node, inspector, reason) {
const onUpdated = inspector.once("fontinspector-updated");
const onInspectorUpdated = inspector.once("fontinspector-updated");
const onEditorUpdated = inspector.once("fonteditor-updated");
await _selectNode(node, inspector, reason);
await onUpdated;
await Promise.all([onInspectorUpdated, onEditorUpdated]);
};
/**
@ -96,7 +97,7 @@ async function updatePreviewText(view, text) {
* @return {Array}
*/
function getUsedFontsEls(viewDoc) {
return viewDoc.querySelectorAll("#font-container > .fonts-list > li");
return viewDoc.querySelectorAll("#font-editor .fonts-list li");
}
/**
@ -139,3 +140,15 @@ function getOtherFontsEls(viewDoc) {
function getName(fontEl) {
return fontEl.querySelector(".font-name").textContent;
}
/**
* Given a font element, return the font's URL.
*
* @param {DOMNode} fontEl
* The font element.
* @return {String}
* The URL where the font was loaded from as shown in the UI.
*/
function getURL(fontEl) {
return fontEl.querySelector(".font-origin").textContent;
}

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

@ -79,6 +79,14 @@ exports.fontOptions = {
previewText: PropTypes.string,
};
const fontFamilies = {
// Font family names used on the selected element
used: PropTypes.arrayOf(PropTypes.string),
// Font family names declared but not used on the selected element
notUsed: PropTypes.arrayOf(PropTypes.string),
};
exports.fontEditor = {
// Variable font axes and their values
axes: PropTypes.object,
@ -87,7 +95,10 @@ exports.fontEditor = {
// of a fontVariationInstance
customInstanceValues: PropTypes.array,
// Fonts applicable to selected element
// Font families declared on this element
families: PropTypes.shape(fontFamilies),
// Fonts used on the selected element whose family names are declared in CSS font-family
fonts: PropTypes.arrayOf(PropTypes.shape(font)),
// Font variation instance currently selected

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

@ -56,7 +56,7 @@ module.exports = {
let axes = {};
const keywords = ["initial", "normal", "inherit", "unset"];
if (keywords.includes(string.trim())) {
if (!string || keywords.includes(string.trim())) {
return axes;
}
@ -67,6 +67,11 @@ module.exports = {
.reduce((acc, pair) => {
// Tags are always in quotes. Split by quote and filter excessive whitespace.
pair = pair.split(/["']/).filter(part => part.trim() !== "");
// Guard against malformed input that may have slipped through.
if (pair.length === 0) {
return acc;
}
const tag = pair[0];
const value = pair[1].trim();
// Axis tags shorter or longer than 4 characters are invalid. Whitespace is valid.

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

@ -31,4 +31,4 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Inspector')
BUG_COMPONENT = ('DevTools', 'Inspector')

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

@ -19,5 +19,5 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: JSON Viewer')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'JSON Viewer')

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

@ -1,121 +0,0 @@
# 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/.
# LOCALIZATION NOTE These strings are used inside the Web Console
# command line which is available from the Web Developer sub-menu
# -> 'Web Console'.
# These messages are displayed when an attempt is made to validate a
# page or a cache manifest using AppCacheUtils.jsm
# The correct localization of this file might be to keep it in
# English, or another language commonly spoken among web developers.
# You want to make that choice consistent across the developer tools.
# A good criteria is the language in which you'd find the best
# documentation on web development on the web.
# LOCALIZATION NOTE (noManifest): the specified page has no cache manifest.
noManifest=The specified page has no manifest.
# LOCALIZATION NOTE (notUTF8): the associated cache manifest has a character
# encoding that is not UTF-8. Parameters: %S is the current encoding.
notUTF8=Manifest has a character encoding of %S. Manifests must have the utf-8 character encoding.
# LOCALIZATION NOTE (badMimeType): the associated cache manifest has a
# mimetype that is not text/cache-manifest. Parameters: %S is the current
# mimetype.
badMimeType=Manifest has a mimetype of %S. Manifests must have a mimetype of text/cache-manifest.
# LOCALIZATION NOTE (duplicateURI): the associated cache manifest references
# the same URI from multiple locations. Parameters: %1$S is the URI, %2$S is a
# list of references to this URI.
duplicateURI=URI %1$S is referenced in multiple locations. This is not allowed: %2$S.
# LOCALIZATION NOTE (networkBlocksURI, fallbackBlocksURI): the associated
# cache manifest references the same URI in the NETWORK (or FALLBACK) section
# as it does in other sections. Parameters: %1$S is the line number, %2$S is
# the resource name, %3$S is the line number, %4$S is the resource name, %5$S
# is the section name.
networkBlocksURI=NETWORK section line %1$S (%2$S) prevents caching of line %3$S (%4$S) in the %5$S section.
fallbackBlocksURI=FALLBACK section line %1$S (%2$S) prevents caching of line %3$S (%4$S) in the %5$S section.
# LOCALIZATION NOTE (fileChangedButNotManifest): the associated cache manifest
# references a URI that has a file modified after the cache manifest.
# Parameters: %1$S is the resource name, %2$S is the cache manifest, %3$S is
# the line number.
fileChangedButNotManifest=The file %1$S was modified after %2$S. Unless the text in the manifest file is changed the cached version will be used instead at line %3$S.
# LOCALIZATION NOTE (cacheControlNoStore): the specified page has a header
# preventing caching or storing information. Parameters: %1$S is the resource
# name, %2$S is the line number.
cacheControlNoStore=%1$S has cache-control set to no-store. This will prevent the application cache from storing the file at line %2$S.
# LOCALIZATION NOTE (notAvailable): the specified resource is not available.
# Parameters: %1$S is the resource name, %2$S is the line number.
notAvailable=%1$S points to a resource that is not available at line %2$S.
# LOCALIZATION NOTE (invalidURI): it's used when an invalid URI is passed to
# the appcache.
invalidURI=The URI passed to AppCacheUtils is invalid.
# LOCALIZATION NOTE (noResults): it's used when a search returns no results.
noResults=Your search returned no results.
# LOCALIZATION NOTE (cacheDisabled): it's used when the cache is disabled and
# an attempt is made to view offline data.
cacheDisabled=Your disk cache is disabled. Please set browser.cache.disk.enable to true in about:config and try again.
# LOCALIZATION NOTE (firstLineMustBeCacheManifest): the associated cache
# manifest has a first line that is not "CACHE MANIFEST". Parameters: %S is
# the line number.
firstLineMustBeCacheManifest=The first line of the manifest must be “CACHE MANIFEST” at line %S.
# LOCALIZATION NOTE (cacheManifestOnlyFirstLine2): the associated cache
# manifest has "CACHE MANIFEST" on a line other than the first line.
# Parameters: %S is the line number where "CACHE MANIFEST" appears.
cacheManifestOnlyFirstLine2=“CACHE MANIFEST” is only valid on the first line but was found at line %S.
# LOCALIZATION NOTE (asteriskInWrongSection2): the associated cache manifest
# has an asterisk (*) in a section other than the NETWORK section. Parameters:
# %1$S is the section name, %2$S is the line number.
asteriskInWrongSection2=Asterisk (*) incorrectly used in the %1$S section at line %2$S. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section. Otherwise such URIs will be treated as unavailable. Other uses of the * character are prohibited.
# LOCALIZATION NOTE (escapeSpaces1): the associated cache manifest has a space
# in a URI. Spaces must be replaced with %20. Parameters: %S is the line
# number where this error occurs.
# %% will be displayed as a single % character (% is commonly used to define
# format specifiers, so it needs to be escaped).
escapeSpaces1=Spaces in URIs need to be replaced with %%20 at line %S.
# LOCALIZATION NOTE (slashDotDotSlashBad): the associated cache manifest has a
# URI containing /../, which is invalid. Parameters: %S is the line number
# where this error occurs.
slashDotDotSlashBad=/../ is not a valid URI prefix at line %S.
# LOCALIZATION NOTE (tooManyDotDotSlashes): the associated cache manifest has
# a URI containing too many ../ operators. Too many of these operators mean
# that the file would be below the root of the site, which is not possible.
# Parameters: %S is the line number where this error occurs.
tooManyDotDotSlashes=Too many dot dot slash operators (../) at line %S.
# LOCALIZATION NOTE (fallbackUseSpaces): the associated cache manifest has a
# FALLBACK section containing more or less than the standard two URIs
# separated by a single space. Parameters: %S is the line number where this
# error occurs.
fallbackUseSpaces=Only two URIs separated by spaces are allowed in the FALLBACK section at line %S.
# LOCALIZATION NOTE (fallbackAsterisk2): the associated cache manifest has a
# FALLBACK section that attempts to use an asterisk (*) as a wildcard. In this
# section the URI is simply a path prefix. Parameters: %S is the line number
# where this error occurs.
fallbackAsterisk2=Asterisk (*) incorrectly used in the FALLBACK section at line %S. URIs in the FALLBACK section simply need to match a prefix of the request URI.
# LOCALIZATION NOTE (settingsBadValue): the associated cache manifest has a
# SETTINGS section containing something other than the valid "prefer-online"
# or "fast". Parameters: %S is the line number where this error occurs.
settingsBadValue=The SETTINGS section may only contain a single value, “prefer-online” or “fast” at line %S.
# LOCALIZATION NOTE (invalidSectionName): the associated cache manifest
# contains an invalid section name. Parameters: %1$S is the section name, %2$S
# is the line number.
invalidSectionName=Invalid section name (%1$S) at line %2$S.

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

@ -4,7 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Memory')
BUG_COMPONENT = ('DevTools', 'Memory')
DIRS += [
'actions',

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

@ -44,4 +44,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')

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

@ -15,4 +15,4 @@ XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Netmonitor')
BUG_COMPONENT = ('DevTools', 'Netmonitor')

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

@ -17,4 +17,4 @@ DevToolsModules(
MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')

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

@ -18,4 +18,4 @@ BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')

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

@ -29,4 +29,4 @@ XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Responsive Design Mode')
BUG_COMPONENT = ('DevTools', 'Responsive Design Mode')

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

@ -12,5 +12,5 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Scratchpad')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Scratchpad')

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

@ -9,5 +9,5 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: WebGL Shader Editor')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'WebGL Shader Editor')

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

@ -1,617 +0,0 @@
/* 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/. */
/**
* validateManifest() warns of the following errors:
* - No manifest specified in page
* - Manifest is not utf-8
* - Manifest mimetype not text/cache-manifest
* - Manifest does not begin with "CACHE MANIFEST"
* - Page modified since appcache last changed
* - Duplicate entries
* - Conflicting entries e.g. in both CACHE and NETWORK sections or in cache
* but blocked by FALLBACK namespace
* - Detect referenced files that are not available
* - Detect referenced files that have cache-control set to no-store
* - Wildcards used in a section other than NETWORK
* - Spaces in URI not replaced with %20
* - Completely invalid URIs
* - Too many dot dot slash operators
* - SETTINGS section is valid
* - Invalid section name
* - etc.
*/
"use strict";
var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm", {});
var { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
var { gDevTools } = require("devtools/client/framework/devtools");
var Services = require("Services");
var { globals } = require("devtools/shared/builtin-modules");
this.EXPORTED_SYMBOLS = ["AppCacheUtils"];
function AppCacheUtils(documentOrUri) {
this._parseManifest = this._parseManifest.bind(this);
if (documentOrUri) {
if (typeof documentOrUri == "string") {
this.uri = documentOrUri;
}
if (/HTMLDocument/.test(documentOrUri.toString())) {
this.doc = documentOrUri;
}
}
}
AppCacheUtils.prototype = {
get cachePath() {
return "";
},
validateManifest: function ACU_validateManifest() {
return new Promise((resolve, reject) => {
this.errors = [];
// Check for missing manifest.
this._getManifestURI().then(manifestURI => {
this.manifestURI = manifestURI;
if (!this.manifestURI) {
this._addError(0, "noManifest");
resolve(this.errors);
}
this._getURIInfo(this.manifestURI).then(uriInfo => {
this._parseManifest(uriInfo).then(() => {
// Sort errors by line number.
this.errors.sort(function(a, b) {
return a.line - b.line;
});
resolve(this.errors);
});
});
});
});
},
_parseManifest: function ACU__parseManifest(uriInfo) {
return new Promise((resolve, reject) => {
const manifestName = uriInfo.name;
const manifestLastModified = new Date(uriInfo.responseHeaders["last-modified"]);
if (uriInfo.charset.toLowerCase() != "utf-8") {
this._addError(0, "notUTF8", uriInfo.charset);
}
if (uriInfo.mimeType != "text/cache-manifest") {
this._addError(0, "badMimeType", uriInfo.mimeType);
}
const parser = new ManifestParser(uriInfo.text, this.manifestURI);
const parsed = parser.parse();
if (parsed.errors.length > 0) {
this.errors.push.apply(this.errors, parsed.errors);
}
// Check for duplicate entries.
const dupes = {};
for (const parsedUri of parsed.uris) {
dupes[parsedUri.uri] = dupes[parsedUri.uri] || [];
dupes[parsedUri.uri].push({
line: parsedUri.line,
section: parsedUri.section,
original: parsedUri.original
});
}
for (const [uri, value] of Object.entries(dupes)) {
if (value.length > 1) {
this._addError(0, "duplicateURI", uri, JSON.stringify(value));
}
}
// Loop through network entries making sure that fallback and cache don't
// contain uris starting with the network uri.
for (const neturi of parsed.uris) {
if (neturi.section == "NETWORK") {
for (const parsedUri of parsed.uris) {
if (parsedUri.section !== "NETWORK" &&
parsedUri.uri.startsWith(neturi.uri)) {
this._addError(neturi.line, "networkBlocksURI", neturi.line,
neturi.original, parsedUri.line, parsedUri.original,
parsedUri.section);
}
}
}
}
// Loop through fallback entries making sure that fallback and cache don't
// contain uris starting with the network uri.
for (const fb of parsed.fallbacks) {
for (const parsedUri of parsed.uris) {
if (parsedUri.uri.startsWith(fb.namespace)) {
this._addError(fb.line, "fallbackBlocksURI", fb.line,
fb.original, parsedUri.line, parsedUri.original,
parsedUri.section);
}
}
}
// Check that all resources exist and that their cach-control headers are
// not set to no-store.
let current = -1;
for (let i = 0, len = parsed.uris.length; i < len; i++) {
const parsedUri = parsed.uris[i];
this._getURIInfo(parsedUri.uri).then(uriInfo => {
current++;
if (uriInfo.success) {
// Check that the resource was not modified after the manifest was last
// modified. If it was then the manifest file should be refreshed.
const resourceLastModified =
new Date(uriInfo.responseHeaders["last-modified"]);
if (manifestLastModified < resourceLastModified) {
this._addError(parsedUri.line, "fileChangedButNotManifest",
uriInfo.name, manifestName, parsedUri.line);
}
// If cache-control: no-store the file will not be added to the
// appCache.
if (uriInfo.nocache) {
this._addError(parsedUri.line, "cacheControlNoStore",
parsedUri.original, parsedUri.line);
}
} else if (parsedUri.original !== "*") {
this._addError(parsedUri.line, "notAvailable",
parsedUri.original, parsedUri.line);
}
if (current == len - 1) {
resolve();
}
});
}
});
},
_getURIInfo: function ACU__getURIInfo(uri) {
return new Promise((resolve, reject) => {
const inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
let buffer = "";
const channel = NetUtil.newChannel({
uri: uri,
loadUsingSystemPrincipal: true,
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
});
// Avoid the cache:
channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
channel.asyncOpen2({
onStartRequest: function(request, context) {
// This empty method is needed in order for onDataAvailable to be
// called.
},
onDataAvailable: function(request, context, stream, offset, count) {
request.QueryInterface(Ci.nsIHttpChannel);
inputStream.init(stream);
buffer = buffer.concat(inputStream.read(count));
},
onStopRequest: function onStartRequest(request, context, statusCode) {
if (statusCode === 0) {
request.QueryInterface(Ci.nsIHttpChannel);
const result = {
name: request.name,
success: request.requestSucceeded,
status: request.responseStatus + " - " + request.responseStatusText,
charset: request.contentCharset || "utf-8",
mimeType: request.contentType,
contentLength: request.contentLength,
nocache: request.isNoCacheResponse() || request.isNoStoreResponse(),
prePath: request.URI.prePath + "/",
text: buffer
};
result.requestHeaders = {};
request.visitRequestHeaders(function(header, value) {
result.requestHeaders[header.toLowerCase()] = value;
});
result.responseHeaders = {};
request.visitResponseHeaders(function(header, value) {
result.responseHeaders[header.toLowerCase()] = value;
});
resolve(result);
} else {
resolve({
name: request.name,
success: false
});
}
}
});
});
},
listEntries: function ACU_show(searchTerm) {
if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
throw new Error(l10n.GetStringFromName("cacheDisabled"));
}
const entries = [];
const appCacheStorage = Services.cache2.appCacheStorage(Services.loadContextInfo.default, null);
appCacheStorage.asyncVisitStorage({
onCacheStorageInfo: function() {},
onCacheEntryInfo: function(aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime) {
const lowerKey = aURI.asciiSpec.toLowerCase();
if (searchTerm && !lowerKey.includes(searchTerm.toLowerCase())) {
return;
}
if (aIdEnhance) {
aIdEnhance += ":";
}
const entry = {
"deviceID": "offline",
"key": aIdEnhance + aURI.asciiSpec,
"fetchCount": aFetchCount,
"lastFetched": null,
"lastModified": new Date(aLastModifiedTime * 1000),
"expirationTime": new Date(aExpirationTime * 1000),
"dataSize": aDataSize
};
entries.push(entry);
return true;
}
}, true);
if (entries.length === 0) {
throw new Error(l10n.GetStringFromName("noResults"));
}
return entries;
},
viewEntry: function ACU_viewEntry(key) {
const win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
const url = "about:cache-entry?storage=appcache&context=&eid=&uri=" + key;
win.openTrustedLinkIn(url, "tab");
},
clearAll: function ACU_clearAll() {
if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
throw new Error(l10n.GetStringFromName("cacheDisabled"));
}
const appCacheStorage = Services.cache2.appCacheStorage(Services.loadContextInfo.default, null);
appCacheStorage.asyncEvictStorage({
onCacheEntryDoomed: function(result) {}
});
},
_getManifestURI: function ACU__getManifestURI() {
return new Promise((resolve, reject) => {
const getURI = () => {
const htmlNode = this.doc.querySelector("html[manifest]");
if (htmlNode) {
const pageUri = this.doc.location ? this.doc.location.href : this.uri;
const manifestURI = htmlNode.getAttribute("manifest");
const originRegExp = new RegExp(/([a-z]*:\/\/[^/]*\/)/);
if (originRegExp.test(manifestURI)) {
return manifestURI;
} else if (manifestURI.startsWith("/")) {
return pageUri.match(originRegExp)[0] + manifestURI.substring(1);
}
return pageUri.substring(0, pageUri.lastIndexOf("/") + 1) + manifestURI;
}
};
if (this.doc) {
const uri = getURI();
return resolve(uri);
}
this._getURIInfo(this.uri).then(uriInfo => {
if (uriInfo.success) {
const html = uriInfo.text;
const parser = _DOMParser;
this.doc = parser.parseFromString(html, "text/html");
const uri = getURI();
resolve(uri);
} else {
this.errors.push({
line: 0,
msg: l10n.GetStringFromName("invalidURI")
});
}
});
});
},
_addError: function ACU__addError(line, l10nString, ...params) {
let msg;
if (params) {
msg = l10n.formatStringFromName(l10nString, params, params.length);
} else {
msg = l10n.GetStringFromName(l10nString);
}
this.errors.push({
line: line,
msg: msg
});
},
};
/**
* We use our own custom parser because we need far more detailed information
* than the system manifest parser provides.
*
* @param {String} manifestText
* The text content of the manifest file.
* @param {String} manifestURI
* The URI of the manifest file. This is used in calculating the path of
* relative URIs.
*/
function ManifestParser(manifestText, manifestURI) {
this.manifestText = manifestText;
this.origin = manifestURI.substr(0, manifestURI.lastIndexOf("/") + 1)
.replace(" ", "%20");
}
ManifestParser.prototype = {
parse: function OCIMP_parse() {
const lines = this.manifestText.split(/\r?\n/);
const fallbacks = this.fallbacks = [];
const settings = this.settings = [];
const errors = this.errors = [];
const uris = this.uris = [];
this.currSection = "CACHE";
for (let i = 0; i < lines.length; i++) {
const text = this.text = lines[i].trim();
this.currentLine = i + 1;
if (i === 0 && text !== "CACHE MANIFEST") {
this._addError(1, "firstLineMustBeCacheManifest", 1);
}
// Ignore comments
if (/^#/.test(text) || !text.length) {
continue;
}
if (text == "CACHE MANIFEST") {
if (this.currentLine != 1) {
this._addError(this.currentLine, "cacheManifestOnlyFirstLine2",
this.currentLine);
}
continue;
}
if (this._maybeUpdateSectionName()) {
continue;
}
switch (this.currSection) {
case "CACHE":
case "NETWORK":
this.parseLine();
break;
case "FALLBACK":
this.parseFallbackLine();
break;
case "SETTINGS":
this.parseSettingsLine();
break;
}
}
return {
uris: uris,
fallbacks: fallbacks,
settings: settings,
errors: errors
};
},
parseLine: function OCIMP_parseLine() {
let text = this.text;
if (text.includes("*")) {
if (this.currSection != "NETWORK" || text.length != 1) {
this._addError(this.currentLine, "asteriskInWrongSection2",
this.currSection, this.currentLine);
return;
}
}
if (/\s/.test(text)) {
this._addError(this.currentLine, "escapeSpaces1", this.currentLine);
text = text.replace(/\s/g, "%20");
}
if (text[0] == "/") {
if (text.substr(0, 4) == "/../") {
this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
} else {
this.uris.push(this._wrapURI(this.origin + text.substring(1)));
}
} else if (text.substr(0, 2) == "./") {
this.uris.push(this._wrapURI(this.origin + text.substring(2)));
} else if (text.substr(0, 4) == "http") {
this.uris.push(this._wrapURI(text));
} else {
let origin = this.origin;
let path = text;
while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) {
const trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1;
origin = origin.substr(0, trimIdx);
path = path.substr(3);
}
if (path.substr(0, 3) == "../") {
this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine);
return;
}
if (/^https?:\/\//.test(path)) {
this.uris.push(this._wrapURI(path));
return;
}
this.uris.push(this._wrapURI(origin + path));
}
},
parseFallbackLine: function OCIMP_parseFallbackLine() {
const split = this.text.split(/\s+/);
const origURI = this.text;
if (split.length != 2) {
this._addError(this.currentLine, "fallbackUseSpaces", this.currentLine);
return;
}
let [ namespace, fallback ] = split;
if (namespace.includes("*")) {
this._addError(this.currentLine, "fallbackAsterisk2", this.currentLine);
}
if (/\s/.test(namespace)) {
this._addError(this.currentLine, "escapeSpaces1", this.currentLine);
namespace = namespace.replace(/\s/g, "%20");
}
if (namespace.substr(0, 4) == "/../") {
this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
}
if (namespace.substr(0, 2) == "./") {
namespace = this.origin + namespace.substring(2);
}
if (namespace.substr(0, 4) != "http") {
let origin = this.origin;
let path = namespace;
while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) {
const trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1;
origin = origin.substr(0, trimIdx);
path = path.substr(3);
}
if (path.substr(0, 3) == "../") {
this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine);
}
if (/^https?:\/\//.test(path)) {
namespace = path;
} else {
if (path[0] == "/") {
path = path.substring(1);
}
namespace = origin + path;
}
}
this.text = fallback;
this.parseLine();
this.fallbacks.push({
line: this.currentLine,
original: origURI,
namespace: namespace,
fallback: fallback
});
},
parseSettingsLine: function OCIMP_parseSettingsLine() {
const text = this.text;
if (this.settings.length == 1 || !/prefer-online|fast/.test(text)) {
this._addError(this.currentLine, "settingsBadValue", this.currentLine);
return;
}
switch (text) {
case "prefer-online":
this.settings.push(this._wrapURI(text));
break;
case "fast":
this.settings.push(this._wrapURI(text));
break;
}
},
_wrapURI: function OCIMP__wrapURI(uri) {
return {
section: this.currSection,
line: this.currentLine,
uri: uri,
original: this.text
};
},
_addError: function OCIMP__addError(line, l10nString, ...params) {
let msg;
if (params) {
msg = l10n.formatStringFromName(l10nString, params, params.length);
} else {
msg = l10n.GetStringFromName(l10nString);
}
this.errors.push({
line: line,
msg: msg
});
},
_maybeUpdateSectionName: function OCIMP__maybeUpdateSectionName() {
let text = this.text;
if (text == text.toUpperCase() && text.charAt(text.length - 1) == ":") {
text = text.substr(0, text.length - 1);
switch (text) {
case "CACHE":
case "NETWORK":
case "FALLBACK":
case "SETTINGS":
this.currSection = text;
return true;
default:
this._addError(this.currentLine,
"invalidSectionName", text, this.currentLine);
return false;
}
}
},
};
XPCOMUtils.defineLazyGetter(this, "l10n", () => Services.strings
.createBundle("chrome://devtools/locale/appcacheutils.properties"));
XPCOMUtils.defineLazyGetter(this, "_DOMParser", function() {
return globals.DOMParser();
});

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

@ -20,7 +20,6 @@ DIRS += [
]
DevToolsModules(
'AppCacheUtils.jsm',
'autocomplete-popup.js',
'browser-loader.js',
'css-angle.js',
@ -58,7 +57,7 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')
with Files('components/**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Shared Components')
BUG_COMPONENT = ('DevTools', 'Shared Components')

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

@ -11,6 +11,13 @@ const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
// opened we make use of setTimeout() to create tool active times.
const TOOL_DELAY = 200;
var animationPanelId;
if (Services.prefs.getBoolPref("devtools.new-animationinspector.enabled")) {
animationPanelId = "newanimationinspector";
} else {
animationPanelId = "animationinspector";
}
const DATA = [
{
timestamp: null,
@ -20,6 +27,17 @@ const DATA = [
value: null,
extra: {
oldpanel: "computedview",
newpanel: animationPanelId
}
},
{
timestamp: null,
category: "devtools.main",
method: "sidepanel_changed",
object: "inspector",
value: null,
extra: {
oldpanel: animationPanelId,
newpanel: "fontinspector"
}
},
@ -53,6 +71,17 @@ const DATA = [
value: null,
extra: {
oldpanel: "computedview",
newpanel: animationPanelId
}
},
{
timestamp: null,
category: "devtools.main",
method: "sidepanel_changed",
object: "inspector",
value: null,
extra: {
oldpanel: animationPanelId,
newpanel: "fontinspector"
}
},
@ -108,7 +137,7 @@ function testSidebar(toolbox) {
const inspector = toolbox.getCurrentPanel();
let sidebarTools = ["computedview", "layoutview", "fontinspector",
"animationinspector"];
animationPanelId];
// Concatenate the array with itself so that we can open each tool twice.
sidebarTools = [...sidebarTools, ...sidebarTools];

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

@ -20,4 +20,4 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Source Editor')
BUG_COMPONENT = ('DevTools', 'Source Editor')

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

@ -11,5 +11,5 @@ DevToolsModules(
'ui.js'
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Storage Inspector')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Storage Inspector')

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

@ -15,5 +15,5 @@ DevToolsModules(
'StyleSheetEditor.jsm',
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Style Editor')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Style Editor')

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

@ -9,5 +9,5 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Web Audio Editor')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Web Audio Editor')

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

@ -29,4 +29,4 @@ DevToolsModules(
'webconsole-output-wrapper.js',
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Console')
BUG_COMPONENT = ('DevTools', 'Console')

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

@ -19,4 +19,4 @@ MOCHITEST_CHROME_MANIFESTS += [
]
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: WebIDE')
BUG_COMPONENT = ('DevTools', 'WebIDE')

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

@ -28,22 +28,22 @@ if CONFIG['MOZ_BUILD_APP'] == 'browser':
export('DIST_SUBDIR')
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')
with Files('docs/**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')
with Files('docs/tools/memory-panel.md'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Memory')
BUG_COMPONENT = ('DevTools', 'Memory')
with Files('docs/tools/debugger-panel.md'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Debugger')
BUG_COMPONENT = ('DevTools', 'Debugger')
with Files('docs/backend/debugger-api.md'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Debugger')
BUG_COMPONENT = ('DevTools', 'Debugger')
with Files('docs/tools/http-inspector.md'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Console')
BUG_COMPONENT = ('DevTools', 'Console')
with Files('docs/tools/inspector-panel.md'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Inspector')
BUG_COMPONENT = ('DevTools', 'Inspector')

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

@ -115,10 +115,12 @@ var AnimationPlayerActor = protocol.ActorClassWithSpec(animationPlayerSpec, {
return pseudo.type === "::before" ? treeWalker.firstChild() : treeWalker.lastChild();
},
get document() {
return this.node.ownerDocument;
},
get window() {
// ownerGlobal doesn't exist in content privileged windows.
// eslint-disable-next-line mozilla/use-ownerGlobal
return this.node.ownerDocument.defaultView;
return this.document.defaultView;
},
/**
@ -866,6 +868,8 @@ exports.AnimationsActor = protocol.ActorClassWithSpec(animationsSpec, {
for (const { player } of actors) {
this.pauseSync(player);
}
return this.waitForNextFrame(actors);
},
/**
@ -877,6 +881,8 @@ exports.AnimationsActor = protocol.ActorClassWithSpec(animationsSpec, {
for (const { player } of actors) {
this.playSync(player);
}
return this.waitForNextFrame(actors);
},
/**
@ -907,7 +913,7 @@ exports.AnimationsActor = protocol.ActorClassWithSpec(animationsSpec, {
player.currentTime = (time - actor.createdTime) * player.playbackRate;
}
return Promise.resolve();
return this.waitForNextFrame(players);
},
/**
@ -993,4 +999,30 @@ exports.AnimationsActor = protocol.ActorClassWithSpec(animationsSpec, {
this.animationCreatedTimeMap.set(animation, createdTime);
}
},
/**
* Wait for next animation frame.
*
* @param {Array} actors
* @return {Promise} which waits for next frame
*/
waitForNextFrame(actors) {
const promises = actors.map(actor => {
const doc = actor.document;
const win = actor.window;
const timeAtCurrent = doc.timeline.currentTime;
return new Promise(resolve => {
win.requestAnimationFrame(() => {
if (timeAtCurrent === doc.timeline.currentTime) {
win.requestAnimationFrame(resolve);
} else {
resolve();
}
});
});
});
return Promise.all(promises);
},
});

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

@ -15,4 +15,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Inspector')
BUG_COMPONENT = ('DevTools', 'Inspector')

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

@ -68,37 +68,37 @@ DevToolsModules(
)
with Files('animation.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Animation Inspector')
BUG_COMPONENT = ('DevTools', 'Animation Inspector')
with Files('breakpoint.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Debugger')
BUG_COMPONENT = ('DevTools', 'Debugger')
with Files('css-properties.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: CSS Rules Inspector')
BUG_COMPONENT = ('DevTools', 'CSS Rules Inspector')
with Files('csscoverage.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Graphics Commandline and Toolbar')
BUG_COMPONENT = ('DevTools', 'Graphics Commandline and Toolbar')
with Files('memory.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Memory')
BUG_COMPONENT = ('DevTools', 'Memory')
with Files('performance*'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')
with Files('source.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Debugger')
BUG_COMPONENT = ('DevTools', 'Debugger')
with Files('storage.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Storage Inspector')
BUG_COMPONENT = ('DevTools', 'Storage Inspector')
with Files('stylesheets.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Style Editor')
BUG_COMPONENT = ('DevTools', 'Style Editor')
with Files('webaudio.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Web Audio Editor')
BUG_COMPONENT = ('DevTools', 'Web Audio Editor')
with Files('webconsole.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Console')
BUG_COMPONENT = ('DevTools', 'Console')
with Files('webgl.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: WebGL Shader Editor')
BUG_COMPONENT = ('DevTools', 'WebGL Shader Editor')

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

@ -301,6 +301,7 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
const fontFace = {
name: font.name,
CSSFamilyName: font.CSSFamilyName,
CSSGeneric: font.CSSGeneric || null,
srcIndex: font.srcIndex,
URI: font.URI,
format: font.format,

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

@ -22,4 +22,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')

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

@ -12,5 +12,5 @@ DevToolsModules(
'timeline.js',
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')

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

@ -281,6 +281,7 @@ exports.CSS_PROPERTIES = {
"scrollbarthumb-vertical",
"scrollbartrack-horizontal",
"scrollbartrack-vertical",
"scrollcorner",
"searchfield",
"separator",
"spinner",

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

@ -5,7 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Memory')
BUG_COMPONENT = ('DevTools', 'Memory')
if CONFIG['ENABLE_TESTS']:
DIRS += ['tests/gtest']

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

@ -76,4 +76,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')

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

@ -12,4 +12,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')

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

@ -1027,72 +1027,6 @@ nsDocShell::LoadURI(nsIURI* aURI,
nullptr); // No nsIRequest
}
NS_IMETHODIMP
nsDocShell::LoadStream(nsIInputStream* aStream, nsIURI* aURI,
const nsACString& aContentType,
const nsACString& aContentCharset,
nsIDocShellLoadInfo* aLoadInfo)
{
NS_ENSURE_ARG(aStream);
mAllowKeywordFixup = false;
// if the caller doesn't pass in a URI we need to create a dummy URI. necko
// currently requires a URI in various places during the load. Some consumers
// do as well.
nsCOMPtr<nsIURI> uri = aURI;
if (!uri) {
// HACK ALERT
nsresult rv = NS_OK;
// Make sure that the URI spec "looks" like a protocol and path...
// For now, just use a bogus protocol called "internal"
rv = NS_MutateURI(NS_SIMPLEURIMUTATOR_CONTRACTID)
.SetSpec(NS_LITERAL_CSTRING("internal:load-stream"))
.Finalize(uri);
if (NS_FAILED(rv)) {
return rv;
}
}
uint32_t loadType = LOAD_NORMAL;
nsCOMPtr<nsIPrincipal> triggeringPrincipal;
if (aLoadInfo) {
nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
(void)aLoadInfo->GetLoadType(&lt);
// Get the appropriate LoadType from nsIDocShellLoadInfo type
loadType = ConvertDocShellInfoLoadTypeToLoadType(lt);
aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
}
NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE);
mLoadType = loadType;
if (!triggeringPrincipal) {
triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
}
// build up a channel for this stream.
nsCOMPtr<nsIChannel> channel;
nsCOMPtr<nsIInputStream> stream = aStream;
nsresult rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
uri,
stream.forget(),
triggeringPrincipal,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
nsIContentPolicy::TYPE_OTHER,
aContentType,
aContentCharset);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
nsCOMPtr<nsIURILoader> uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID));
NS_ENSURE_TRUE(uriLoader, NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(DoChannelLoad(channel, uriLoader, false),
NS_ERROR_FAILURE);
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::CreateLoadInfo(nsIDocShellLoadInfo** aLoadInfo)
{
@ -13542,15 +13476,6 @@ nsDocShell::OnLinkClickSync(nsIContent* aContent,
CopyUTF8toUTF16(type, typeHint);
}
// Clone the URI now, in case a content policy or something messes
// with it under InternalLoad; we do _not_ want to change the URI
// our caller passed in.
nsCOMPtr<nsIURI> clonedURI;
aURI->Clone(getter_AddRefs(clonedURI));
if (!clonedURI) {
return NS_ERROR_OUT_OF_MEMORY;
}
// if the triggeringPrincipal is not passed explicitly, then we
// fall back to using doc->NodePrincipal() as the triggeringPrincipal.
nsCOMPtr<nsIPrincipal> triggeringPrincipal =
@ -13567,7 +13492,7 @@ nsDocShell::OnLinkClickSync(nsIContent* aContent,
flags |= INTERNAL_LOAD_FLAGS_IS_USER_TRIGGERED;
}
nsresult rv = InternalLoad(clonedURI, // New URI
nsresult rv = InternalLoad(aURI, // New URI
nullptr, // Original URI
Nothing(), // Let the protocol handler assign it
false, // LoadReplace

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

@ -91,31 +91,6 @@ interface nsIDocShell : nsIDocShellTreeItem
in unsigned long aLoadFlags,
in boolean firstParty);
/**
* Loads a given stream. This will give priority to loading the requested
* stream in the object implementing this interface. If it can't be loaded
* here however, the URL dispatched will go through its normal process of
* content loading.
*
* @param aStream - The input stream that provides access to the data
* to be loaded. This must be a blocking, threadsafe
* stream implementation.
* @param aURI - The URI representing the stream, or null.
* @param aContentType - The type (MIME) of data being loaded (empty if unknown).
* @param aContentCharset - The charset of the data being loaded (empty if unknown).
* @param aLoadInfo - This is the extended load info for this load. This
* most often will be null, but if you need to do
* additional setup for this load you can get a
* loadInfo object by calling createLoadInfo. Once
* you have this object you can set the needed
* properties on it and then pass it to loadStream.
*/
[noscript]void loadStream(in nsIInputStream aStream,
in nsIURI aURI,
in ACString aContentType,
in ACString aContentCharset,
in nsIDocShellLoadInfo aLoadInfo);
const long INTERNAL_LOAD_FLAGS_NONE = 0x0;
const long INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL = 0x1;
const long INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER = 0x2;

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

@ -5,7 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')
EXPORTS.mozilla += [
'AbstractTimelineMarker.h',

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

@ -29,10 +29,10 @@ with Files('browser/*tab_touch_events*'):
BUG_COMPONENT = ('Core', 'DOM: Events')
with Files('browser/*timelineMarkers*'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')
with Files('browser/*ua_emulation*'):
BUG_COMPONENT = ('Firefox', 'Developer Tools')
BUG_COMPONENT = ('DevTools', 'General')
with Files('chrome/*112564*'):
BUG_COMPONENT = ('Core', 'Networking: HTTP')

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

@ -859,19 +859,6 @@ Link::UnregisterFromHistory()
}
}
already_AddRefed<nsIURI>
Link::GetURIToMutate()
{
MOZ_ASSERT(false, "TODO: REMOVE THIS METHOD");
nsCOMPtr<nsIURI> uri(GetURI());
if (!uri) {
return nullptr;
}
nsCOMPtr<nsIURI> clone;
(void)uri->Clone(getter_AddRefs(clone));
return clone.forget();
}
void
Link::SetHrefAttribute(nsIURI *aURI)
{

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

@ -170,7 +170,6 @@ private:
*/
void UnregisterFromHistory();
already_AddRefed<nsIURI> GetURIToMutate();
void SetHrefAttribute(nsIURI *aURI);
void GetContentPolicyMimeTypeMedia(nsAttrValue& aAsAttr,

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

@ -221,7 +221,8 @@ Location::GetWritableURI(nsIURI** aURI, const nsACString* aNewRef)
}
if (!aNewRef) {
return uri->Clone(aURI);
uri.forget(aURI);
return NS_OK;
}
return uri->CloneWithNewRef(*aNewRef, aURI);

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

@ -863,8 +863,10 @@ BrowserElementChild.prototype = {
documentURI: documentURI,
text: elem.textContent.substring(0, kLongestReturnedString)};
}
if (elem instanceof Ci.nsIImageLoadingContent && elem.currentRequestFinalURI) {
return {uri: elem.currentRequestFinalURI.spec, documentURI: documentURI};
if (elem instanceof Ci.nsIImageLoadingContent &&
(elem.currentRequestFinalURI || elem.currentURI)) {
let uri = elem.currentRequestFinalURI || elem.currentURI;
return {uri: uri.spec, documentURI: documentURI};
}
if (ChromeUtils.getClassName(elem) === "HTMLImageElement") {
return {uri: elem.src, documentURI: documentURI};

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

@ -8,22 +8,22 @@ with Files("**"):
BUG_COMPONENT = ("Core", "DOM")
with Files("ChannelWrapper.webidl"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: Request Handling")
BUG_COMPONENT = ("WebExtensions", "Request Handling")
with Files("HeapSnapshot.webidl"):
BUG_COMPONENT = ("Firefox", "Developer Tools: Memory")
BUG_COMPONENT = ("DevTools", "Memory")
with Files("InspectorUtils.webidl"):
BUG_COMPONENT = ("Firefox", "Developer Tools: Inspector")
BUG_COMPONENT = ("DevTools", "Inspector")
with Files("MatchGlob.webidl"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: General")
BUG_COMPONENT = ("WebExtensions", "General")
with Files("MatchPattern.webidl"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: General")
BUG_COMPONENT = ("WebExtensions", "General")
with Files("WebExtension*.webidl"):
BUG_COMPONENT = ("Toolkit", "WebExtensions: General")
BUG_COMPONENT = ("WebExtensions", "General")
PREPROCESSED_WEBIDL_FILES = [
'ChromeUtils.webidl',

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

@ -1670,8 +1670,7 @@ HTMLFormElement::GetActionURL(nsIURI** aActionURL,
return NS_OK;
}
rv = docURI->Clone(getter_AddRefs(actionURL));
NS_ENSURE_SUCCESS(rv, rv);
actionURL = docURI;
} else {
nsCOMPtr<nsIURI> baseURL = GetBaseURI();
NS_ASSERTION(baseURL, "No Base URL found in Form Submit!\n");

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

@ -1376,16 +1376,7 @@ nsJSURI::Deserialize(const mozilla::ipc::URIParams& aParams)
nsJSURI::StartClone(mozilla::net::nsSimpleURI::RefHandlingEnum refHandlingMode,
const nsACString& newRef)
{
nsCOMPtr<nsIURI> baseClone;
if (mBaseURI) {
// Note: We preserve ref on *base* URI, regardless of ref handling mode.
nsresult rv = mBaseURI->Clone(getter_AddRefs(baseClone));
if (NS_FAILED(rv)) {
return nullptr;
}
}
nsJSURI* url = new nsJSURI(baseClone);
nsJSURI* url = new nsJSURI(mBaseURI);
SetRefOnClone(url, refHandlingMode, newRef);
return url;
}

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

@ -409,7 +409,7 @@ MediaResult
H264Converter::CheckForSPSChange(MediaRawData* aSample)
{
RefPtr<MediaByteBuffer> extra_data =
H264::ExtractExtraData(aSample);
aSample->mKeyframe ? H264::ExtractExtraData(aSample) : nullptr;
if (!H264::HasSPS(extra_data)) {
MOZ_ASSERT(mCanRecycleDecoder.isSome());
if (!*mCanRecycleDecoder) {
@ -423,14 +423,12 @@ H264Converter::CheckForSPSChange(MediaRawData* aSample)
// This scenario can only occur on Android with devices that can recycle a
// decoder.
if (!H264::HasSPS(aSample->mExtraData) ||
H264::CompareExtraData(aSample->mExtraData,
mOriginalExtraData)) {
H264::CompareExtraData(aSample->mExtraData, mOriginalExtraData)) {
return NS_OK;
}
extra_data = mOriginalExtraData = aSample->mExtraData;
}
if (H264::CompareExtraData(extra_data,
mCurrentConfig.mExtraData)) {
if (H264::CompareExtraData(extra_data, mCurrentConfig.mExtraData)) {
return NS_OK;
}

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