зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to inbound. a=merge CLOSED TREE
This commit is contained in:
Коммит
0a37599745
|
@ -11,3 +11,4 @@ skip-if = e10s
|
|||
[browser_test_textcaret.js]
|
||||
[browser_test_focus_browserui.js]
|
||||
[browser_test_focus_dialog.js]
|
||||
[browser_test_focus_urlbar.js]
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* import-globals-from ../../mochitest/states.js */
|
||||
/* import-globals-from ../../mochitest/role.js */
|
||||
loadScripts({ name: "states.js", dir: MOCHITESTS_DIR },
|
||||
{ name: "role.js", dir: MOCHITESTS_DIR });
|
||||
ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
|
||||
"resource://testing-common/PlacesTestUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
function isEventForAutocompleteItem(event) {
|
||||
return event.accessible.role == ROLE_COMBOBOX_OPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an autocomplete search to finish.
|
||||
* This is necessary to ensure predictable results, as these searches are
|
||||
* async. Pressing down arrow will use results from the previous input if the
|
||||
* search isn't finished yet.
|
||||
*/
|
||||
function waitForSearchFinish() {
|
||||
return BrowserTestUtils.waitForCondition(() =>
|
||||
(gURLBar.popupOpen && gURLBar.controller.searchStatus >=
|
||||
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH),
|
||||
"Waiting for search to complete");
|
||||
}
|
||||
|
||||
// Check that the URL bar manages accessibility focus appropriately.
|
||||
async function runTests() {
|
||||
registerCleanupFunction(async function() {
|
||||
await PlacesUtils.history.clear();
|
||||
});
|
||||
|
||||
await PlacesTestUtils.addVisits([
|
||||
{uri: makeURI("http://example1.com/blah")},
|
||||
{uri: makeURI("http://example2.com/blah")},
|
||||
{uri: makeURI("http://example1.com/")},
|
||||
{uri: makeURI("http://example2.com/")}
|
||||
]);
|
||||
|
||||
let focused = waitForEvent(EVENT_FOCUS,
|
||||
event => event.accessible.role == ROLE_ENTRY);
|
||||
gURLBar.focus();
|
||||
let event = await focused;
|
||||
let textBox = event.accessible;
|
||||
// Ensure the URL bar is ready for a new URL to be typed.
|
||||
// Sometimes, when this test runs, the existing text isn't selected when the
|
||||
// URL bar is focused. Pressing escape twice ensures that the popup is
|
||||
// closed and that the existing text is selected.
|
||||
EventUtils.synthesizeKey("KEY_Escape");
|
||||
EventUtils.synthesizeKey("KEY_Escape");
|
||||
|
||||
info("Ensuring no focus change when first text is typed");
|
||||
EventUtils.sendString("example");
|
||||
await waitForSearchFinish();
|
||||
// Wait a tick for a11y events to fire.
|
||||
await TestUtils.waitForTick();
|
||||
testStates(textBox, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring no focus change on backspace");
|
||||
EventUtils.synthesizeKey("KEY_Backspace");
|
||||
await waitForSearchFinish();
|
||||
// Wait a tick for a11y events to fire.
|
||||
await TestUtils.waitForTick();
|
||||
testStates(textBox, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring no focus change on text selection and delete");
|
||||
EventUtils.synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
|
||||
EventUtils.synthesizeKey("KEY_Delete");
|
||||
await waitForSearchFinish();
|
||||
// Wait a tick for a11y events to fire.
|
||||
await TestUtils.waitForTick();
|
||||
testStates(textBox, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring autocomplete focus on down arrow");
|
||||
focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
event = await focused;
|
||||
testStates(event.accessible, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring focus of another autocomplete item on down arrow");
|
||||
focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
event = await focused;
|
||||
testStates(event.accessible, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring focus of another autocomplete item on up arrow");
|
||||
focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
event = await focused;
|
||||
testStates(event.accessible, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring text box focus on left arrow");
|
||||
focused = waitForEvent(EVENT_FOCUS, textBox);
|
||||
EventUtils.synthesizeKey("KEY_ArrowLeft");
|
||||
await focused;
|
||||
testStates(textBox, STATE_FOCUSED);
|
||||
// On Mac, down arrow when not at the end of the field moves to the end.
|
||||
// Move back to the end so the next press of down arrow opens the popup.
|
||||
EventUtils.synthesizeKey("KEY_ArrowRight");
|
||||
|
||||
info("Ensuring autocomplete focus on down arrow");
|
||||
focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
event = await focused;
|
||||
testStates(event.accessible, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring text box focus when text is typed");
|
||||
focused = waitForEvent(EVENT_FOCUS, textBox);
|
||||
EventUtils.sendString("z");
|
||||
await focused;
|
||||
testStates(textBox, STATE_FOCUSED);
|
||||
EventUtils.synthesizeKey("KEY_Backspace");
|
||||
await waitForSearchFinish();
|
||||
|
||||
info("Ensuring autocomplete focus on down arrow");
|
||||
focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
event = await focused;
|
||||
testStates(event.accessible, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring text box focus on backspace");
|
||||
focused = waitForEvent(EVENT_FOCUS, textBox);
|
||||
EventUtils.synthesizeKey("KEY_Backspace");
|
||||
await focused;
|
||||
testStates(textBox, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring autocomplete focus on down arrow");
|
||||
focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
event = await focused;
|
||||
testStates(event.accessible, STATE_FOCUSED);
|
||||
|
||||
info("Ensuring text box focus on text selection");
|
||||
focused = waitForEvent(EVENT_FOCUS, textBox);
|
||||
EventUtils.synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
|
||||
await focused;
|
||||
testStates(textBox, STATE_FOCUSED);
|
||||
}
|
||||
|
||||
addAccessibleTask(``, runTests);
|
|
@ -9,8 +9,6 @@ const EXPORTED_SYMBOLS = ["LinkHandlerChild"];
|
|||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "Feeds",
|
||||
"resource:///modules/Feeds.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "FaviconLoader",
|
||||
"resource:///modules/FaviconLoader.jsm");
|
||||
|
||||
|
@ -97,7 +95,6 @@ class LinkHandlerChild extends ActorChild {
|
|||
|
||||
// Note: following booleans only work for the current link, not for the
|
||||
// whole content
|
||||
let feedAdded = false;
|
||||
let iconAdded = false;
|
||||
let searchAdded = false;
|
||||
let rels = {};
|
||||
|
@ -108,22 +105,6 @@ class LinkHandlerChild extends ActorChild {
|
|||
let isRichIcon = false;
|
||||
|
||||
switch (relVal) {
|
||||
case "feed":
|
||||
case "alternate":
|
||||
if (!feedAdded && event.type == "DOMLinkAdded") {
|
||||
if (!rels.feed && rels.alternate && rels.stylesheet)
|
||||
break;
|
||||
|
||||
if (Feeds.isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) {
|
||||
this.mm.sendAsyncMessage("Link:AddFeed", {
|
||||
type: link.type,
|
||||
href: link.href,
|
||||
title: link.title,
|
||||
});
|
||||
feedAdded = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "apple-touch-icon":
|
||||
case "apple-touch-icon-precomposed":
|
||||
case "fluid-icon":
|
||||
|
|
|
@ -10,7 +10,6 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Feeds: "resource:///modules/Feeds.jsm",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||
setTimeout: "resource://gre/modules/Timer.jsm",
|
||||
});
|
||||
|
@ -34,7 +33,6 @@ class PageInfoChild extends ActorChild {
|
|||
|
||||
let pageInfoData = {metaViewRows: this.getMetaInfo(document),
|
||||
docInfo: this.getDocumentInfo(document),
|
||||
feeds: this.getFeedsInfo(document, strings),
|
||||
windowInfo: this.getWindowInfo(window)};
|
||||
|
||||
message.target.sendAsyncMessage("PageInfo:data", pageInfoData);
|
||||
|
@ -98,36 +96,6 @@ class PageInfoChild extends ActorChild {
|
|||
return docInfo;
|
||||
}
|
||||
|
||||
getFeedsInfo(document, strings) {
|
||||
let feeds = [];
|
||||
// Get the feeds from the page.
|
||||
let linkNodes = document.getElementsByTagName("link");
|
||||
let length = linkNodes.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
let link = linkNodes[i];
|
||||
if (!link.href) {
|
||||
continue;
|
||||
}
|
||||
let rel = link.rel && link.rel.toLowerCase();
|
||||
let rels = {};
|
||||
|
||||
if (rel) {
|
||||
for (let relVal of rel.split(/\s+/)) {
|
||||
rels[relVal] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) {
|
||||
let type = Feeds.isValidFeed(link, document.nodePrincipal, "feed" in rels);
|
||||
if (type) {
|
||||
type = strings[type] || strings["application/rss+xml"];
|
||||
feeds.push([link.title, type, link.href]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return feeds;
|
||||
}
|
||||
|
||||
// Only called once to get the media tab's media elements from the content page.
|
||||
getMediaInfo(document, window, strings, mm) {
|
||||
let frameList = this.goThroughFrames(document, window);
|
||||
|
|
|
@ -114,170 +114,6 @@ function getMimeTypeForFeedType(aFeedType) {
|
|||
var FeedHandler = {
|
||||
_prefChangeCallback: null,
|
||||
|
||||
/** Called when the user clicks on the Subscribe to This Page... menu item,
|
||||
* or when the user clicks the feed button when the page contains multiple
|
||||
* feeds.
|
||||
* Builds a menu of unique feeds associated with the page, and if there
|
||||
* is only one, shows the feed inline in the browser window.
|
||||
* @param container
|
||||
* The feed list container (menupopup or subview) to be populated.
|
||||
* @param isSubview
|
||||
* Whether we're creating a subview (true) or menu (false/undefined)
|
||||
* @return true if the menu/subview should be shown, false if there was only
|
||||
* one feed and the feed should be shown inline in the browser
|
||||
* window (do not show the menupopup/subview).
|
||||
*/
|
||||
buildFeedList(container, isSubview) {
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
if (!isSubview && feeds == null) {
|
||||
// XXX hack -- menu opening depends on setting of an "open"
|
||||
// attribute, and the menu refuses to open if that attribute is
|
||||
// set (because it thinks it's already open). onpopupshowing gets
|
||||
// called after the attribute is unset, and it doesn't get unset
|
||||
// if we return false. so we unset it here; otherwise, the menu
|
||||
// refuses to work past this point.
|
||||
container.parentNode.removeAttribute("open");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = container.childNodes.length - 1; i >= 0; --i) {
|
||||
let node = container.childNodes[i];
|
||||
if (isSubview && node.localName == "label")
|
||||
continue;
|
||||
container.removeChild(node);
|
||||
}
|
||||
|
||||
if (!feeds || feeds.length <= 1)
|
||||
return false;
|
||||
|
||||
// Build the menu showing the available feed choices for viewing.
|
||||
let itemNodeType = isSubview ? "toolbarbutton" : "menuitem";
|
||||
for (let feedInfo of feeds) {
|
||||
let item = document.createElement(itemNodeType);
|
||||
let baseTitle = feedInfo.title || feedInfo.href;
|
||||
item.setAttribute("label", baseTitle);
|
||||
item.setAttribute("feed", feedInfo.href);
|
||||
item.setAttribute("tooltiptext", feedInfo.href);
|
||||
item.setAttribute("crop", "center");
|
||||
let className = "feed-" + itemNodeType;
|
||||
if (isSubview) {
|
||||
className += " subviewbutton";
|
||||
}
|
||||
item.setAttribute("class", className);
|
||||
container.appendChild(item);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to a given feed. Called when
|
||||
* 1. Page has a single feed and user clicks feed icon in location bar
|
||||
* 2. Page has a single feed and user selects Subscribe menu item
|
||||
* 3. Page has multiple feeds and user selects from feed icon popup (or subview)
|
||||
* 4. Page has multiple feeds and user selects from Subscribe submenu
|
||||
* @param href
|
||||
* The feed to subscribe to. May be null, in which case the
|
||||
* event target's feed attribute is examined.
|
||||
* @param event
|
||||
* The event this method is handling. Used to decide where
|
||||
* to open the preview UI. (Optional, unless href is null)
|
||||
*/
|
||||
subscribeToFeed(href, event) {
|
||||
// Just load the feed in the content area to either subscribe or show the
|
||||
// preview UI
|
||||
if (!href)
|
||||
href = event.target.getAttribute("feed");
|
||||
urlSecurityCheck(href, gBrowser.contentPrincipal,
|
||||
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
|
||||
this.loadFeed(href, event);
|
||||
},
|
||||
|
||||
loadFeed(href, event) {
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
try {
|
||||
openUILink(href, event, {
|
||||
ignoreAlt: true,
|
||||
triggeringPrincipal: gBrowser.contentPrincipal,
|
||||
});
|
||||
} finally {
|
||||
// We might default to a livebookmarks modal dialog,
|
||||
// so reset that if the user happens to click it again
|
||||
gBrowser.selectedBrowser.feeds = feeds;
|
||||
}
|
||||
},
|
||||
|
||||
get _feedMenuitem() {
|
||||
delete this._feedMenuitem;
|
||||
return this._feedMenuitem = document.getElementById("subscribeToPageMenuitem");
|
||||
},
|
||||
|
||||
get _feedMenupopup() {
|
||||
delete this._feedMenupopup;
|
||||
return this._feedMenupopup = document.getElementById("subscribeToPageMenupopup");
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the browser UI to show whether or not feeds are available when
|
||||
* a page is loaded or the user switches tabs to a page that has feeds.
|
||||
*/
|
||||
updateFeeds() {
|
||||
if (this._updateFeedTimeout)
|
||||
clearTimeout(this._updateFeedTimeout);
|
||||
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
let haveFeeds = feeds && feeds.length > 0;
|
||||
|
||||
let feedButton = document.getElementById("feed-button");
|
||||
if (feedButton) {
|
||||
if (haveFeeds) {
|
||||
feedButton.removeAttribute("disabled");
|
||||
} else {
|
||||
feedButton.setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
|
||||
if (!haveFeeds) {
|
||||
this._feedMenuitem.setAttribute("disabled", "true");
|
||||
this._feedMenuitem.removeAttribute("hidden");
|
||||
this._feedMenupopup.setAttribute("hidden", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
if (feeds.length > 1) {
|
||||
this._feedMenuitem.setAttribute("hidden", "true");
|
||||
this._feedMenupopup.removeAttribute("hidden");
|
||||
} else {
|
||||
this._feedMenuitem.setAttribute("feed", feeds[0].href);
|
||||
this._feedMenuitem.removeAttribute("disabled");
|
||||
this._feedMenuitem.removeAttribute("hidden");
|
||||
this._feedMenupopup.setAttribute("hidden", "true");
|
||||
}
|
||||
},
|
||||
|
||||
addFeed(link, browserForLink) {
|
||||
if (!browserForLink.feeds)
|
||||
browserForLink.feeds = [];
|
||||
|
||||
urlSecurityCheck(link.href, gBrowser.contentPrincipal,
|
||||
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
|
||||
|
||||
let feedURI = makeURI(link.href, document.characterSet);
|
||||
if (!/^https?$/.test(feedURI.scheme))
|
||||
return;
|
||||
|
||||
browserForLink.feeds.push({ href: link.href, title: link.title });
|
||||
|
||||
// If this addition was for the current browser, update the UI. For
|
||||
// background browsers, we'll update on tab switch.
|
||||
if (browserForLink == gBrowser.selectedBrowser) {
|
||||
// Batch updates to avoid updating the UI for multiple onLinkAdded events
|
||||
// fired within 100ms of each other.
|
||||
if (this._updateFeedTimeout)
|
||||
clearTimeout(this._updateFeedTimeout);
|
||||
this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the human-readable display name of a file. This could be the
|
||||
* application name.
|
||||
|
|
|
@ -400,26 +400,6 @@
|
|||
<menuitem id="menu_bookmarkThisPage"
|
||||
command="Browser:AddBookmarkAs"
|
||||
key="addBookmarkAsKb"/>
|
||||
<menuitem id="subscribeToPageMenuitem"
|
||||
disabled="true"
|
||||
#ifndef XP_MACOSX
|
||||
class="menuitem-iconic"
|
||||
#endif
|
||||
label="&subscribeToPageMenuitem.label;"
|
||||
oncommand="return FeedHandler.subscribeToFeed(null, event);"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
/>
|
||||
<menu id="subscribeToPageMenupopup"
|
||||
hidden="true"
|
||||
#ifndef XP_MACOSX
|
||||
class="menu-iconic"
|
||||
#endif
|
||||
label="&subscribeToPageMenupopup.label;">
|
||||
<menupopup id="subscribeToPageSubmenuMenupopup"
|
||||
onpopupshowing="return FeedHandler.buildFeedList(event.target);"
|
||||
oncommand="return FeedHandler.subscribeToFeed(null, event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
</menu>
|
||||
<menuitem id="menu_bookmarkAllTabs"
|
||||
label="&addCurPagesCmd.label;"
|
||||
class="show-only-for-keyboard"
|
||||
|
|
|
@ -3661,7 +3661,6 @@ var newWindowButtonObserver = {
|
|||
const DOMEventHandler = {
|
||||
init() {
|
||||
let mm = window.messageManager;
|
||||
mm.addMessageListener("Link:AddFeed", this);
|
||||
mm.addMessageListener("Link:LoadingIcon", this);
|
||||
mm.addMessageListener("Link:SetIcon", this);
|
||||
mm.addMessageListener("Link:SetFailedIcon", this);
|
||||
|
@ -3671,11 +3670,6 @@ const DOMEventHandler = {
|
|||
|
||||
receiveMessage(aMsg) {
|
||||
switch (aMsg.name) {
|
||||
case "Link:AddFeed":
|
||||
let link = {type: aMsg.data.type, href: aMsg.data.href, title: aMsg.data.title};
|
||||
FeedHandler.addFeed(link, aMsg.target);
|
||||
break;
|
||||
|
||||
case "Link:LoadingIcon":
|
||||
if (aMsg.data.canUseForTab) {
|
||||
this.setPendingIcon(aMsg.target);
|
||||
|
@ -4708,9 +4702,6 @@ var XULBrowserWindow = {
|
|||
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
|
||||
|
||||
if (aRequest && aWebProgress.isTopLevel) {
|
||||
// clear out feed data
|
||||
browser.feeds = null;
|
||||
|
||||
// clear out search-engine data
|
||||
browser.engines = null;
|
||||
}
|
||||
|
@ -4940,7 +4931,6 @@ var XULBrowserWindow = {
|
|||
},
|
||||
|
||||
asyncUpdateUI() {
|
||||
FeedHandler.updateFeeds();
|
||||
BrowserSearch.updateOpenSearchBadge();
|
||||
},
|
||||
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
// Via pageInfo.xul -> utilityOverlay.js
|
||||
/* import-globals-from ../utilityOverlay.js */
|
||||
/* import-globals-from ./pageInfo.js */
|
||||
|
||||
function initFeedTab(feeds) {
|
||||
for (const [name, type, url] of feeds) {
|
||||
addRow(name, type, url);
|
||||
}
|
||||
|
||||
const feedListbox = document.getElementById("feedListbox");
|
||||
document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
|
||||
}
|
||||
|
||||
function addRow(name, type, url) {
|
||||
const item = document.createXULElement("richlistitem");
|
||||
|
||||
const top = document.createXULElement("hbox");
|
||||
top.setAttribute("flex", "1");
|
||||
item.appendChild(top);
|
||||
|
||||
const bottom = document.createXULElement("hbox");
|
||||
bottom.setAttribute("flex", "1");
|
||||
item.appendChild(bottom);
|
||||
|
||||
const nameLabel = document.createXULElement("label");
|
||||
nameLabel.className = "feedTitle";
|
||||
nameLabel.textContent = name;
|
||||
nameLabel.setAttribute("flex", "1");
|
||||
top.appendChild(nameLabel);
|
||||
|
||||
const typeLabel = document.createXULElement("label");
|
||||
typeLabel.textContent = type;
|
||||
top.appendChild(typeLabel);
|
||||
|
||||
const urlContainer = document.createXULElement("hbox");
|
||||
urlContainer.setAttribute("flex", "1");
|
||||
bottom.appendChild(urlContainer);
|
||||
|
||||
const urlLabel = document.createXULElement("label");
|
||||
urlLabel.className = "text-link";
|
||||
urlLabel.textContent = url;
|
||||
urlLabel.setAttribute("tooltiptext", url);
|
||||
urlLabel.addEventListener("click", ev => openUILink(this.value, ev, {triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})}));
|
||||
urlContainer.appendChild(urlLabel);
|
||||
|
||||
const subscribeButton = document.createXULElement("button");
|
||||
subscribeButton.className = "feed-subscribe";
|
||||
subscribeButton.addEventListener("click",
|
||||
() => openWebLinkIn(url, "current", { ignoreAlt: true }));
|
||||
subscribeButton.setAttribute("label", gBundle.getString("feedSubscribe"));
|
||||
subscribeButton.setAttribute("accesskey", gBundle.getString("feedSubscribe.accesskey"));
|
||||
bottom.appendChild(subscribeButton);
|
||||
|
||||
document.getElementById("feedListbox").appendChild(item);
|
||||
}
|
|
@ -12,14 +12,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#feedListbox richlistitem {
|
||||
-moz-box-orient: vertical;
|
||||
}
|
||||
|
||||
#feedListbox richlistitem:not([selected="true"]) .feed-subscribe {
|
||||
display: none;
|
||||
}
|
||||
|
||||
groupbox[closed="true"] > .groupbox-body {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
/* import-globals-from ../../../../toolkit/content/globalOverlay.js */
|
||||
/* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */
|
||||
/* import-globals-from ../../../../toolkit/content/treeUtils.js */
|
||||
/* import-globals-from feeds.js */
|
||||
/* import-globals-from ../utilityOverlay.js */
|
||||
/* import-globals-from permissions.js */
|
||||
/* import-globals-from security.js */
|
||||
|
||||
|
@ -333,12 +333,6 @@ function loadPageInfo(frameOuterWindowID, imageElement, browser) {
|
|||
browser = browser || window.opener.gBrowser.selectedBrowser;
|
||||
let mm = browser.messageManager;
|
||||
|
||||
gStrings["application/rss+xml"] = gBundle.getString("feedRss");
|
||||
gStrings["application/atom+xml"] = gBundle.getString("feedAtom");
|
||||
gStrings["text/xml"] = gBundle.getString("feedXML");
|
||||
gStrings["application/xml"] = gBundle.getString("feedXML");
|
||||
gStrings["application/rdf+xml"] = gBundle.getString("feedXML");
|
||||
|
||||
let imageInfo = imageElement;
|
||||
|
||||
// Look for pageInfoListener in content.js. Sends message to listener with arguments.
|
||||
|
@ -346,7 +340,7 @@ function loadPageInfo(frameOuterWindowID, imageElement, browser) {
|
|||
|
||||
let pageInfoData;
|
||||
|
||||
// Get initial pageInfoData needed to display the general, feeds, permission and security tabs.
|
||||
// Get initial pageInfoData needed to display the general, permission and security tabs.
|
||||
mm.addMessageListener("PageInfo:data", function onmessage(message) {
|
||||
mm.removeMessageListener("PageInfo:data", onmessage);
|
||||
pageInfoData = message.data;
|
||||
|
@ -365,7 +359,6 @@ function loadPageInfo(frameOuterWindowID, imageElement, browser) {
|
|||
document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location);
|
||||
|
||||
makeGeneralTab(pageInfoData.metaViewRows, docInfo);
|
||||
initFeedTab(pageInfoData.feeds);
|
||||
onLoadPermission(uri, principal);
|
||||
securityOnLoad(uri, windowInfo);
|
||||
});
|
||||
|
@ -409,11 +402,6 @@ function resetPageInfo(args) {
|
|||
gImageView.clear();
|
||||
gImageHash = {};
|
||||
|
||||
/* Reset Feeds Tab */
|
||||
var feedListbox = document.getElementById("feedListbox");
|
||||
while (feedListbox.firstChild)
|
||||
feedListbox.firstChild.remove();
|
||||
|
||||
/* Call registered overlay reset functions */
|
||||
onResetRegistry.forEach(function(func) { func(); });
|
||||
|
||||
|
@ -435,7 +423,6 @@ function doHelpButton() {
|
|||
const helpTopics = {
|
||||
"generalPanel": "pageinfo_general",
|
||||
"mediaPanel": "pageinfo_media",
|
||||
"feedPanel": "pageinfo_feed",
|
||||
"permPanel": "pageinfo_permissions",
|
||||
"securityPanel": "pageinfo_security",
|
||||
};
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
|
||||
<script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
|
||||
|
@ -74,8 +73,6 @@
|
|||
oncommand="showTab('general');"/>
|
||||
<radio id="mediaTab" label="&mediaTab;" accesskey="&mediaTab.accesskey;"
|
||||
oncommand="showTab('media');" hidden="true"/>
|
||||
<radio id="feedTab" label="&feedTab;" accesskey="&feedTab.accesskey;"
|
||||
oncommand="showTab('feed');" hidden="true"/>
|
||||
<radio id="permTab" label="&permTab;" accesskey="&permTab.accesskey;"
|
||||
oncommand="showTab('perm');"/>
|
||||
<radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
|
||||
|
@ -264,11 +261,6 @@
|
|||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<!-- Feeds -->
|
||||
<vbox id="feedPanel">
|
||||
<richlistbox id="feedListbox" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Permissions -->
|
||||
<vbox id="permPanel">
|
||||
<hbox id="permHostBox">
|
||||
|
|
|
@ -33,7 +33,6 @@ support-files =
|
|||
download_page_1.txt
|
||||
download_page_2.txt
|
||||
dummy_page.html
|
||||
feed_tab.html
|
||||
file_documentnavigation_frameset.html
|
||||
file_double_close_tab.html
|
||||
file_fullscreen-window-open.html
|
||||
|
@ -96,8 +95,6 @@ skip-if = (verify && !debug && (os == 'win'))
|
|||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug406216.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug413915.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug416661.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug417483.js]
|
||||
|
@ -338,9 +335,6 @@ subsuite = clipboard
|
|||
support-files = offlineQuotaNotification.cacheManifest offlineQuotaNotification.html
|
||||
skip-if = os == "linux" && !debug # bug 1304273
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_feed_discovery.js]
|
||||
support-files = feed_discovery.html
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_gZipOfflineChild.js]
|
||||
skip-if = verify
|
||||
support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
ChromeUtils.defineModuleGetter(this, "Feeds",
|
||||
"resource:///modules/Feeds.jsm");
|
||||
|
||||
function test() {
|
||||
var exampleUri = makeURI("http://example.com/");
|
||||
var principal = Services.scriptSecurityManager.createCodebasePrincipal(exampleUri, {});
|
||||
|
||||
function testIsFeed(aTitle, aHref, aType, aKnown) {
|
||||
var link = {
|
||||
title: aTitle,
|
||||
href: aHref,
|
||||
type: aType,
|
||||
ownerDocument: {
|
||||
characterSet: "UTF-8",
|
||||
},
|
||||
};
|
||||
return Feeds.isValidFeed(link, principal, aKnown);
|
||||
}
|
||||
|
||||
var href = "http://example.com/feed/";
|
||||
var atomType = "application/atom+xml";
|
||||
var funkyAtomType = " aPPLICAtion/Atom+XML ";
|
||||
var rssType = "application/rss+xml";
|
||||
var funkyRssType = " Application/RSS+XML ";
|
||||
var rdfType = "application/rdf+xml";
|
||||
var texmlType = "text/xml";
|
||||
var appxmlType = "application/xml";
|
||||
var noRss = "Foo";
|
||||
var rss = "RSS";
|
||||
|
||||
// things that should be valid
|
||||
ok(testIsFeed(noRss, href, atomType, false) == atomType,
|
||||
"detect Atom feed");
|
||||
ok(testIsFeed(noRss, href, funkyAtomType, false) == atomType,
|
||||
"clean up and detect Atom feed");
|
||||
ok(testIsFeed(noRss, href, rssType, false) == rssType,
|
||||
"detect RSS feed");
|
||||
ok(testIsFeed(noRss, href, funkyRssType, false) == rssType,
|
||||
"clean up and detect RSS feed");
|
||||
|
||||
// things that should not be feeds
|
||||
ok(testIsFeed(noRss, href, rdfType, false) == null,
|
||||
"should not detect RDF non-feed");
|
||||
ok(testIsFeed(rss, href, rdfType, false) == null,
|
||||
"should not detect RDF feed from type and title");
|
||||
ok(testIsFeed(noRss, href, texmlType, false) == null,
|
||||
"should not detect text/xml non-feed");
|
||||
ok(testIsFeed(rss, href, texmlType, false) == null,
|
||||
"should not detect text/xml feed from type and title");
|
||||
ok(testIsFeed(noRss, href, appxmlType, false) == null,
|
||||
"should not detect application/xml non-feed");
|
||||
ok(testIsFeed(rss, href, appxmlType, false) == null,
|
||||
"should not detect application/xml feed from type and title");
|
||||
|
||||
// security check only, returns cleaned up type or "application/rss+xml"
|
||||
ok(testIsFeed(noRss, href, atomType, true) == atomType,
|
||||
"feed security check should return Atom type");
|
||||
ok(testIsFeed(noRss, href, funkyAtomType, true) == atomType,
|
||||
"feed security check should return cleaned up Atom type");
|
||||
ok(testIsFeed(noRss, href, rssType, true) == rssType,
|
||||
"feed security check should return RSS type");
|
||||
ok(testIsFeed(noRss, href, funkyRssType, true) == rssType,
|
||||
"feed security check should return cleaned up RSS type");
|
||||
ok(testIsFeed(noRss, href, "", true) == rssType,
|
||||
"feed security check without type should return RSS type");
|
||||
ok(testIsFeed(noRss, href, "garbage", true) == "garbage",
|
||||
"feed security check with garbage type should return garbage");
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/feed_discovery.html";
|
||||
|
||||
/** Test for Bug 377611 **/
|
||||
|
||||
add_task(async function() {
|
||||
// Open a new tab.
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, URL);
|
||||
registerCleanupFunction(() => gBrowser.removeCurrentTab());
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
await BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
let discovered = browser.feeds;
|
||||
ok(discovered.length > 0, "some feeds should be discovered");
|
||||
|
||||
let feeds = {};
|
||||
for (let aFeed of discovered) {
|
||||
feeds[aFeed.href] = true;
|
||||
}
|
||||
|
||||
await ContentTask.spawn(browser, feeds, async function(contentFeeds) {
|
||||
for (let aLink of content.document.getElementsByTagName("link")) {
|
||||
// ignore real stylesheets, and anything without an href property
|
||||
if (aLink.type != "text/css" && aLink.href) {
|
||||
if (/bogus/i.test(aLink.title)) {
|
||||
ok(!contentFeeds[aLink.href], "don't discover " + aLink.href);
|
||||
} else {
|
||||
ok(contentFeeds[aLink.href], "should discover " + aLink.href);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,78 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=377611
|
||||
-->
|
||||
<head>
|
||||
<title>Test for feed discovery</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Straight up standard -->
|
||||
<link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
|
||||
<link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
|
||||
<link rel="feed" title="3" href="/3.xml" />
|
||||
|
||||
<!-- invalid protocol -->
|
||||
<link rel="alternate" type="application/atom+xml" title="Bogus non file protocol" href="file://path/1.rss" />
|
||||
<link rel="alternate" type="application/atom+xml" title="Bogus non feed:http protocol" href="feed:http://path/1.rss" />
|
||||
<link rel="alternate" type="application/atom+xml" title="Bogus non pcast protocol" href="pcast://path/1.rss" />
|
||||
|
||||
<!-- rel is a space-separated list -->
|
||||
<link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" />
|
||||
<link rel="foo alternate" type="application/atom+xml" title="5" href="/5.atom" />
|
||||
<link rel="alternate foo" type="application/atom+xml" title="6" href="/6.atom" />
|
||||
<link rel="foo alternate foo" type="application/atom+xml" title="7" href="/7.atom" />
|
||||
<link rel="meat feed cake" title="8" href="/8.atom" />
|
||||
|
||||
<!-- rel is case-insensitive -->
|
||||
<link rel="ALTERNate" type="application/atom+xml" title="9" href="/9.atom" />
|
||||
<link rel="fEEd" title="10" href="/10.atom" />
|
||||
|
||||
<!-- type can have leading and trailing whitespace -->
|
||||
<link rel="alternate" type=" application/atom+xml " title="11" href="/11.atom" />
|
||||
|
||||
<!-- type is case-insensitive -->
|
||||
<link rel="alternate" type="aPPliCAtion/ATom+xML" title="12" href="/12.atom" />
|
||||
|
||||
<!-- "feed stylesheet" is a feed, though "alternate stylesheet" isn't -->
|
||||
<link rel="feed stylesheet" title="13" href="/13.atom" />
|
||||
|
||||
<!-- hyphens or letters around rel not allowed -->
|
||||
<link rel="disabled-alternate" type="application/atom+xml" title="Bogus1" href="/Bogus1" />
|
||||
<link rel="alternates" type="application/atom+xml" title="Bogus2" href="/Bogus2" />
|
||||
<link rel=" alternate-like" type="application/atom+xml" title="Bogus3" href="/Bogus3" />
|
||||
|
||||
<!-- don't tolerate text/xml if title includes 'rss' not as a word -->
|
||||
<link rel="alternate" type="text/xml" title="Bogus4 scissorsshaped" href="/Bogus4" />
|
||||
|
||||
<!-- don't tolerate application/xml if title includes 'rss' not as a word -->
|
||||
<link rel="alternate" type="application/xml" title="Bogus5 scissorsshaped" href="/Bogus5" />
|
||||
|
||||
<!-- don't tolerate application/rdf+xml if title includes 'rss' not as a word -->
|
||||
<link rel="alternate" type="application/rdf+xml" title="Bogus6 scissorsshaped" href="/Bogus6" />
|
||||
|
||||
<!-- don't tolerate random types -->
|
||||
<link rel="alternate" type="text/plain" title="Bogus7 rss" href="/Bogus7" />
|
||||
|
||||
<!-- don't find Atom by title -->
|
||||
<link rel="foopy" type="application/atom+xml" title="Bogus8 Atom and RSS" href="/Bogus8" />
|
||||
|
||||
<!-- don't find application/rss+xml by title -->
|
||||
<link rel="goats" type="application/rss+xml" title="Bogus9 RSS and Atom" href="/Bogus9" />
|
||||
|
||||
<!-- don't find application/rdf+xml by title -->
|
||||
<link rel="alternate" type="application/rdf+xml" title="Bogus10 RSS and Atom" href="/Bogus10" />
|
||||
|
||||
<!-- don't find application/xml by title -->
|
||||
<link rel="alternate" type="application/xml" title="Bogus11 RSS and Atom" href="/Bogus11" />
|
||||
|
||||
<!-- don't find text/xml by title -->
|
||||
<link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" />
|
||||
|
||||
<!-- alternate and stylesheet isn't a feed -->
|
||||
<link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" />
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
[DEFAULT]
|
||||
|
||||
[browser_pageInfo.js]
|
||||
support-files =
|
||||
../general/feed_tab.html
|
||||
[browser_pageinfo_firstPartyIsolation.js]
|
||||
support-files =
|
||||
image.html
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
const URI = "https://example.com/browser/browser/base/content/test/pageinfo/feed_tab.html";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
var pageInfo;
|
||||
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
|
||||
URI).then(() => {
|
||||
Services.obs.addObserver(observer, "page-info-dialog-loaded");
|
||||
pageInfo = BrowserPageInfo();
|
||||
});
|
||||
gBrowser.selectedBrowser.loadURI(URI);
|
||||
|
||||
function observer(win, topic, data) {
|
||||
Services.obs.removeObserver(observer, "page-info-dialog-loaded");
|
||||
pageInfo.onFinished.push(handlePageInfo);
|
||||
}
|
||||
|
||||
function handlePageInfo() {
|
||||
ok(pageInfo.document.getElementById("feedTab"), "Feed tab");
|
||||
let feedListbox = pageInfo.document.getElementById("feedListbox");
|
||||
ok(feedListbox, "Feed list should exist.");
|
||||
|
||||
var feedRowsNum = feedListbox.getRowCount();
|
||||
is(feedRowsNum, 3, "Number of feeds listed should be correct.");
|
||||
|
||||
for (var i = 0; i < feedRowsNum; i++) {
|
||||
let feedItem = feedListbox.getItemAtIndex(i);
|
||||
let feedTitle = feedItem.querySelector(".feedTitle");
|
||||
is(feedTitle.textContent, i + 1, "Feed name should be correct.");
|
||||
}
|
||||
|
||||
pageInfo.close();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -105,10 +105,6 @@
|
|||
list-style-image: var(--sidebars-icon) !important;
|
||||
}
|
||||
|
||||
:root[lwthemeicons~="--subscribe-icon"] #feed-button:-moz-lwtheme {
|
||||
list-style-image: var(--subscribe-icon) !important;
|
||||
}
|
||||
|
||||
:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme {
|
||||
list-style-image: var(--text_encoding-icon) !important;
|
||||
}
|
||||
|
@ -147,7 +143,6 @@
|
|||
:root[lwthemeicons~="--synced_tabs-icon"] #sync-button:-moz-lwtheme,
|
||||
:root[lwthemeicons~="--open_file-icon"] #open-file-button:-moz-lwtheme,
|
||||
:root[lwthemeicons~="--sidebars-icon"] #sidebar-button:-moz-lwtheme,
|
||||
:root[lwthemeicons~="--subscribe-icon"] #feed-button:-moz-lwtheme,
|
||||
:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme,
|
||||
:root[lwthemeicons~="--email_link-icon"] #email-link-button:-moz-lwtheme,
|
||||
:root[lwthemeicons~="--forget-icon"] #panic-button:-moz-lwtheme {
|
||||
|
|
|
@ -311,6 +311,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
break;
|
||||
case KeyEvent.DOM_VK_TAB:
|
||||
this.userSelectionBehavior = "tab";
|
||||
// The user is explicitly making a selection, so the popup
|
||||
// should get accessibility focus.
|
||||
this.popup.richlistbox.suppressMenuItemEvent = false;
|
||||
break;
|
||||
case KeyEvent.DOM_VK_UP:
|
||||
case KeyEvent.DOM_VK_DOWN:
|
||||
|
@ -318,6 +321,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
case KeyEvent.DOM_VK_PAGE_DOWN:
|
||||
if (this.userSelectionBehavior != "tab")
|
||||
this.userSelectionBehavior = "arrow";
|
||||
// The user is explicitly making a selection, so the popup
|
||||
// should get accessibility focus.
|
||||
this.popup.richlistbox.suppressMenuItemEvent = false;
|
||||
break;
|
||||
}
|
||||
if (!this.popup.disableKeyNavigation) {
|
||||
|
@ -1756,6 +1762,15 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
} else {
|
||||
this.removeAttribute("usertyping");
|
||||
}
|
||||
// If the popup already had accessibility focus, bring it back to
|
||||
// the input, since the user is editing.
|
||||
if (!this.popup.richlistbox.suppressMenuItemEvent &&
|
||||
this.popup.richlistbox.currentItem) {
|
||||
this.popup.richlistbox.currentItem._fireEvent("DOMMenuItemInactive");
|
||||
}
|
||||
// The user is typing, so don't give accessibility focus to the
|
||||
// popup, even if an item gets automatically selected.
|
||||
this.popup.richlistbox.suppressMenuItemEvent = true;
|
||||
// Only wait for a result when we are sure to get one. In some
|
||||
// cases, like when pasting the same exact text, we may not fire
|
||||
// a new search and we won't get a result.
|
||||
|
@ -2434,6 +2449,15 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
window.windowUtils.getBoundsWithoutFlushing(document.getElementById("nav-bar")).bottom -
|
||||
window.windowUtils.getBoundsWithoutFlushing(aInput).bottom);
|
||||
|
||||
if (!this.richlistbox.suppressMenuItemEvent && this.richlistbox.currentItem) {
|
||||
// The richlistbox fired a DOMMenuItemActive for accessibility,
|
||||
// but because the popup isn't open yet, accessibility will ignore
|
||||
// it. Therefore, fire it again once the popup opens.
|
||||
this.addEventListener("popupshown", () => {
|
||||
this.richlistbox.currentItem._fireEvent("DOMMenuItemActive");
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
this.openPopup(aElement, "after_start", 0, yOffset, false, false);
|
||||
|
||||
// Do this immediately after we've requested the popup to open. This
|
||||
|
@ -2726,11 +2750,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
// If nothing is selected yet, select the first result if it is a
|
||||
// pre-selected "heuristic" result. (See UnifiedComplete.js.)
|
||||
if (this.selectedIndex == -1 && this._isFirstResultHeuristic) {
|
||||
// Don't fire DOMMenuItemActive so that screen readers still see
|
||||
// the input as being focused.
|
||||
this.richlistbox.suppressMenuItemEvent = true;
|
||||
this.input.controller.setInitiallySelectedIndex(0);
|
||||
this.richlistbox.suppressMenuItemEvent = false;
|
||||
}
|
||||
// If this is the first time we get the result from the current
|
||||
// search and we are not in the private context, we can speculatively
|
||||
|
|
|
@ -82,7 +82,6 @@ browser.jar:
|
|||
* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
|
||||
content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js)
|
||||
content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css)
|
||||
content/browser/pageinfo/feeds.js (content/pageinfo/feeds.js)
|
||||
content/browser/pageinfo/permissions.js (content/pageinfo/permissions.js)
|
||||
content/browser/pageinfo/security.js (content/pageinfo/security.js)
|
||||
content/browser/content-refreshblocker.js (content/content-refreshblocker.js)
|
||||
|
|
|
@ -55,7 +55,7 @@ const kSubviewEvents = [
|
|||
* The current version. We can use this to auto-add new default widgets as necessary.
|
||||
* (would be const but isn't because of testing purposes)
|
||||
*/
|
||||
var kVersion = 14;
|
||||
var kVersion = 15;
|
||||
|
||||
/**
|
||||
* Buttons removed from built-ins by version they were removed. kVersion must be
|
||||
|
@ -63,6 +63,7 @@ var kVersion = 14;
|
|||
* version the button is removed in as the value. e.g. "pocket-button": 5
|
||||
*/
|
||||
var ObsoleteBuiltinButtons = {
|
||||
"feed-button": 15,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -329,44 +329,6 @@ const CustomizableWidgets = [
|
|||
},
|
||||
},
|
||||
{
|
||||
id: "feed-button",
|
||||
type: "view",
|
||||
viewId: "PanelUI-feeds",
|
||||
tooltiptext: "feed-button.tooltiptext2",
|
||||
onClick(aEvent) {
|
||||
let win = aEvent.target.ownerGlobal;
|
||||
let feeds = win.gBrowser.selectedBrowser.feeds;
|
||||
|
||||
// Here, we only care about the case where we have exactly 1 feed and the
|
||||
// user clicked...
|
||||
let isClick = (aEvent.button == 0 || aEvent.button == 1);
|
||||
if (feeds && feeds.length == 1 && isClick) {
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
win.FeedHandler.subscribeToFeed(feeds[0].href, aEvent);
|
||||
CustomizableUI.hidePanelForNode(aEvent.target);
|
||||
}
|
||||
},
|
||||
onViewShowing(aEvent) {
|
||||
let doc = aEvent.target.ownerDocument;
|
||||
let container = doc.getElementById("PanelUI-feeds");
|
||||
let gotView = doc.defaultView.FeedHandler.buildFeedList(container, true);
|
||||
|
||||
// For no feeds or only a single one, don't show the panel.
|
||||
if (!gotView) {
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
}
|
||||
},
|
||||
onCreated(node) {
|
||||
let win = node.ownerGlobal;
|
||||
let selectedBrowser = win.gBrowser.selectedBrowser;
|
||||
let feeds = selectedBrowser && selectedBrowser.feeds;
|
||||
if (!feeds || !feeds.length) {
|
||||
node.setAttribute("disabled", "true");
|
||||
}
|
||||
},
|
||||
}, {
|
||||
id: "characterencoding-button",
|
||||
label: "characterencoding-button2.label",
|
||||
type: "view",
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
support-files =
|
||||
head.js
|
||||
support/test_967000_charEncoding_page.html
|
||||
support/feeds_test_page.html
|
||||
support/test-feed.xml
|
||||
|
||||
[browser_694291_searchbar_preference.js]
|
||||
[browser_873501_handle_specials.js]
|
||||
|
@ -95,8 +93,6 @@ skip-if = verify
|
|||
skip-if = verify
|
||||
[browser_963639_customizing_attribute_non_customizable_toolbar.js]
|
||||
[browser_967000_button_charEncoding.js]
|
||||
[browser_967000_button_feeds.js]
|
||||
skip-if = (verify && debug && (os == 'linux'))
|
||||
[browser_968565_insert_before_hidden_items.js]
|
||||
[browser_969427_recreate_destroyed_widget_after_reset.js]
|
||||
[browser_969661_character_encoding_navbar_disabled.js]
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// don't try this at home, kids.
|
||||
function test() {
|
||||
// Customize something to make sure stuff changed:
|
||||
CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
|
||||
CustomizableUI.addWidgetToArea("save-page-button", CustomizableUI.AREA_NAVBAR);
|
||||
|
||||
// Check what version we're on:
|
||||
let CustomizableUIBSPass = ChromeUtils.import("resource:///modules/CustomizableUI.jsm", {});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
// don't try this at home, kids.
|
||||
function test() {
|
||||
// Customize something to make sure stuff changed:
|
||||
CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
|
||||
CustomizableUI.addWidgetToArea("save-page-button", CustomizableUI.AREA_NAVBAR);
|
||||
|
||||
let CustomizableUIBSPass = ChromeUtils.import("resource:///modules/CustomizableUI.jsm", {});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
const kXULWidgetId = "a-test-button"; // we'll create a button with this ID.
|
||||
const kAPIWidgetId = "feed-button";
|
||||
const kAPIWidgetId = "save-page-button";
|
||||
const kPanel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
|
||||
const kToolbar = CustomizableUI.AREA_NAVBAR;
|
||||
const kVisiblePalette = "customization-palette";
|
||||
|
|
|
@ -8,12 +8,12 @@ CustomizableUI.createWidget({id: "cui-panel-item-to-drag-to", defaultArea: Custo
|
|||
// Dragging an item from the palette to another button in the panel should work.
|
||||
add_task(async function() {
|
||||
await startCustomizing();
|
||||
let btn = document.getElementById("feed-button");
|
||||
let btn = document.getElementById("new-window-button");
|
||||
let placements = getAreaWidgetIds(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
|
||||
let lastButtonIndex = placements.length - 1;
|
||||
let lastButton = placements[lastButtonIndex];
|
||||
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
|
||||
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["new-window-button", lastButton]);
|
||||
let lastButtonNode = document.getElementById(lastButton);
|
||||
simulateItemDrag(btn, lastButtonNode, "start");
|
||||
assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterInsert);
|
||||
|
@ -28,11 +28,11 @@ add_task(async function() {
|
|||
add_task(async function() {
|
||||
CustomizableUI.addWidgetToArea("cui-panel-item-to-drag-to", CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
await startCustomizing();
|
||||
let btn = document.getElementById("feed-button");
|
||||
let btn = document.getElementById("new-window-button");
|
||||
let panel = document.getElementById(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
let placements = getAreaWidgetIds(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
|
||||
let placementsAfterAppend = placements.concat(["feed-button"]);
|
||||
let placementsAfterAppend = placements.concat(["new-window-button"]);
|
||||
simulateItemDrag(btn, panel);
|
||||
assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterAppend);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
|
@ -49,12 +49,12 @@ add_task(async function() {
|
|||
CustomizableUI.removeWidgetFromArea(widgetIds.shift());
|
||||
}
|
||||
await startCustomizing();
|
||||
let btn = document.getElementById("feed-button");
|
||||
let btn = document.getElementById("new-window-button");
|
||||
let panel = document.getElementById(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
|
||||
assertAreaPlacements(panel.id, []);
|
||||
|
||||
let placementsAfterAppend = ["feed-button"];
|
||||
let placementsAfterAppend = ["new-window-button"];
|
||||
simulateItemDrag(btn, panel);
|
||||
assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterAppend);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
|
|
|
@ -29,10 +29,10 @@ add_task(function() {
|
|||
// Insert, then remove items
|
||||
add_task(function() {
|
||||
let currentSet = navbar.currentSet;
|
||||
let newCurrentSet = currentSet.replace("home-button", "feed-button,sync-button,home-button");
|
||||
let newCurrentSet = currentSet.replace("home-button", "new-window-button,sync-button,home-button");
|
||||
navbar.currentSet = newCurrentSet;
|
||||
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
|
||||
let feedBtn = document.getElementById("feed-button");
|
||||
let feedBtn = document.getElementById("new-window-button");
|
||||
let syncBtn = document.getElementById("sync-button");
|
||||
ok(feedBtn, "Feed button should have been added.");
|
||||
ok(syncBtn, "Sync button should have been added.");
|
||||
|
@ -54,10 +54,10 @@ add_task(function() {
|
|||
// Simultaneous insert/remove:
|
||||
add_task(function() {
|
||||
let currentSet = navbar.currentSet;
|
||||
let newCurrentSet = currentSet.replace("home-button", "feed-button");
|
||||
let newCurrentSet = currentSet.replace("home-button", "new-window-button");
|
||||
navbar.currentSet = newCurrentSet;
|
||||
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
|
||||
let feedBtn = document.getElementById("feed-button");
|
||||
let feedBtn = document.getElementById("new-window-button");
|
||||
ok(feedBtn, "Feed button should have been added.");
|
||||
let homeBtn = document.getElementById("home-button");
|
||||
ok(!homeBtn, "Home button should have been removed.");
|
||||
|
|
|
@ -1,62 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/feeds_test_page.html";
|
||||
const TEST_FEED = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test-feed.xml";
|
||||
|
||||
var newTab = null;
|
||||
var initialLocation = gBrowser.currentURI.spec;
|
||||
|
||||
add_task(async function() {
|
||||
info("Check Subscribe button functionality");
|
||||
|
||||
// add the Subscribe button to the panel
|
||||
CustomizableUI.addWidgetToArea("feed-button",
|
||||
CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
|
||||
await waitForOverflowButtonShown();
|
||||
|
||||
// check the button's functionality
|
||||
await document.getElementById("nav-bar").overflowable.show();
|
||||
|
||||
let feedButton = document.getElementById("feed-button");
|
||||
ok(feedButton, "The Subscribe button was added to the Panel Menu");
|
||||
is(feedButton.getAttribute("disabled"), "true", "The Subscribe button is initially disabled");
|
||||
|
||||
let panelHidePromise = promiseOverflowHidden(window);
|
||||
await document.getElementById("nav-bar").overflowable._panel.hidePopup();
|
||||
await panelHidePromise;
|
||||
|
||||
newTab = gBrowser.selectedTab;
|
||||
await promiseTabLoadEvent(newTab, TEST_PAGE);
|
||||
|
||||
await gCUITestUtils.openMainMenu();
|
||||
|
||||
await waitForCondition(() => !feedButton.hasAttribute("disabled"));
|
||||
ok(!feedButton.hasAttribute("disabled"), "The Subscribe button gets enabled");
|
||||
|
||||
feedButton.click();
|
||||
await promiseTabLoadEvent(newTab, TEST_FEED);
|
||||
|
||||
is(gBrowser.currentURI.spec, TEST_FEED, "Subscribe page opened");
|
||||
ok(!isOverflowOpen(), "Panel is closed");
|
||||
|
||||
if (isOverflowOpen()) {
|
||||
panelHidePromise = promiseOverflowHidden(window);
|
||||
await document.getElementById("nav-bar").overflowable._panel.hidePopup();
|
||||
await panelHidePromise;
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function asyncCleanup() {
|
||||
// reset the panel UI to the default state
|
||||
await resetCustomization();
|
||||
ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
|
||||
|
||||
// restore the initial location
|
||||
BrowserTestUtils.addTab(gBrowser, initialLocation);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
|
@ -79,7 +79,7 @@ add_task(function() {
|
|||
defaultPlacements: [] });
|
||||
CustomizableUI.registerArea("area-996899-2", { anchor: "PanelUI-menu-button",
|
||||
type: CustomizableUI.TYPE_MENU_PANEL,
|
||||
defaultPlacements: ["feed-button"] });
|
||||
defaultPlacements: ["new-window-button"] });
|
||||
} catch (ex) {
|
||||
exceptionThrown = ex;
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Feeds test page</title>
|
||||
<link rel="alternate" type="application/rss+xml" href="test-feed.xml" title="Test feed">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
This is a test page for feeds
|
||||
</body>
|
||||
</html>
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
|
||||
<title>Example Feed</title>
|
||||
<link href="http://example.org/"/>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
</author>
|
||||
<id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
|
||||
|
||||
<entry>
|
||||
|
||||
<title>Item</title>
|
||||
<link href="http://example.org/first"/>
|
||||
<id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<summary>Some text.</summary>
|
||||
</entry>
|
||||
|
||||
</feed>
|
|
@ -120,7 +120,6 @@ async function runTestWithIcons(icons) {
|
|||
["synced_tabs", "#sync-button", "sync-button"],
|
||||
["open_file", "#open-file-button", "open-file-button"],
|
||||
["sidebars", "#sidebar-button", "sidebar-button"],
|
||||
["subscribe", "#feed-button", "feed-button"],
|
||||
["text_encoding", "#characterencoding-button", "characterencoding-button"],
|
||||
["email_link", "#email-link-button", "email-link-button"],
|
||||
["forget", "#panic-button", "panic-button"],
|
||||
|
@ -193,7 +192,6 @@ add_task(async function test_all_icons() {
|
|||
["synced_tabs", "fox.svg"],
|
||||
["open_file", "fox.svg"],
|
||||
["sidebars", "fox.svg"],
|
||||
["subscribe", "fox.svg"],
|
||||
["text_encoding", "fox.svg"],
|
||||
["email_link", "fox.svg"],
|
||||
["forget", "fox.svg"],
|
||||
|
@ -233,7 +231,6 @@ add_task(async function test_some_icons() {
|
|||
["synced_tabs", ""],
|
||||
["open_file", ""],
|
||||
["sidebars", ""],
|
||||
["subscribe", ""],
|
||||
["text_encoding", ""],
|
||||
["email_link", ""],
|
||||
["forget", ""],
|
||||
|
|
|
@ -341,12 +341,6 @@ FeedResultService.prototype = {
|
|||
subtitle,
|
||||
feedHandler: "default" });
|
||||
break;
|
||||
default:
|
||||
// fall through
|
||||
case "bookmarks":
|
||||
Services.cpmm.sendAsyncMessage("FeedConverter:addLiveBookmark",
|
||||
{ spec, title });
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -68,15 +68,7 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "gCanFrameFeeds",
|
|||
"browser.feeds.unsafelyFrameFeeds", false);
|
||||
|
||||
function FeedWriter() {
|
||||
this._selectedApp = undefined;
|
||||
this._selectedAppMenuItem = null;
|
||||
this._subscribeCallback = null;
|
||||
this._defaultHandlerMenuItem = null;
|
||||
|
||||
Services.telemetry.scalarAdd("browser.feeds.preview_loaded", 1);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "_mm",
|
||||
() => this._window.docShell.messageManager);
|
||||
}
|
||||
|
||||
FeedWriter.prototype = {
|
||||
|
@ -146,24 +138,6 @@ FeedWriter.prototype = {
|
|||
return this._bundle.GetStringFromName(key);
|
||||
},
|
||||
|
||||
_setCheckboxCheckedState(aValue) {
|
||||
let checkbox = this._document.getElementById("alwaysUse");
|
||||
if (checkbox) {
|
||||
// see checkbox.xml, xbl bindings are not applied within the sandbox! TODO
|
||||
let change = (aValue != (checkbox.getAttribute("checked") == "true"));
|
||||
if (aValue)
|
||||
checkbox.setAttribute("checked", "true");
|
||||
else
|
||||
checkbox.removeAttribute("checked");
|
||||
|
||||
if (change) {
|
||||
let event = this._document.createEvent("Events");
|
||||
event.initEvent("CheckboxStateChange", true, true);
|
||||
checkbox.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a date suitable for displaying in the feed preview.
|
||||
* If the date cannot be parsed, the return value is "false".
|
||||
|
@ -193,25 +167,6 @@ FeedWriter.prototype = {
|
|||
return this.__dateFormatter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the feed type.
|
||||
*/
|
||||
__feedType: null,
|
||||
_getFeedType() {
|
||||
if (this.__feedType != null)
|
||||
return this.__feedType;
|
||||
|
||||
try {
|
||||
// grab the feed because it's got the feed.type in it.
|
||||
let container = this._getContainer();
|
||||
let feed = container.QueryInterface(Ci.nsIFeed);
|
||||
this.__feedType = feed.type;
|
||||
return feed.type;
|
||||
} catch (ex) { }
|
||||
|
||||
return Ci.nsIFeed.TYPE_FEED;
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the feed title into the preview document.
|
||||
* @param container
|
||||
|
@ -472,234 +427,6 @@ FeedWriter.prototype = {
|
|||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get moz-icon url for a file
|
||||
* @param file
|
||||
* A nsIFile object for which the moz-icon:// is returned
|
||||
* @returns moz-icon url of the given file as a string
|
||||
*/
|
||||
_getFileIconURL(file) {
|
||||
let fph = Services.io.getProtocolHandler("file")
|
||||
.QueryInterface(Ci.nsIFileProtocolHandler);
|
||||
let urlSpec = fph.getURLSpecFromFile(file);
|
||||
return "moz-icon://" + urlSpec + "?size=16";
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays a prompt from which the user may choose a (client) feed reader.
|
||||
* @param aCallback the callback method, passes in true if a feed reader was
|
||||
* selected, false otherwise.
|
||||
*/
|
||||
_chooseClientApp(aCallback) {
|
||||
this._subscribeCallback = aCallback;
|
||||
this._mm.sendAsyncMessage("FeedWriter:ChooseClientApp",
|
||||
{ title: this._getString("chooseApplicationDialogTitle"),
|
||||
feedType: this._getFeedType() });
|
||||
},
|
||||
|
||||
_setSubscribeUsingLabel() {
|
||||
let stringLabel = "subscribeFeedUsing";
|
||||
switch (this._getFeedType()) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
stringLabel = "subscribeVideoPodcastUsing";
|
||||
break;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
stringLabel = "subscribeAudioPodcastUsing";
|
||||
break;
|
||||
}
|
||||
|
||||
let subscribeUsing = this._document.getElementById("subscribeUsingDescription");
|
||||
let textNode = this._document.createTextNode(this._getString(stringLabel));
|
||||
subscribeUsing.insertBefore(textNode, subscribeUsing.firstChild);
|
||||
},
|
||||
|
||||
_setAlwaysUseLabel() {
|
||||
let checkbox = this._document.getElementById("alwaysUse");
|
||||
if (checkbox && this._handlersList) {
|
||||
let handlerName = this._handlersList.selectedOptions[0]
|
||||
.textContent;
|
||||
let stringLabel = "alwaysUseForFeeds";
|
||||
switch (this._getFeedType()) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
stringLabel = "alwaysUseForVideoPodcasts";
|
||||
break;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
stringLabel = "alwaysUseForAudioPodcasts";
|
||||
break;
|
||||
}
|
||||
|
||||
let label = this._getFormattedString(stringLabel, [handlerName]);
|
||||
|
||||
let checkboxText = this._document.getElementById("checkboxText");
|
||||
if (checkboxText.lastChild.nodeType == checkboxText.TEXT_NODE) {
|
||||
checkboxText.lastChild.textContent = label;
|
||||
} else {
|
||||
LOG("FeedWriter._setAlwaysUseLabel: Expected textNode as lastChild of alwaysUse label");
|
||||
let textNode = this._document.createTextNode(label);
|
||||
checkboxText.appendChild(textNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// EventListener
|
||||
handleEvent(event) {
|
||||
if (event.target.ownerDocument != this._document) {
|
||||
LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case "click":
|
||||
if (event.target.id == "subscribeButton") {
|
||||
this.subscribe();
|
||||
}
|
||||
break;
|
||||
case "change":
|
||||
LOG("Change fired");
|
||||
if (event.target.selectedOptions[0].id == "chooseApplicationMenuItem") {
|
||||
this._chooseClientApp(() => {
|
||||
// Select the (per-prefs) selected handler if no application
|
||||
// was selected
|
||||
LOG("Selected handler after callback");
|
||||
this._setAlwaysUseLabel();
|
||||
});
|
||||
} else {
|
||||
this._setAlwaysUseLabel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_setSelectedHandlerResponse(handler) {
|
||||
LOG(`Selecting handler response ${handler}`);
|
||||
switch (handler) {
|
||||
case "client":
|
||||
case "default":
|
||||
// do nothing, these are handled by the onchange event
|
||||
break;
|
||||
case "bookmarks":
|
||||
default: {
|
||||
let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
|
||||
if (liveBookmarksMenuItem)
|
||||
liveBookmarksMenuItem.selected = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_initSubscriptionUI(setupMessage) {
|
||||
if (!this._handlersList)
|
||||
return;
|
||||
LOG("UI init");
|
||||
|
||||
let feedType = this._getFeedType();
|
||||
|
||||
// change the background
|
||||
let header = this._document.getElementById("feedHeader");
|
||||
switch (feedType) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
header.className = "videoPodcastBackground";
|
||||
break;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
header.className = "audioPodcastBackground";
|
||||
break;
|
||||
|
||||
default:
|
||||
header.className = "feedBackground";
|
||||
}
|
||||
|
||||
let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
|
||||
|
||||
// Last-selected application
|
||||
let menuItem = liveBookmarksMenuItem.cloneNode(false);
|
||||
menuItem.removeAttribute("selected");
|
||||
menuItem.setAttribute("id", "selectedAppMenuItem");
|
||||
menuItem.setAttribute("handlerType", "client");
|
||||
|
||||
// Hide the menuitem until we select an app
|
||||
menuItem.style.display = "none";
|
||||
this._selectedAppMenuItem = menuItem;
|
||||
|
||||
this._handlersList.appendChild(this._selectedAppMenuItem);
|
||||
|
||||
// Create the menuitem for the default reader, but don't show/populate it until
|
||||
// we get confirmation of what it is from the parent
|
||||
menuItem = liveBookmarksMenuItem.cloneNode(false);
|
||||
menuItem.removeAttribute("selected");
|
||||
menuItem.setAttribute("id", "defaultHandlerMenuItem");
|
||||
menuItem.setAttribute("handlerType", "client");
|
||||
menuItem.style.display = "none";
|
||||
|
||||
this._defaultHandlerMenuItem = menuItem;
|
||||
this._handlersList.appendChild(this._defaultHandlerMenuItem);
|
||||
|
||||
// "Choose Application..." menuitem
|
||||
menuItem = liveBookmarksMenuItem.cloneNode(false);
|
||||
menuItem.removeAttribute("selected");
|
||||
menuItem.setAttribute("id", "chooseApplicationMenuItem");
|
||||
menuItem.textContent = this._getString("chooseApplicationMenuItem");
|
||||
|
||||
this._handlersList.appendChild(menuItem);
|
||||
|
||||
this._setSelectedHandlerResponse(setupMessage.reader.handler);
|
||||
|
||||
if (setupMessage.defaultMenuItem) {
|
||||
LOG(`Setting default menu item ${setupMessage.defaultMenuItem}`);
|
||||
this._setApplicationLauncherMenuItem(this._defaultHandlerMenuItem, setupMessage.defaultMenuItem);
|
||||
}
|
||||
if (setupMessage.selectedMenuItem) {
|
||||
LOG(`Setting selected menu item ${setupMessage.selectedMenuItem}`);
|
||||
this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, setupMessage.selectedMenuItem);
|
||||
}
|
||||
|
||||
// "Subscribe using..."
|
||||
this._setSubscribeUsingLabel();
|
||||
|
||||
// "Always use..." checkbox initial state
|
||||
this._setCheckboxCheckedState(setupMessage.reader.alwaysUse);
|
||||
this._setAlwaysUseLabel();
|
||||
|
||||
// We update the "Always use.." checkbox label whenever the selected item
|
||||
// in the list is changed
|
||||
this._handlersList.addEventListener("change", this);
|
||||
|
||||
// Set up the "Subscribe Now" button
|
||||
this._document.getElementById("subscribeButton")
|
||||
.addEventListener("click", this);
|
||||
|
||||
// first-run ui
|
||||
if (setupMessage.showFirstRunUI) {
|
||||
let textfeedinfo1, textfeedinfo2;
|
||||
switch (feedType) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
textfeedinfo1 = "feedSubscriptionVideoPodcast1";
|
||||
textfeedinfo2 = "feedSubscriptionVideoPodcast2";
|
||||
break;
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
textfeedinfo1 = "feedSubscriptionAudioPodcast1";
|
||||
textfeedinfo2 = "feedSubscriptionAudioPodcast2";
|
||||
break;
|
||||
default:
|
||||
textfeedinfo1 = "feedSubscriptionFeed1";
|
||||
textfeedinfo2 = "feedSubscriptionFeed2";
|
||||
}
|
||||
|
||||
let feedinfo1 = this._document.getElementById("feedSubscriptionInfo1");
|
||||
let feedinfo1Str = this._getString(textfeedinfo1);
|
||||
let feedinfo2 = this._document.getElementById("feedSubscriptionInfo2");
|
||||
let feedinfo2Str = this._getString(textfeedinfo2);
|
||||
|
||||
feedinfo1.textContent = feedinfo1Str;
|
||||
feedinfo2.textContent = feedinfo2Str;
|
||||
|
||||
header.setAttribute("firstrun", "true");
|
||||
|
||||
this._mm.sendAsyncMessage("FeedWriter:ShownFirstRun");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the original URI object of the feed and ensures that this
|
||||
* component is only ever invoked from the preview document.
|
||||
|
@ -735,7 +462,6 @@ FeedWriter.prototype = {
|
|||
_document: null,
|
||||
_feedURI: null,
|
||||
_feedPrincipal: null,
|
||||
_handlersList: null,
|
||||
|
||||
// BrowserFeedWriter WebIDL methods
|
||||
init(aWindow) {
|
||||
|
@ -749,73 +475,10 @@ FeedWriter.prototype = {
|
|||
|
||||
this._window = window;
|
||||
this._document = window.document;
|
||||
this._handlersList = this._document.getElementById("handlersMenuList");
|
||||
|
||||
this._feedPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(this._feedURI, {});
|
||||
|
||||
LOG("Subscribe Preview: feed uri = " + this._window.location.href);
|
||||
|
||||
|
||||
this._mm.addMessageListener("FeedWriter:PreferenceUpdated", this);
|
||||
this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
|
||||
this._mm.addMessageListener("FeedWriter:GetSubscriptionUIResponse", this);
|
||||
|
||||
const feedType = this._getFeedType();
|
||||
this._mm.sendAsyncMessage("FeedWriter:GetSubscriptionUI",
|
||||
{ feedType });
|
||||
},
|
||||
|
||||
receiveMessage(msg) {
|
||||
if (!this._window) {
|
||||
// this._window is null unless this.init was called with a trusted
|
||||
// window object.
|
||||
return;
|
||||
}
|
||||
LOG(`received message from parent ${msg.name}`);
|
||||
switch (msg.name) {
|
||||
case "FeedWriter:PreferenceUpdated":
|
||||
// This is called when browser-feeds.js spots a pref change
|
||||
// This will happen when
|
||||
// - about:preferences#general changes
|
||||
// - another feed reader page changes the preference
|
||||
// - when this page itself changes the select and there isn't a redirect
|
||||
// bookmarks and launching an external app means the page stays open after subscribe
|
||||
const feedType = this._getFeedType();
|
||||
LOG(`Got prefChange! ${JSON.stringify(msg.data)} current type: ${feedType}`);
|
||||
let feedTypePref = msg.data.default;
|
||||
if (feedType in msg.data) {
|
||||
feedTypePref = msg.data[feedType];
|
||||
}
|
||||
LOG(`Got pref ${JSON.stringify(feedTypePref)}`);
|
||||
this._setCheckboxCheckedState(feedTypePref.alwaysUse);
|
||||
this._setSelectedHandlerResponse(feedTypePref.handler);
|
||||
this._setAlwaysUseLabel();
|
||||
break;
|
||||
case "FeedWriter:GetSubscriptionUIResponse":
|
||||
// Set up the subscription UI
|
||||
this._initSubscriptionUI(msg.data);
|
||||
break;
|
||||
case "FeedWriter:SetApplicationLauncherMenuItem":
|
||||
LOG(`FeedWriter:SetApplicationLauncherMenuItem - picked ${msg.data.name}`);
|
||||
this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, msg.data.name);
|
||||
// Potentially a bit racy, but I don't think we can get into a state where this callback is set and
|
||||
// we're not coming back from ChooseClientApp in browser-feeds.js
|
||||
if (this._subscribeCallback) {
|
||||
this._subscribeCallback();
|
||||
this._subscribeCallback = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_setApplicationLauncherMenuItem(menuItem, aName) {
|
||||
/* unselect all handlers */
|
||||
[...this._handlersList.children].forEach((option) => {
|
||||
option.removeAttribute("selected");
|
||||
});
|
||||
menuItem.textContent = aName;
|
||||
menuItem.style.display = "";
|
||||
menuItem.selected = true;
|
||||
},
|
||||
|
||||
writeContent() {
|
||||
|
@ -840,21 +503,12 @@ FeedWriter.prototype = {
|
|||
if (!this._window) {
|
||||
return;
|
||||
}
|
||||
this._document.getElementById("subscribeButton")
|
||||
.removeEventListener("click", this);
|
||||
this._handlersList
|
||||
.removeEventListener("change", this);
|
||||
this._document = null;
|
||||
this._window = null;
|
||||
this._handlersList = null;
|
||||
|
||||
this._removeFeedFromCache();
|
||||
this.__bundle = null;
|
||||
this._feedURI = null;
|
||||
|
||||
this._selectedApp = undefined;
|
||||
this._selectedAppMenuItem = null;
|
||||
this._defaultHandlerMenuItem = null;
|
||||
},
|
||||
|
||||
_removeFeedFromCache() {
|
||||
|
@ -866,69 +520,6 @@ FeedWriter.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
if (!this._window) {
|
||||
return;
|
||||
}
|
||||
let feedType = this._getFeedType();
|
||||
|
||||
// Subscribe to the feed using the selected handler and save prefs
|
||||
let defaultHandler = "reader";
|
||||
let useAsDefault = this._document.getElementById("alwaysUse").getAttribute("checked");
|
||||
|
||||
let selectedItem = this._handlersList.selectedOptions[0];
|
||||
let subscribeCallback = () => {
|
||||
let feedReader = null;
|
||||
let settings = {
|
||||
feedType,
|
||||
useAsDefault,
|
||||
// Pull the title and subtitle out of the document
|
||||
feedTitle: this._document.getElementById(TITLE_ID).textContent,
|
||||
feedSubtitle: this._document.getElementById(SUBTITLE_ID).textContent,
|
||||
feedLocation: this._window.location.href,
|
||||
};
|
||||
switch (selectedItem.id) {
|
||||
case "selectedAppMenuItem":
|
||||
feedReader = "client";
|
||||
break;
|
||||
case "defaultHandlerMenuItem":
|
||||
feedReader = "default";
|
||||
break;
|
||||
case "liveBookmarksMenuItem":
|
||||
defaultHandler = "bookmarks";
|
||||
feedReader = "bookmarks";
|
||||
break;
|
||||
}
|
||||
settings.reader = feedReader;
|
||||
|
||||
// If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
|
||||
// to either "reader" (If an application is selected),
|
||||
// or to "bookmarks" (if the live bookmarks option is selected).
|
||||
// Otherwise, we should set it to "ask"
|
||||
if (!useAsDefault) {
|
||||
defaultHandler = "ask";
|
||||
}
|
||||
settings.action = defaultHandler;
|
||||
LOG(`FeedWriter:SetFeedPrefsAndSubscribe - ${JSON.stringify(settings)}`);
|
||||
this._mm.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribe",
|
||||
settings);
|
||||
};
|
||||
|
||||
// Show the file picker before subscribing if the
|
||||
// choose application menuitem was chosen using the keyboard
|
||||
if (selectedItem.id == "chooseApplicationMenuItem") {
|
||||
this._chooseClientApp(aResult => {
|
||||
if (aResult) {
|
||||
selectedItem =
|
||||
this._handlersList.selectedOptions[0];
|
||||
subscribeCallback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
subscribeCallback();
|
||||
}
|
||||
},
|
||||
|
||||
classID: FEEDWRITER_CID,
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsIDOMGlobalPropertyInitializer]),
|
||||
|
|
|
@ -36,18 +36,6 @@
|
|||
<p id="feedSubscriptionInfo1" />
|
||||
<p id="feedSubscriptionInfo2" />
|
||||
</div>
|
||||
<div id="feedSubscribeLine">
|
||||
<label id="subscribeUsingDescription">
|
||||
<select id="handlersMenuList">
|
||||
<option id="liveBookmarksMenuItem" selected="true">&feedLiveBookmarks;</option>
|
||||
<option disabled="true">━━━━━━━</option>
|
||||
</select>
|
||||
</label>
|
||||
<label id="checkboxText">
|
||||
<input type="checkbox" id="alwaysUse" class="alwaysUse" checked="false"/>
|
||||
</label>
|
||||
<button id="subscribeButton">&feedSubscribeNow;</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="feedHeaderContainerSpacer"/>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
[browser_registerProtocolHandler_notification.js]
|
||||
support-files =
|
||||
browser_registerProtocolHandler_notification.html
|
||||
[browser_telemetry_checks.js]
|
||||
support-files =
|
||||
valid-feed.xml
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function() {
|
||||
function getSnapShot() {
|
||||
return Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT, false);
|
||||
}
|
||||
const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
|
||||
const FEED_URI = TEST_PATH + "valid-feed.xml";
|
||||
|
||||
// Ensure we don't have any pre-existing telemetry that'd mess up counts in here:
|
||||
Services.telemetry.clearScalars();
|
||||
const kScalarPrefix = "browser.feeds.";
|
||||
const kPreviewLoaded = kScalarPrefix + "preview_loaded";
|
||||
const kSubscribed = kScalarPrefix + "feed_subscribed";
|
||||
const kLivemarkCount = kScalarPrefix + "livebookmark_count";
|
||||
const kLivemarkOpened = kScalarPrefix + "livebookmark_opened";
|
||||
const kLivemarkItemOpened = kScalarPrefix + "livebookmark_item_opened";
|
||||
|
||||
let scalarForContent = gMultiProcessBrowser ? "content" : "parent";
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, FEED_URI);
|
||||
// Ensure we get telemetry from the content process:
|
||||
let previewCount = await TestUtils.waitForCondition(() => {
|
||||
let snapshot = getSnapShot()[scalarForContent];
|
||||
return snapshot && snapshot[kPreviewLoaded];
|
||||
});
|
||||
Assert.equal(previewCount, 1, "Should register the preview in telemetry.");
|
||||
|
||||
// Now check subscription. We stub out the actual code for adding the live bookmark,
|
||||
// because the dialog creates an initial copy of the bookmark when it's opened and
|
||||
// that's hard to deal with deterministically in tests.
|
||||
let old = PlacesCommandHook.addLiveBookmark;
|
||||
let createBMPromise = new Promise(resolve => {
|
||||
PlacesCommandHook.addLiveBookmark = function(...args) {
|
||||
resolve(args);
|
||||
// Return the promise because Feeds.jsm expects a promise:
|
||||
return createBMPromise;
|
||||
};
|
||||
});
|
||||
registerCleanupFunction(() => PlacesCommandHook.addLiveBookmark = old);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#subscribeButton", {}, tab.linkedBrowser);
|
||||
let bmArgs = await createBMPromise;
|
||||
Assert.deepEqual(bmArgs, [FEED_URI, "Example Feed"], "Should have been trying to subscribe");
|
||||
let snapshot = getSnapShot();
|
||||
Assert.equal(snapshot.parent[kSubscribed], 1, "Should have subscribed once.");
|
||||
|
||||
// Now manually add a livemark in the menu and one in the bookmarks toolbar:
|
||||
let livemarks = await Promise.all([
|
||||
PlacesUtils.livemarks.addLivemark({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
feedURI: Services.io.newURI(FEED_URI),
|
||||
}),
|
||||
PlacesUtils.livemarks.addLivemark({
|
||||
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
feedURI: Services.io.newURI(FEED_URI),
|
||||
}),
|
||||
]);
|
||||
registerCleanupFunction(async () => {
|
||||
for (let mark of livemarks) {
|
||||
await PlacesUtils.livemarks.removeLivemark(mark);
|
||||
}
|
||||
});
|
||||
|
||||
if (document.getElementById("PersonalToolbar").getAttribute("collapsed") == "true") {
|
||||
CustomizableUI.setToolbarVisibility("PersonalToolbar", true);
|
||||
registerCleanupFunction(() => CustomizableUI.setToolbarVisibility("PersonalToolbar", false));
|
||||
}
|
||||
|
||||
// Force updating telemetry:
|
||||
let {PlacesDBUtils} = ChromeUtils.import("resource://gre/modules/PlacesDBUtils.jsm", {});
|
||||
await PlacesDBUtils._telemetryForFeeds();
|
||||
Assert.equal(getSnapShot().parent[kLivemarkCount], 2,
|
||||
"Should have created two livemarks and counted them.");
|
||||
|
||||
info("Waiting for livemark");
|
||||
// Check we count opening the livemark popup:
|
||||
let livemarkOnToolbar = await TestUtils.waitForCondition(
|
||||
() => document.querySelector("#PersonalToolbar .bookmark-item[livemark]"));
|
||||
let popup = livemarkOnToolbar.querySelector("menupopup");
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
|
||||
info("Clicking on livemark");
|
||||
// Using .click() or .doCommand() doesn't seem to work.
|
||||
EventUtils.synthesizeMouseAtCenter(livemarkOnToolbar, {});
|
||||
await popupShownPromise;
|
||||
Assert.equal(getSnapShot().parent[kLivemarkOpened], 1, "Should count livemark opening");
|
||||
|
||||
// And opening an item in the popup:
|
||||
let item = await TestUtils.waitForCondition(
|
||||
() => popup.querySelector("menuitem.bookmark-item"));
|
||||
item.doCommand();
|
||||
Assert.equal(getSnapShot().parent[kLivemarkItemOpened], 1, "Should count livemark item opening");
|
||||
popup.hidePopup();
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
|
||||
<title>Example Feed</title>
|
||||
<link href="http://example.org/"/>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
</author>
|
||||
<id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
|
||||
|
||||
<entry>
|
||||
|
||||
<title>Item</title>
|
||||
<link href="http://example.org/first"/>
|
||||
<id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<summary>Some text.</summary>
|
||||
</entry>
|
||||
|
||||
</feed>
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
|
||||
<title>Example Feed</title>
|
||||
<link href="http://example.org/"/>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
</author>
|
||||
<id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
|
||||
|
||||
<entry>
|
||||
|
||||
<title>Item</title>
|
||||
<link href="http://example.org/first"/>
|
||||
<id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<summary>Some text.</summary>
|
||||
</entry>
|
||||
|
||||
</feed>
|
|
@ -7,7 +7,6 @@ support-files =
|
|||
bug408328-data.xml
|
||||
bug436801-data.xml
|
||||
bug494328-data.xml
|
||||
bug589543-data.xml
|
||||
valid-feed.xml
|
||||
valid-unsniffable-feed.xml
|
||||
|
||||
|
@ -17,6 +16,5 @@ support-files =
|
|||
bug364677-data.xml^headers^
|
||||
[test_bug436801.html]
|
||||
[test_bug494328.html]
|
||||
[test_bug589543.html]
|
||||
[test_registerHandler.html]
|
||||
[test_registerHandler_disabled.html]
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=589543
|
||||
-->
|
||||
<head>
|
||||
<title>Test feed preview subscribe UI</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589543">Mozilla Bug 589543</a>
|
||||
<p id="display"><iframe id="testFrame" src="bug589543-data.xml"></iframe></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 589543 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
addLoadEvent(function() {
|
||||
var doc = SpecialPowers.wrap($("testFrame")).contentDocument;
|
||||
var popup = doc.getElementById("handlersMenuList");
|
||||
isnot(popup, null, "Feed preview should have a handlers popup");
|
||||
});
|
||||
addLoadEvent(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -194,8 +194,6 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
|||
<!ENTITY editThisBookmarkCmd.label "Edit This Bookmark">
|
||||
<!ENTITY bookmarkThisPageCmd.commandkey "d">
|
||||
|
||||
<!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
|
||||
<!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
|
||||
<!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">
|
||||
<!ENTITY showAllBookmarks2.label "Show All Bookmarks">
|
||||
<!ENTITY recentBookmarks.label "Recently Bookmarked">
|
||||
|
|
|
@ -73,9 +73,6 @@ paste-button.label = Paste
|
|||
# LOCALIZATION NOTE(paste-button.tooltiptext2): %S is the keyboard shortcut.
|
||||
paste-button.tooltiptext2 = Paste (%S)
|
||||
|
||||
feed-button.label = Subscribe
|
||||
feed-button.tooltiptext2 = Subscribe to this page
|
||||
|
||||
# LOCALIZATION NOTE (characterencoding-button2.label): The \u00ad text at the beginning
|
||||
# of the string is used to disable auto hyphenation on the button text when it is displayed
|
||||
# in the menu panel.
|
||||
|
|
|
@ -44,9 +44,6 @@
|
|||
<!ENTITY mediaSaveAs2.accesskey "e">
|
||||
<!ENTITY mediaPreview "Media Preview:">
|
||||
|
||||
<!ENTITY feedTab "Feeds">
|
||||
<!ENTITY feedTab.accesskey "F">
|
||||
|
||||
<!ENTITY permTab "Permissions">
|
||||
<!ENTITY permTab.accesskey "P">
|
||||
<!ENTITY permissionsFor "Permissions for:">
|
||||
|
|
|
@ -38,12 +38,6 @@ generalSize=%S KB (%S bytes)
|
|||
generalMetaTag=Meta (1 tag)
|
||||
generalMetaTags=Meta (%S tags)
|
||||
|
||||
feedRss=RSS
|
||||
feedAtom=Atom
|
||||
feedXML=XML
|
||||
feedSubscribe=Subscribe
|
||||
feedSubscribe.accesskey=u
|
||||
|
||||
securityNoOwner=This website does not supply ownership information.
|
||||
# LOCALIZATION NOTE (securityVisitsNumber):
|
||||
# Semi-colon list of plural forms.
|
||||
|
|
|
@ -6,13 +6,6 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = [ "Feeds" ];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
|
||||
"resource://gre/modules/BrowserUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
|
||||
"resource:///modules/BrowserWindowTracker.jsm");
|
||||
|
||||
var Feeds = {
|
||||
// Listeners are added in nsBrowserGlue.js
|
||||
receiveMessage(aMessage) {
|
||||
|
@ -25,48 +18,6 @@ var Feeds = {
|
|||
aMessage.target);
|
||||
break;
|
||||
}
|
||||
|
||||
case "FeedConverter:addLiveBookmark": {
|
||||
let topWindow = BrowserWindowTracker.getTopWindow();
|
||||
topWindow.PlacesCommandHook.addLiveBookmark(data.spec, data.title)
|
||||
.catch(Cu.reportError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* isValidFeed: checks whether the given data represents a valid feed.
|
||||
*
|
||||
* @param aLink
|
||||
* An object representing a feed with title, href and type.
|
||||
* @param aPrincipal
|
||||
* The principal of the document, used for security check.
|
||||
* @param aIsFeed
|
||||
* Whether this is already a known feed or not, if true only a security
|
||||
* check will be performed.
|
||||
*/
|
||||
isValidFeed(aLink, aPrincipal, aIsFeed) {
|
||||
if (!aLink || !aPrincipal)
|
||||
return false;
|
||||
|
||||
var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
|
||||
if (!aIsFeed) {
|
||||
aIsFeed = (type == "application/rss+xml" ||
|
||||
type == "application/atom+xml");
|
||||
}
|
||||
|
||||
if (aIsFeed) {
|
||||
try {
|
||||
let href = Services.io.newURI(aLink.href, aLink.ownerDocument.characterSet);
|
||||
BrowserUtils.urlSecurityCheck(href, aPrincipal,
|
||||
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
|
||||
return type || "application/rss+xml";
|
||||
} catch (ex) {
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -1,6 +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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M3.5 10A2.5 2.5 0 1 0 6 12.5 2.5 2.5 0 0 0 3.5 10zM2 1a1 1 0 0 0 0 2 10.883 10.883 0 0 1 11 11 1 1 0 0 0 2 0A12.862 12.862 0 0 0 2 1zm0 4a1 1 0 0 0 0 2 6.926 6.926 0 0 1 7 7 1 1 0 0 0 2 0 8.9 8.9 0 0 0-9-9z"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 580 B |
|
@ -148,7 +148,6 @@
|
|||
skin/classic/browser/edit-copy.svg (../shared/icons/edit-copy.svg)
|
||||
skin/classic/browser/edit-cut.svg (../shared/icons/edit-cut.svg)
|
||||
skin/classic/browser/edit-paste.svg (../shared/icons/edit-paste.svg)
|
||||
skin/classic/browser/feed.svg (../shared/icons/feed.svg)
|
||||
skin/classic/browser/folder.svg (../shared/icons/folder.svg)
|
||||
skin/classic/browser/forget.svg (../shared/icons/forget.svg)
|
||||
skin/classic/browser/forward.svg (../shared/icons/forward.svg)
|
||||
|
|
|
@ -212,10 +212,6 @@ toolbar[brighttext] {
|
|||
list-style-image: url("chrome://browser/skin/tab.svg");
|
||||
}
|
||||
|
||||
#feed-button {
|
||||
list-style-image: url("chrome://browser/skin/feed.svg");
|
||||
}
|
||||
|
||||
#characterencoding-button {
|
||||
list-style-image: url("chrome://browser/skin/characterEncoding.svg");
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ class ConnectPage extends PureComponent {
|
|||
render() {
|
||||
return dom.article(
|
||||
{
|
||||
className: "page connect-page",
|
||||
className: "page connect-page js-connect-page",
|
||||
},
|
||||
dom.h1(
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@ class NetworkLocationsForm extends PureComponent {
|
|||
},
|
||||
dom.span({}, "Host:port"),
|
||||
dom.input({
|
||||
className: "connect-page__network-form__input",
|
||||
className: "connect-page__network-form__input js-network-form-input",
|
||||
placeholder: "localhost:6080",
|
||||
type: "text",
|
||||
value: this.state.value,
|
||||
|
@ -49,7 +49,7 @@ class NetworkLocationsForm extends PureComponent {
|
|||
}
|
||||
}),
|
||||
dom.button({
|
||||
className: "aboutdebugging-button"
|
||||
className: "aboutdebugging-button js-network-form-submit-button"
|
||||
}, "Add")
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,17 +24,17 @@ class NetworkLocationsList extends PureComponent {
|
|||
this.props.networkLocations.map(location =>
|
||||
dom.li(
|
||||
{
|
||||
className: "connect-page__network-location"
|
||||
className: "connect-page__network-location js-network-location"
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "ellipsis-text"
|
||||
className: "ellipsis-text js-network-location-value"
|
||||
},
|
||||
location
|
||||
),
|
||||
dom.button(
|
||||
{
|
||||
className: "aboutdebugging-button",
|
||||
className: "aboutdebugging-button js-network-location-remove-button",
|
||||
onClick: () => {
|
||||
this.props.dispatch(Actions.removeNetworkLocation(location));
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class SidebarItem extends PureComponent {
|
|||
|
||||
return dom.li(
|
||||
{
|
||||
className: "sidebar-item" +
|
||||
className: "sidebar-item js-sidebar-item" +
|
||||
(isSelected ?
|
||||
" sidebar-item--selected js-sidebar-item-selected" :
|
||||
""
|
||||
|
|
|
@ -7,3 +7,4 @@ support-files =
|
|||
!/devtools/client/shared/test/telemetry-test-helpers.js
|
||||
|
||||
[browser_aboutdebugging_thisfirefox.js]
|
||||
[browser_aboutdebugging_connect_networklocations.js]
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the network locations form of the Connect page.
|
||||
* Check that a network location can be added and removed.
|
||||
*/
|
||||
|
||||
const TEST_NETWORK_LOCATION = "localhost:1111";
|
||||
|
||||
add_task(async function() {
|
||||
const { document, tab } = await openAboutDebugging();
|
||||
|
||||
const sidebarItems = document.querySelectorAll(".js-sidebar-item");
|
||||
const connectSidebarItem = [...sidebarItems].find(element => {
|
||||
return element.textContent === "Connect";
|
||||
});
|
||||
ok(connectSidebarItem, "Sidebar contains a Connect item");
|
||||
|
||||
info("Click on the Connect item in the sidebar");
|
||||
connectSidebarItem.click();
|
||||
|
||||
info("Wait until Connect page is displayed");
|
||||
await waitUntil(() => document.querySelector(".js-connect-page"));
|
||||
|
||||
let networkLocations = document.querySelectorAll(".js-network-location");
|
||||
is(networkLocations.length, 0, "By default, no network locations are displayed");
|
||||
|
||||
addNetworkLocation(TEST_NETWORK_LOCATION, document);
|
||||
|
||||
info("Wait until the new network location is visible in the list");
|
||||
await waitUntil(() => document.querySelectorAll(".js-network-location").length === 1);
|
||||
networkLocations = document.querySelectorAll(".js-network-location");
|
||||
|
||||
const networkLocationValue =
|
||||
networkLocations[0].querySelector(".js-network-location-value");
|
||||
is(networkLocationValue.textContent, TEST_NETWORK_LOCATION,
|
||||
"Added network location has the expected value");
|
||||
|
||||
removeNetworkLocation(TEST_NETWORK_LOCATION, document);
|
||||
|
||||
info("Wait until the new network location is removed from the list");
|
||||
await waitUntil(() => document.querySelectorAll(".js-network-location").length === 0);
|
||||
|
||||
await removeTab(tab);
|
||||
});
|
||||
|
||||
function addNetworkLocation(location, document) {
|
||||
info("Setting a value in the network form input");
|
||||
const networkLocationInput =
|
||||
document.querySelector(".js-network-form-input");
|
||||
networkLocationInput.focus();
|
||||
EventUtils.sendString(location, networkLocationInput.ownerGlobal);
|
||||
|
||||
info("Click on network form submit button");
|
||||
const networkLocationSubmitButton =
|
||||
document.querySelector(".js-network-form-submit-button");
|
||||
networkLocationSubmitButton.click();
|
||||
}
|
||||
|
||||
function removeNetworkLocation(location, document) {
|
||||
const networkLocation = getNetworkLocation(location, document);
|
||||
ok(networkLocation, "Network location container found.");
|
||||
|
||||
info("Click on the remove button for the provided network location");
|
||||
const removeButton =
|
||||
networkLocation.querySelector(".js-network-location-remove-button");
|
||||
removeButton.click();
|
||||
}
|
||||
|
||||
function getNetworkLocation(location, document) {
|
||||
info("Find the container for network location: " + location);
|
||||
const networkLocations = document.querySelectorAll(".js-network-location");
|
||||
return [...networkLocations].find(element => {
|
||||
return element.querySelector(".js-network-location-value").textContent === location;
|
||||
});
|
||||
}
|
|
@ -20,9 +20,6 @@ const EXPECTED_TARGET_PANES = [
|
|||
add_task(async function() {
|
||||
const { document, tab } = await openAboutDebugging();
|
||||
|
||||
// Wait until the client connection was established.
|
||||
await waitUntil(() => document.querySelector(".js-runtime-page"));
|
||||
|
||||
// Check that the selected sidebar item is "This Firefox"
|
||||
const selectedSidebarItem = document.querySelector(".js-sidebar-item-selected");
|
||||
ok(selectedSidebarItem, "An item is selected in the sidebar");
|
||||
|
|
|
@ -31,5 +31,8 @@ async function openAboutDebugging(page, win) {
|
|||
info("Wait until the main about debugging container is available");
|
||||
await waitUntil(() => document.querySelector(".app"));
|
||||
|
||||
info("Wait until the client connection was established");
|
||||
await waitUntil(() => document.querySelector(".js-runtime-page"));
|
||||
|
||||
return { tab, document, window };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
trackChange,
|
||||
} = require("./actions/changes");
|
||||
|
||||
class ChangesManager {
|
||||
constructor(inspector) {
|
||||
this.store = inspector.store;
|
||||
}
|
||||
|
||||
track(change) {
|
||||
this.store.dispatch(trackChange(change));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.store = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChangesManager;
|
|
@ -0,0 +1,53 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
|
||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const ChangesApp = createFactory(require("./components/ChangesApp"));
|
||||
|
||||
const {
|
||||
resetChanges,
|
||||
} = require("./actions/changes");
|
||||
|
||||
class ChangesView {
|
||||
constructor(inspector) {
|
||||
this.inspector = inspector;
|
||||
this.store = this.inspector.store;
|
||||
|
||||
this.destroy = this.destroy.bind(this);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const changesApp = ChangesApp({});
|
||||
|
||||
// Expose the provider to let inspector.js use it in setupSidebar.
|
||||
this.provider = createElement(Provider, {
|
||||
id: "changesview",
|
||||
key: "changesview",
|
||||
store: this.store,
|
||||
}, changesApp);
|
||||
|
||||
// TODO: save store and restore/replay on refresh.
|
||||
// Bug 1478439 - https://bugzilla.mozilla.org/show_bug.cgi?id=1478439
|
||||
this.inspector.target.once("will-navigate", this.destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destruction function called when the inspector is destroyed.
|
||||
*/
|
||||
destroy() {
|
||||
this.store.dispatch(resetChanges());
|
||||
this.inspector = null;
|
||||
this.store = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChangesView;
|
|
@ -0,0 +1,27 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
RESET_CHANGES,
|
||||
TRACK_CHANGE,
|
||||
} = require("./index");
|
||||
|
||||
module.exports = {
|
||||
|
||||
resetChanges() {
|
||||
return {
|
||||
type: RESET_CHANGES,
|
||||
};
|
||||
},
|
||||
|
||||
trackChange(data) {
|
||||
return {
|
||||
type: TRACK_CHANGE,
|
||||
data,
|
||||
};
|
||||
},
|
||||
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createEnum } = require("devtools/client/shared/enum");
|
||||
|
||||
createEnum([
|
||||
|
||||
// Remove all changes
|
||||
"RESET_CHANGES",
|
||||
|
||||
// Track a style change
|
||||
"TRACK_CHANGE",
|
||||
|
||||
], module.exports);
|
|
@ -0,0 +1,10 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'changes.js',
|
||||
'index.js',
|
||||
)
|
|
@ -0,0 +1,78 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { 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 { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
class ChangesApp extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
changes: PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
renderMutations(remove = {}, add = {}) {
|
||||
const removals = Object.entries(remove).map(([prop, value]) => {
|
||||
return dom.div(
|
||||
{ className: "line diff-remove"},
|
||||
`${prop}: ${value};`
|
||||
);
|
||||
});
|
||||
|
||||
const additions = Object.entries(add).map(([prop, value]) => {
|
||||
return dom.div(
|
||||
{ className: "line diff-add"},
|
||||
`${prop}: ${value};`
|
||||
);
|
||||
});
|
||||
|
||||
return [removals, additions];
|
||||
}
|
||||
|
||||
renderSelectors(selectors = {}) {
|
||||
return Object.keys(selectors).map(sel => {
|
||||
return dom.details(
|
||||
{ className: "selector", open: true },
|
||||
dom.summary(
|
||||
{
|
||||
title: sel,
|
||||
},
|
||||
sel),
|
||||
this.renderMutations(selectors[sel].remove, selectors[sel].add)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderDiff(diff = {}) {
|
||||
// Render groups of style sources: stylesheets, embedded styles and inline styles
|
||||
return Object.keys(diff).map(href => {
|
||||
return dom.details(
|
||||
{ className: "source", open: true },
|
||||
dom.summary(
|
||||
{
|
||||
title: href,
|
||||
},
|
||||
href),
|
||||
// Render groups of selectors
|
||||
this.renderSelectors(diff[href])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return dom.div(
|
||||
{
|
||||
className: "theme-sidebar inspector-tabpanel",
|
||||
id: "sidebar-panel-changes"
|
||||
},
|
||||
this.renderDiff(this.props.changes.diff)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state)(ChangesApp);
|
|
@ -0,0 +1,9 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'ChangesApp.js',
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'actions',
|
||||
'components',
|
||||
'reducers',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'ChangesManager.js',
|
||||
'ChangesView.js',
|
||||
)
|
|
@ -0,0 +1,128 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
RESET_CHANGES,
|
||||
TRACK_CHANGE,
|
||||
} = require("../actions/index");
|
||||
|
||||
const INITIAL_STATE = {
|
||||
/**
|
||||
* Diff of changes grouped by stylesheet href, then by selector, then into add/remove
|
||||
* objects with CSS property names and values for corresponding changes.
|
||||
*
|
||||
* Structure:
|
||||
*
|
||||
* diff = {
|
||||
* "href": {
|
||||
* "selector": {
|
||||
* add: {
|
||||
* "property": value
|
||||
* ... // more properties
|
||||
* },
|
||||
* remove: {
|
||||
* "property": value
|
||||
* ...
|
||||
* }
|
||||
* },
|
||||
* ... // more selectors
|
||||
* }
|
||||
* ... // more stylesheet hrefs
|
||||
* }
|
||||
*/
|
||||
diff: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutate the given diff object with data about a new change.
|
||||
*
|
||||
* @param {Object} diff
|
||||
* Diff object from the store.
|
||||
* @param {Object} change
|
||||
* Data about the change: which property was added or removed, on which selector,
|
||||
* in which stylesheet or whether the source is an element's inline style.
|
||||
* @return {Object}
|
||||
* Mutated diff object.
|
||||
*/
|
||||
function updateDiff(diff = {}, change = {}) {
|
||||
// Ensure expected diff structure exists
|
||||
diff[change.href] = diff[change.href] || {};
|
||||
diff[change.href][change.selector] = diff[change.href][change.selector] || {};
|
||||
diff[change.href][change.selector].add = diff[change.href][change.selector].add || {};
|
||||
diff[change.href][change.selector].remove = diff[change.href][change.selector].remove
|
||||
|| {};
|
||||
|
||||
// Reference to the diff data for this stylesheet/selector pair.
|
||||
const ref = diff[change.href][change.selector];
|
||||
|
||||
// Track the remove operation only if the property WAS NOT previously introduced by an
|
||||
// add operation. This ensures that repeated changes of the same property show up as a
|
||||
// single remove operation of the original value.
|
||||
if (change.remove && change.remove.property && !ref.add[change.remove.property]) {
|
||||
ref.remove[change.remove.property] = change.remove.value;
|
||||
}
|
||||
|
||||
if (change.add && change.add.property) {
|
||||
ref.add[change.add.property] = change.add.value;
|
||||
}
|
||||
|
||||
const propertyName = change.add && change.add.property ||
|
||||
change.remove && change.remove.property;
|
||||
|
||||
// Remove information about operations on the property if they cancel each other out.
|
||||
if (ref.add[propertyName] === ref.remove[propertyName]) {
|
||||
delete ref.add[propertyName];
|
||||
delete ref.remove[propertyName];
|
||||
|
||||
// Remove information about the selector if there are no changes to its declarations.
|
||||
if (Object.keys(ref.add).length === 0 && Object.keys(ref.remove).length === 0) {
|
||||
delete diff[change.href][change.selector];
|
||||
}
|
||||
|
||||
// Remove information about the stylesheet if there are no changes to its rules.
|
||||
if (Object.keys(diff[change.href]).length === 0) {
|
||||
delete diff[change.href];
|
||||
}
|
||||
}
|
||||
|
||||
ref.tag = change.tag;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
const reducers = {
|
||||
|
||||
[TRACK_CHANGE](state, { data }) {
|
||||
const defaults = {
|
||||
href: "",
|
||||
selector: "",
|
||||
tag: null,
|
||||
add: null,
|
||||
remove: null
|
||||
};
|
||||
|
||||
data = { ...defaults, ...data };
|
||||
|
||||
// Update the state in-place with data about a style change (no deep clone of state).
|
||||
// TODO: redefine state as a shallow object structure after figuring how to track
|
||||
// both CSS Declarations and CSS Rules and At-Rules (@media, @keyframes, etc).
|
||||
// @See https://bugzilla.mozilla.org/show_bug.cgi?id=1491263
|
||||
return Object.assign({}, { diff: updateDiff(state.diff, data) });
|
||||
},
|
||||
|
||||
[RESET_CHANGES](state) {
|
||||
return INITIAL_STATE;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = function(state = INITIAL_STATE, action) {
|
||||
const reducer = reducers[action.type];
|
||||
if (!reducer) {
|
||||
return state;
|
||||
}
|
||||
return reducer(state, action);
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'changes.js',
|
||||
)
|
|
@ -12,6 +12,7 @@
|
|||
<link rel="stylesheet" href="chrome://devtools/skin/inspector.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/rules.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/computed.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/changes.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/fonts.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/boxmodel.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/layout.css"/>
|
||||
|
|
|
@ -37,6 +37,8 @@ loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clip
|
|||
loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
|
||||
loader.lazyRequireGetter(this, "getScreenshotFront", "devtools/shared/fronts/screenshot", true);
|
||||
loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
|
||||
loader.lazyRequireGetter(this, "ChangesManager",
|
||||
"devtools/client/inspector/changes/ChangesManager");
|
||||
|
||||
loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
|
||||
|
||||
|
@ -67,6 +69,7 @@ const THREE_PANE_ENABLED_PREF = "devtools.inspector.three-pane-enabled";
|
|||
const THREE_PANE_ENABLED_SCALAR = "devtools.inspector.three_pane_enabled";
|
||||
const THREE_PANE_CHROME_ENABLED_PREF = "devtools.inspector.chrome.three-pane-enabled";
|
||||
const TELEMETRY_EYEDROPPER_OPENED = "devtools.toolbar.eyedropper.opened";
|
||||
const TRACK_CHANGES_ENABLED = "devtools.inspector.changes.enabled";
|
||||
|
||||
/**
|
||||
* Represents an open instance of the Inspector for a tab.
|
||||
|
@ -122,6 +125,9 @@ function Inspector(toolbox) {
|
|||
|
||||
this.reflowTracker = new ReflowTracker(this._target);
|
||||
this.styleChangeTracker = new InspectorStyleChangeTracker(this);
|
||||
if (Services.prefs.getBoolPref(TRACK_CHANGES_ENABLED)) {
|
||||
this.changesManager = new ChangesManager(this);
|
||||
}
|
||||
|
||||
// Store the URL of the target page prior to navigation in order to ensure
|
||||
// telemetry counts in the Grid Inspector are not double counted on reload.
|
||||
|
@ -979,6 +985,32 @@ Inspector.prototype = {
|
|||
},
|
||||
defaultTab == fontId);
|
||||
|
||||
if (Services.prefs.getBoolPref(TRACK_CHANGES_ENABLED)) {
|
||||
// Inject a lazy loaded react tab by exposing a fake React object
|
||||
// with a lazy defined Tab thanks to `panel` being a function
|
||||
const changesId = "changesview";
|
||||
const changesTitle = INSPECTOR_L10N.getStr("inspector.sidebar.changesViewTitle");
|
||||
this.sidebar.queueTab(
|
||||
changesId,
|
||||
changesTitle,
|
||||
{
|
||||
props: {
|
||||
id: changesId,
|
||||
title: changesTitle
|
||||
},
|
||||
panel: () => {
|
||||
if (!this.changesView) {
|
||||
const ChangesView =
|
||||
this.browserRequire("devtools/client/inspector/changes/ChangesView");
|
||||
this.changesView = new ChangesView(this, this.panelWin);
|
||||
}
|
||||
|
||||
return this.changesView.provider;
|
||||
}
|
||||
},
|
||||
defaultTab == changesId);
|
||||
}
|
||||
|
||||
this.sidebar.addAllQueuedTabs();
|
||||
|
||||
// Persist splitter state in preferences.
|
||||
|
@ -1417,6 +1449,10 @@ Inspector.prototype = {
|
|||
this.layoutview.destroy();
|
||||
}
|
||||
|
||||
if (this.changesView) {
|
||||
this.changesView.destroy();
|
||||
}
|
||||
|
||||
if (this.fontinspector) {
|
||||
this.fontinspector.destroy();
|
||||
}
|
||||
|
@ -1452,6 +1488,10 @@ Inspector.prototype = {
|
|||
this.reflowTracker.destroy();
|
||||
this.styleChangeTracker.destroy();
|
||||
|
||||
if (this.changesManager) {
|
||||
this.changesManager.destroy();
|
||||
}
|
||||
|
||||
this._is3PaneModeChromeEnabled = null;
|
||||
this._is3PaneModeEnabled = null;
|
||||
this._notificationBox = null;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
DIRS += [
|
||||
'animation',
|
||||
'boxmodel',
|
||||
'changes',
|
||||
'components',
|
||||
'computed',
|
||||
'extensions',
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
exports.animations = require("devtools/client/inspector/animation/reducers/animations");
|
||||
exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
|
||||
exports.changes = require("devtools/client/inspector/changes/reducers/changes");
|
||||
exports.extensionsSidebar = require("devtools/client/inspector/extensions/reducers/sidebar");
|
||||
exports.flexbox = require("devtools/client/inspector/flexbox/reducers/flexbox");
|
||||
exports.fontOptions = require("devtools/client/inspector/fonts/reducers/font-options");
|
||||
|
|
|
@ -1,351 +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/. */
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode} = require("devtools/client/inspector/animation-old/utils");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const STRINGS_URI = "devtools/client/locales/inspector.properties";
|
||||
const L10N = new LocalizationHelper(STRINGS_URI);
|
||||
|
||||
/**
|
||||
* UI component responsible for displaying a preview of a dom node.
|
||||
* @param {InspectorPanel} inspector Requires a reference to the inspector-panel
|
||||
* to highlight and select the node, as well as refresh it when there are
|
||||
* mutations.
|
||||
* @param {Object} options Supported properties are:
|
||||
* - compact {Boolean} Defaults to false.
|
||||
* By default, nodes are previewed like <tag id="id" class="class">
|
||||
* If true, nodes will be previewed like tag#id.class instead.
|
||||
*/
|
||||
function DomNodePreview(inspector, options = {}) {
|
||||
this.inspector = inspector;
|
||||
this.options = options;
|
||||
|
||||
this.onPreviewMouseOver = this.onPreviewMouseOver.bind(this);
|
||||
this.onPreviewMouseOut = this.onPreviewMouseOut.bind(this);
|
||||
this.onSelectElClick = this.onSelectElClick.bind(this);
|
||||
this.onMarkupMutations = this.onMarkupMutations.bind(this);
|
||||
this.onHighlightElClick = this.onHighlightElClick.bind(this);
|
||||
this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.DomNodePreview = DomNodePreview;
|
||||
|
||||
DomNodePreview.prototype = {
|
||||
init: function(containerEl) {
|
||||
const document = containerEl.ownerDocument;
|
||||
|
||||
// Init the markup for displaying the target node.
|
||||
this.el = createNode({
|
||||
parent: containerEl,
|
||||
attributes: {
|
||||
"class": "animation-target"
|
||||
}
|
||||
});
|
||||
|
||||
// Icon to select the node in the inspector.
|
||||
this.highlightNodeEl = createNode({
|
||||
parent: this.el,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "node-highlighter",
|
||||
"title": L10N.getStr("inspector.nodePreview.highlightNodeLabel")
|
||||
}
|
||||
});
|
||||
|
||||
// Wrapper used for mouseover/out event handling.
|
||||
this.previewEl = createNode({
|
||||
parent: this.el,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"title": L10N.getStr("inspector.nodePreview.selectNodeLabel")
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.options.compact) {
|
||||
this.previewEl.appendChild(document.createTextNode("<"));
|
||||
}
|
||||
|
||||
// Only used for ::before and ::after pseudo-elements.
|
||||
this.pseudoEl = createNode({
|
||||
parent: this.previewEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "pseudo-element theme-fg-color5"
|
||||
}
|
||||
});
|
||||
|
||||
// Tag name.
|
||||
this.tagNameEl = createNode({
|
||||
parent: this.previewEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "tag-name theme-fg-color3"
|
||||
}
|
||||
});
|
||||
|
||||
// Id attribute container.
|
||||
this.idEl = createNode({
|
||||
parent: this.previewEl,
|
||||
nodeType: "span"
|
||||
});
|
||||
|
||||
if (!this.options.compact) {
|
||||
createNode({
|
||||
parent: this.idEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "attribute-name theme-fg-color2"
|
||||
},
|
||||
textContent: "id"
|
||||
});
|
||||
this.idEl.appendChild(document.createTextNode("=\""));
|
||||
} else {
|
||||
createNode({
|
||||
parent: this.idEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "theme-fg-color4"
|
||||
},
|
||||
textContent: "#"
|
||||
});
|
||||
}
|
||||
|
||||
createNode({
|
||||
parent: this.idEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "attribute-value theme-fg-color4"
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.options.compact) {
|
||||
this.idEl.appendChild(document.createTextNode("\""));
|
||||
}
|
||||
|
||||
// Class attribute container.
|
||||
this.classEl = createNode({
|
||||
parent: this.previewEl,
|
||||
nodeType: "span"
|
||||
});
|
||||
|
||||
if (!this.options.compact) {
|
||||
createNode({
|
||||
parent: this.classEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "attribute-name theme-fg-color2"
|
||||
},
|
||||
textContent: "class"
|
||||
});
|
||||
this.classEl.appendChild(document.createTextNode("=\""));
|
||||
} else {
|
||||
createNode({
|
||||
parent: this.classEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "theme-fg-color4"
|
||||
},
|
||||
textContent: "."
|
||||
});
|
||||
}
|
||||
|
||||
createNode({
|
||||
parent: this.classEl,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "attribute-value theme-fg-color4"
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.options.compact) {
|
||||
this.classEl.appendChild(document.createTextNode("\""));
|
||||
this.previewEl.appendChild(document.createTextNode(">"));
|
||||
}
|
||||
|
||||
this.startListeners();
|
||||
},
|
||||
|
||||
startListeners: function() {
|
||||
// Init events for highlighting and selecting the node.
|
||||
this.previewEl.addEventListener("mouseover", this.onPreviewMouseOver);
|
||||
this.previewEl.addEventListener("mouseout", this.onPreviewMouseOut);
|
||||
this.previewEl.addEventListener("click", this.onSelectElClick);
|
||||
this.highlightNodeEl.addEventListener("click", this.onHighlightElClick);
|
||||
|
||||
// Start to listen for markupmutation events.
|
||||
this.inspector.on("markupmutation", this.onMarkupMutations);
|
||||
|
||||
// Listen to the target node highlighter.
|
||||
HighlighterLock.on("highlighted", this.onHighlighterLocked);
|
||||
},
|
||||
|
||||
stopListeners: function() {
|
||||
HighlighterLock.off("highlighted", this.onHighlighterLocked);
|
||||
this.inspector.off("markupmutation", this.onMarkupMutations);
|
||||
this.previewEl.removeEventListener("mouseover", this.onPreviewMouseOver);
|
||||
this.previewEl.removeEventListener("mouseout", this.onPreviewMouseOut);
|
||||
this.previewEl.removeEventListener("click", this.onSelectElClick);
|
||||
this.highlightNodeEl.removeEventListener("click", this.onHighlightElClick);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
HighlighterLock.unhighlight().catch(console.error);
|
||||
|
||||
this.stopListeners();
|
||||
|
||||
this.el.remove();
|
||||
this.el = this.tagNameEl = this.idEl = this.classEl = this.pseudoEl = null;
|
||||
this.highlightNodeEl = this.previewEl = null;
|
||||
this.nodeFront = this.inspector = null;
|
||||
},
|
||||
|
||||
get highlighterUtils() {
|
||||
if (this.inspector && this.inspector.toolbox) {
|
||||
return this.inspector.toolbox.highlighterUtils;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
onPreviewMouseOver: function() {
|
||||
if (!this.nodeFront || !this.highlighterUtils) {
|
||||
return;
|
||||
}
|
||||
this.highlighterUtils.highlightNodeFront(this.nodeFront)
|
||||
.catch(console.error);
|
||||
},
|
||||
|
||||
onPreviewMouseOut: function() {
|
||||
if (!this.nodeFront || !this.highlighterUtils) {
|
||||
return;
|
||||
}
|
||||
this.highlighterUtils.unhighlight()
|
||||
.catch(console.error);
|
||||
},
|
||||
|
||||
onSelectElClick: function() {
|
||||
if (!this.nodeFront) {
|
||||
return;
|
||||
}
|
||||
this.inspector.selection.setNodeFront(this.nodeFront, { reason: "dom-node-preview" });
|
||||
},
|
||||
|
||||
onHighlightElClick: function(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
const classList = this.highlightNodeEl.classList;
|
||||
const isHighlighted = classList.contains("selected");
|
||||
|
||||
if (isHighlighted) {
|
||||
classList.remove("selected");
|
||||
HighlighterLock.unhighlight().then(() => {
|
||||
this.emit("target-highlighter-unlocked");
|
||||
}, console.error);
|
||||
} else {
|
||||
classList.add("selected");
|
||||
HighlighterLock.highlight(this).then(() => {
|
||||
this.emit("target-highlighter-locked");
|
||||
}, console.error);
|
||||
}
|
||||
},
|
||||
|
||||
onHighlighterLocked: function(domNodePreview) {
|
||||
if (domNodePreview !== this) {
|
||||
this.highlightNodeEl.classList.remove("selected");
|
||||
}
|
||||
},
|
||||
|
||||
onMarkupMutations: function(mutations) {
|
||||
if (!this.nodeFront) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {target} of mutations) {
|
||||
if (target === this.nodeFront) {
|
||||
// Re-render with the same nodeFront to update the output.
|
||||
this.render(this.nodeFront);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render: function(nodeFront) {
|
||||
this.nodeFront = nodeFront;
|
||||
const {displayName, attributes} = nodeFront;
|
||||
|
||||
if (nodeFront.isPseudoElement) {
|
||||
this.pseudoEl.textContent = nodeFront.isBeforePseudoElement
|
||||
? "::before"
|
||||
: "::after";
|
||||
this.pseudoEl.style.display = "inline";
|
||||
this.tagNameEl.style.display = "none";
|
||||
} else {
|
||||
this.tagNameEl.textContent = displayName;
|
||||
this.pseudoEl.style.display = "none";
|
||||
this.tagNameEl.style.display = "inline";
|
||||
}
|
||||
|
||||
const idIndex = attributes.findIndex(({name}) => name === "id");
|
||||
if (idIndex > -1 && attributes[idIndex].value) {
|
||||
this.idEl.querySelector(".attribute-value").textContent =
|
||||
attributes[idIndex].value;
|
||||
this.idEl.style.display = "inline";
|
||||
} else {
|
||||
this.idEl.style.display = "none";
|
||||
}
|
||||
|
||||
const classIndex = attributes.findIndex(({name}) => name === "class");
|
||||
if (classIndex > -1 && attributes[classIndex].value) {
|
||||
let value = attributes[classIndex].value;
|
||||
if (this.options.compact) {
|
||||
value = value.split(" ").join(".");
|
||||
}
|
||||
|
||||
this.classEl.querySelector(".attribute-value").textContent = value;
|
||||
this.classEl.style.display = "inline";
|
||||
} else {
|
||||
this.classEl.style.display = "none";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* HighlighterLock is a helper used to lock the highlighter on DOM nodes in the
|
||||
* page.
|
||||
* It instantiates a new highlighter that is then shared amongst all instances
|
||||
* of DomNodePreview. This is useful because that means showing the highlighter
|
||||
* on one node will unhighlight the previously highlighted one, but will not
|
||||
* interfere with the default inspector highlighter.
|
||||
*/
|
||||
var HighlighterLock = {
|
||||
highlighter: null,
|
||||
isShown: false,
|
||||
|
||||
async highlight(animationTargetNode) {
|
||||
if (!this.highlighter) {
|
||||
const util = animationTargetNode.inspector.toolbox.highlighterUtils;
|
||||
this.highlighter = await util.getHighlighterByType("BoxModelHighlighter");
|
||||
}
|
||||
|
||||
await this.highlighter.show(animationTargetNode.nodeFront);
|
||||
this.isShown = true;
|
||||
this.emit("highlighted", animationTargetNode);
|
||||
},
|
||||
|
||||
async unhighlight() {
|
||||
if (!this.highlighter || !this.isShown) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.highlighter.hide();
|
||||
this.isShown = false;
|
||||
this.emit("unhighlighted");
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.decorate(HighlighterLock);
|
|
@ -5,7 +5,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'dom-node-preview.js',
|
||||
'highlighters-overlay.js',
|
||||
'node-types.js',
|
||||
'reflow-tracker.js',
|
||||
|
|
|
@ -243,6 +243,7 @@ devtools.jar:
|
|||
skin/images/cubic-bezier-swatch.png (themes/images/cubic-bezier-swatch.png)
|
||||
skin/images/cubic-bezier-swatch@2x.png (themes/images/cubic-bezier-swatch@2x.png)
|
||||
skin/fonts.css (themes/fonts.css)
|
||||
skin/changes.css (themes/changes.css)
|
||||
skin/computed.css (themes/computed.css)
|
||||
skin/layout.css (themes/layout.css)
|
||||
skin/images/arrow-e.png (themes/images/arrow-e.png)
|
||||
|
|
|
@ -400,6 +400,11 @@ markupView.scrollInto.key=s
|
|||
# that corresponds to the tool displaying the list of fonts used in the page.
|
||||
inspector.sidebar.fontInspectorTitle=Fonts
|
||||
|
||||
# LOCALIZATION NOTE (inspector.sidebar.changesViewTitle):
|
||||
# Title of the Changes sidebar tab shown in the Inspector panel. The Changes panel shows
|
||||
# style changes made using DevTools.
|
||||
inspector.sidebar.changesViewTitle=Changes
|
||||
|
||||
# LOCALIZATION NOTE (inspector.sidebar.ruleViewTitle):
|
||||
# This is the title shown in a tab in the side panel of the Inspector panel
|
||||
# that corresponds to the tool displaying the list of CSS rules used
|
||||
|
|
|
@ -74,7 +74,6 @@
|
|||
<!ENTITY runtimePanel_usb "USB Devices">
|
||||
<!ENTITY runtimePanel_wifi "Wi-Fi Devices">
|
||||
<!ENTITY runtimePanel_other "Other">
|
||||
<!ENTITY runtimePanel_noadbextension "Install ADB Extension">
|
||||
<!ENTITY runtimePanel_nousbdevice "Can’t see your device?">
|
||||
<!ENTITY runtimePanel_refreshDevices_label "Refresh Devices">
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ addons_stable=stable
|
|||
addons_unstable=unstable
|
||||
addons_install_button=install
|
||||
addons_uninstall_button=uninstall
|
||||
addons_adb_label2=ADB Extension Add-on
|
||||
addons_adb_warning=USB devices won’t be detected without this add-on
|
||||
addons_status_unknown=?
|
||||
addons_status_installed=Installed
|
||||
|
@ -68,6 +67,11 @@ runtimedetails_checkno=no
|
|||
runtimedetails_checkyes=yes
|
||||
runtimedetails_notUSBDevice=Not a USB device
|
||||
|
||||
# LOCALIZATION NOTE (runtimePanel_noadbextension): Displayed in the WebIDE right sidebar
|
||||
# when the ADB Extension is not installed, %S will be replaced with the name of extension
|
||||
# ("ADB Extension").
|
||||
runtimePanel_noadbextension=Install %S
|
||||
|
||||
# Validation status
|
||||
status_tooltip=Validation status: %1$S
|
||||
status_valid=VALID
|
||||
|
|
|
@ -63,6 +63,8 @@ pref("devtools.inspector.shapesHighlighter.enabled", true);
|
|||
pref("devtools.inspector.fonteditor.enabled", true);
|
||||
// Enable the font highlight-on-hover feature
|
||||
pref("devtools.inspector.fonthighlighter.enabled", true);
|
||||
// Enable tracking of style changes and the Changes panel in the Inspector
|
||||
pref("devtools.inspector.changes.enabled", false);
|
||||
|
||||
// Flexbox preferences
|
||||
// Enable the Flexbox highlighter and inspector panel in Nightly
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/* 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/. */
|
||||
|
||||
/* CSS Variables specific to the Changes panel that aren't defined by the themes */
|
||||
:root {
|
||||
--diff-add-background-color: #f1feec;
|
||||
--diff-add-text-color: #54983f;
|
||||
--diff-remove-background-color: #fbf2f5;
|
||||
--diff-remove-text-color: #bf7173;
|
||||
}
|
||||
|
||||
#sidebar-panel-changes {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
details {
|
||||
font-family: var(--monospace-font-family);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
summary {
|
||||
outline: none;
|
||||
padding: 5px 0;
|
||||
-moz-user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
details.source[open] {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
details.source > summary {
|
||||
background: var(--theme-sidebar-background);
|
||||
border-top: 1px solid var(--theme-splitter-color);
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
padding-left: 5px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
details.selector > summary {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
details.selector summary::after{
|
||||
content: "{…}";
|
||||
display: inline-block;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
details.selector[open]{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
details.selector[open] summary::after{
|
||||
content: "{";
|
||||
}
|
||||
|
||||
details.selector[open]::after{
|
||||
content: "}";
|
||||
display: block;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.line {
|
||||
padding: 3px 5px 3px 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.diff-add::before,
|
||||
.diff-remove::before{
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.diff-add {
|
||||
background-color: var(--diff-add-background-color);
|
||||
}
|
||||
|
||||
.diff-add::before {
|
||||
content: "+";
|
||||
color: var(--diff-add-text-color);
|
||||
}
|
||||
|
||||
.diff-remove {
|
||||
background-color: var(--diff-remove-background-color);
|
||||
}
|
||||
|
||||
.diff-remove::before{
|
||||
content: "-";
|
||||
color: var(--diff-remove-text-color);
|
||||
}
|
|
@ -69,7 +69,7 @@ function BuildItem(addon, type) {
|
|||
switch (type) {
|
||||
case "adb":
|
||||
li.setAttribute("addon", type);
|
||||
name.textContent = Strings.GetStringFromName("addons_adb_label2");
|
||||
name.textContent = "ADB Extension";
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<button class="runtime-panel-item-refreshdevices refresh-icon" id="refresh-devices" title="&runtimePanel_refreshDevices_label;"></button>
|
||||
</label>
|
||||
<button class="panel-item" id="runtime-panel-nousbdevice">&runtimePanel_nousbdevice;</button>
|
||||
<button class="panel-item" id="runtime-panel-noadbextension">&runtimePanel_noadbextension;</button>
|
||||
<button class="panel-item" id="runtime-panel-noadbextension"></button>
|
||||
<div id="runtime-panel-usb"></div>
|
||||
<label class="panel-header" id="runtime-header-wifi">&runtimePanel_wifi;</label>
|
||||
<div id="runtime-panel-wifi"></div>
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const {AppManager} = require("devtools/client/webide/modules/app-manager");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {RuntimeScanners, WiFiScanner} = require("devtools/client/webide/modules/runtimes");
|
||||
const {Devices} = require("resource://devtools/shared/apps/Devices.jsm");
|
||||
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||
|
||||
var RuntimeList;
|
||||
|
||||
|
@ -124,6 +126,8 @@ RuntimeList.prototype = {
|
|||
const otherListNode = doc.querySelector("#runtime-panel-other");
|
||||
const noADBExtensionNode = doc.querySelector("#runtime-panel-noadbextension");
|
||||
const noUSBNode = doc.querySelector("#runtime-panel-nousbdevice");
|
||||
noADBExtensionNode.textContent =
|
||||
Strings.formatStringFromName("runtimePanel_noadbextension", ["ADB Extension"], 1);
|
||||
|
||||
if (Devices.adbExtensionInstalled) {
|
||||
noADBExtensionNode.setAttribute("hidden", "true");
|
||||
|
|
|
@ -9,9 +9,6 @@ loader.lazyRequireGetter(this, "Services");
|
|||
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
|
||||
loader.lazyRequireGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm", true);
|
||||
loader.lazyGetter(this, "screenManager", () => {
|
||||
return Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
|
||||
});
|
||||
loader.lazyGetter(this, "hostname", () => {
|
||||
try {
|
||||
// On some platforms (Linux according to try), this service does not exist and fails.
|
||||
|
@ -185,57 +182,4 @@ function getProfileLocation() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for fetching screen dimensions and returning
|
||||
* an enum for Telemetry.
|
||||
*/
|
||||
function getScreenDimensions() {
|
||||
const width = {};
|
||||
const height = {};
|
||||
|
||||
screenManager.primaryScreen.GetRect({}, {}, width, height);
|
||||
const dims = width.value + "x" + height.value;
|
||||
|
||||
if (width.value < 800 || height.value < 600) {
|
||||
return 0;
|
||||
}
|
||||
if (dims === "800x600") {
|
||||
return 1;
|
||||
}
|
||||
if (dims === "1024x768") {
|
||||
return 2;
|
||||
}
|
||||
if (dims === "1280x800") {
|
||||
return 3;
|
||||
}
|
||||
if (dims === "1280x1024") {
|
||||
return 4;
|
||||
}
|
||||
if (dims === "1366x768") {
|
||||
return 5;
|
||||
}
|
||||
if (dims === "1440x900") {
|
||||
return 6;
|
||||
}
|
||||
if (dims === "1920x1080") {
|
||||
return 7;
|
||||
}
|
||||
if (dims === "2560×1440") {
|
||||
return 8;
|
||||
}
|
||||
if (dims === "2560×1600") {
|
||||
return 9;
|
||||
}
|
||||
if (dims === "2880x1800") {
|
||||
return 10;
|
||||
}
|
||||
if (width.value > 2880 || height.value > 1800) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
// Other dimension such as a VM.
|
||||
return 11;
|
||||
}
|
||||
|
||||
exports.getSystemInfo = getSystemInfo;
|
||||
exports.getScreenDimensions = getScreenDimensions;
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/EventStates.h"
|
||||
#include "mozilla/FullscreenRequest.h"
|
||||
#include "mozilla/FullscreenChange.h"
|
||||
#include "mozilla/InternalMutationEvent.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/RestyleManager.h"
|
||||
|
|
|
@ -21,36 +21,18 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
|
||||
class FullscreenChange : public LinkedListElement<FullscreenChange>
|
||||
{
|
||||
typedef dom::Promise Promise;
|
||||
public:
|
||||
FullscreenChange(const FullscreenChange&) = delete;
|
||||
|
||||
static UniquePtr<FullscreenRequest> Create(Element* aElement,
|
||||
dom::CallerType aCallerType,
|
||||
ErrorResult& aRv)
|
||||
enum ChangeType
|
||||
{
|
||||
RefPtr<Promise> promise = Promise::Create(aElement->GetOwnerGlobal(), aRv);
|
||||
return WrapUnique(new FullscreenRequest(aElement, promise.forget(),
|
||||
aCallerType, true));
|
||||
}
|
||||
eEnter,
|
||||
eExit,
|
||||
};
|
||||
|
||||
static UniquePtr<FullscreenRequest> CreateForRemote(Element* aElement)
|
||||
{
|
||||
return WrapUnique(new FullscreenRequest(aElement, nullptr,
|
||||
dom::CallerType::NonSystem,
|
||||
false));
|
||||
}
|
||||
|
||||
FullscreenRequest(const FullscreenRequest&) = delete;
|
||||
|
||||
~FullscreenRequest()
|
||||
{
|
||||
MOZ_COUNT_DTOR(FullscreenRequest);
|
||||
MOZ_ASSERT_IF(mPromise,
|
||||
mPromise->State() != Promise::PromiseState::Pending);
|
||||
}
|
||||
|
||||
dom::Element* Element() const { return mElement; }
|
||||
ChangeType Type() const { return mType; }
|
||||
nsIDocument* Document() const { return mDocument; }
|
||||
dom::Promise* GetPromise() const { return mPromise; }
|
||||
|
||||
|
@ -70,28 +52,78 @@ struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
|
|||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
typedef dom::Promise Promise;
|
||||
|
||||
FullscreenChange(ChangeType aType, nsIDocument* aDocument,
|
||||
already_AddRefed<Promise> aPromise)
|
||||
: mType(aType)
|
||||
, mDocument(aDocument)
|
||||
, mPromise(aPromise)
|
||||
{
|
||||
MOZ_ASSERT(aDocument);
|
||||
}
|
||||
|
||||
~FullscreenChange()
|
||||
{
|
||||
MOZ_ASSERT_IF(mPromise,
|
||||
mPromise->State() != Promise::PromiseState::Pending);
|
||||
}
|
||||
|
||||
private:
|
||||
ChangeType mType;
|
||||
nsCOMPtr<nsIDocument> mDocument;
|
||||
RefPtr<Promise> mPromise;
|
||||
};
|
||||
|
||||
class FullscreenRequest : public FullscreenChange
|
||||
{
|
||||
public:
|
||||
static const ChangeType kType = eEnter;
|
||||
|
||||
static UniquePtr<FullscreenRequest> Create(Element* aElement,
|
||||
dom::CallerType aCallerType,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
RefPtr<Promise> promise = Promise::Create(aElement->GetOwnerGlobal(), aRv);
|
||||
return WrapUnique(new FullscreenRequest(aElement, promise.forget(),
|
||||
aCallerType, true));
|
||||
}
|
||||
|
||||
static UniquePtr<FullscreenRequest> CreateForRemote(Element* aElement)
|
||||
{
|
||||
return WrapUnique(new FullscreenRequest(aElement, nullptr,
|
||||
dom::CallerType::NonSystem,
|
||||
false));
|
||||
}
|
||||
|
||||
~FullscreenRequest()
|
||||
{
|
||||
MOZ_COUNT_DTOR(FullscreenRequest);
|
||||
}
|
||||
|
||||
dom::Element* Element() const { return mElement; }
|
||||
|
||||
// Reject the fullscreen request with the given reason.
|
||||
// It will dispatch the fullscreenerror event.
|
||||
void Reject(const char* aReason) const
|
||||
{
|
||||
if (nsPresContext* presContext = mDocument->GetPresContext()) {
|
||||
if (nsPresContext* presContext = Document()->GetPresContext()) {
|
||||
auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
|
||||
FullscreenEventType::Error, mDocument, mElement);
|
||||
FullscreenEventType::Error, Document(), mElement);
|
||||
presContext->RefreshDriver()->
|
||||
ScheduleFullscreenEvent(std::move(pendingEvent));
|
||||
}
|
||||
MayRejectPromise();
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("DOM"),
|
||||
mDocument,
|
||||
Document(),
|
||||
nsContentUtils::eDOM_PROPERTIES,
|
||||
aReason);
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<dom::Element> mElement;
|
||||
RefPtr<nsIDocument> mDocument;
|
||||
RefPtr<dom::Promise> mPromise;
|
||||
|
||||
public:
|
||||
// This value should be true if the fullscreen request is
|
||||
|
@ -110,9 +142,8 @@ private:
|
|||
already_AddRefed<dom::Promise> aPromise,
|
||||
dom::CallerType aCallerType,
|
||||
bool aShouldNotifyNewOrigin)
|
||||
: mElement(aElement)
|
||||
, mDocument(aElement->OwnerDoc())
|
||||
, mPromise(aPromise)
|
||||
: FullscreenChange(kType, aElement->OwnerDoc(), std::move(aPromise))
|
||||
, mElement(aElement)
|
||||
, mCallerType(aCallerType)
|
||||
, mShouldNotifyNewOrigin(aShouldNotifyNewOrigin)
|
||||
{
|
||||
|
@ -120,6 +151,35 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
class FullscreenExit : public FullscreenChange
|
||||
{
|
||||
public:
|
||||
static const ChangeType kType = eExit;
|
||||
|
||||
static UniquePtr<FullscreenExit> Create(nsIDocument* aDoc, ErrorResult& aRv)
|
||||
{
|
||||
RefPtr<Promise> promise = Promise::Create(aDoc->GetOwnerGlobal(), aRv);
|
||||
return WrapUnique(new FullscreenExit(aDoc, promise.forget()));
|
||||
}
|
||||
|
||||
static UniquePtr<FullscreenExit> CreateForRemote(nsIDocument* aDoc)
|
||||
{
|
||||
return WrapUnique(new FullscreenExit(aDoc, nullptr));
|
||||
}
|
||||
|
||||
~FullscreenExit()
|
||||
{
|
||||
MOZ_COUNT_DTOR(FullscreenExit);
|
||||
}
|
||||
|
||||
private:
|
||||
FullscreenExit(nsIDocument* aDoc, already_AddRefed<Promise> aPromise)
|
||||
: FullscreenChange(kType, aDoc, std::move(aPromise))
|
||||
{
|
||||
MOZ_COUNT_CTOR(FullscreenExit);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_FullscreenRequest_h
|
|
@ -131,7 +131,7 @@ EXPORTS.mozilla += [
|
|||
'CORSMode.h',
|
||||
'FeedWriterEnabled.h',
|
||||
'FlushType.h',
|
||||
'FullscreenRequest.h',
|
||||
'FullscreenChange.h',
|
||||
'RangeBoundary.h',
|
||||
'SelectionChangeEventDispatcher.h',
|
||||
'TextInputProcessor.h',
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
#include "mozilla/BasicEvents.h"
|
||||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/FullscreenRequest.h"
|
||||
#include "mozilla/FullscreenChange.h"
|
||||
|
||||
#include "mozilla/dom/Attr.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
|
@ -10618,8 +10618,109 @@ FullscreenRoots::IsEmpty()
|
|||
return !sInstance;
|
||||
}
|
||||
|
||||
// Any fullscreen change waiting for the widget to finish transition
|
||||
// is queued here. This is declared static instead of a member of
|
||||
// nsDocument because in the majority of time, there would be at most
|
||||
// one document requesting or exiting fullscreen. We shouldn't waste
|
||||
// the space to hold for it in every document.
|
||||
class PendingFullscreenChangeList
|
||||
{
|
||||
public:
|
||||
PendingFullscreenChangeList() = delete;
|
||||
|
||||
template<typename T>
|
||||
static void Add(UniquePtr<T> aChange)
|
||||
{
|
||||
sList.insertBack(aChange.release());
|
||||
}
|
||||
|
||||
static const FullscreenChange* GetLast()
|
||||
{
|
||||
return sList.getLast();
|
||||
}
|
||||
|
||||
enum IteratorOption
|
||||
{
|
||||
// When we are committing fullscreen changes or preparing for
|
||||
// that, we generally want to iterate all requests in the same
|
||||
// window with eDocumentsWithSameRoot option.
|
||||
eDocumentsWithSameRoot,
|
||||
// If we are removing a document from the tree, we would only
|
||||
// want to remove the requests from the given document and its
|
||||
// descendants. For that case, use eInclusiveDescendants.
|
||||
eInclusiveDescendants
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
explicit Iterator(nsIDocument* aDoc, IteratorOption aOption)
|
||||
: mCurrent(PendingFullscreenChangeList::sList.getFirst())
|
||||
, mRootShellForIteration(aDoc->GetDocShell())
|
||||
{
|
||||
if (mCurrent) {
|
||||
if (mRootShellForIteration && aOption == eDocumentsWithSameRoot) {
|
||||
mRootShellForIteration->
|
||||
GetRootTreeItem(getter_AddRefs(mRootShellForIteration));
|
||||
}
|
||||
SkipToNextMatch();
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<T> TakeAndNext()
|
||||
{
|
||||
auto thisChange = TakeAndNextInternal();
|
||||
SkipToNextMatch();
|
||||
return thisChange;
|
||||
}
|
||||
bool AtEnd() const { return mCurrent == nullptr; }
|
||||
|
||||
private:
|
||||
UniquePtr<T> TakeAndNextInternal()
|
||||
{
|
||||
FullscreenChange* thisChange = mCurrent;
|
||||
MOZ_ASSERT(thisChange->Type() == T::kType);
|
||||
mCurrent = mCurrent->removeAndGetNext();
|
||||
return WrapUnique(static_cast<T*>(thisChange));
|
||||
}
|
||||
void SkipToNextMatch()
|
||||
{
|
||||
while (mCurrent) {
|
||||
if (mCurrent->Type() == T::kType) {
|
||||
nsCOMPtr<nsIDocShellTreeItem>
|
||||
docShell = mCurrent->Document()->GetDocShell();
|
||||
if (!docShell) {
|
||||
// Always automatically drop fullscreen changes which are
|
||||
// from a document detached from the doc shell.
|
||||
UniquePtr<T> change = TakeAndNextInternal();
|
||||
change->MayRejectPromise();
|
||||
continue;
|
||||
}
|
||||
while (docShell && docShell != mRootShellForIteration) {
|
||||
docShell->GetParent(getter_AddRefs(docShell));
|
||||
}
|
||||
if (docShell) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// The current one either don't have matched type, or isn't
|
||||
// inside the given subtree, so skip this item.
|
||||
mCurrent = mCurrent->getNext();
|
||||
}
|
||||
}
|
||||
|
||||
FullscreenChange* mCurrent;
|
||||
nsCOMPtr<nsIDocShellTreeItem> mRootShellForIteration;
|
||||
};
|
||||
|
||||
private:
|
||||
static LinkedList<FullscreenChange> sList;
|
||||
};
|
||||
|
||||
/* static */ LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
|
||||
|
||||
} // end namespace mozilla.
|
||||
using mozilla::FullscreenRoots;
|
||||
|
||||
nsIDocument*
|
||||
nsIDocument::GetFullscreenRoot()
|
||||
|
@ -10634,10 +10735,13 @@ nsIDocument::SetFullscreenRoot(nsIDocument* aRoot)
|
|||
mFullscreenRoot = do_GetWeakReference(aRoot);
|
||||
}
|
||||
|
||||
void
|
||||
nsIDocument::ExitFullscreen()
|
||||
already_AddRefed<Promise>
|
||||
nsIDocument::ExitFullscreen(ErrorResult& aRv)
|
||||
{
|
||||
RestorePreviousFullscreenState();
|
||||
UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
|
||||
RefPtr<Promise> promise = exit->GetPromise();
|
||||
RestorePreviousFullscreenState(std::move(exit));
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -10809,6 +10913,14 @@ nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc)
|
|||
// Unlock the pointer
|
||||
UnlockPointer();
|
||||
|
||||
// Resolve all promises which waiting for exit fullscreen.
|
||||
PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
|
||||
aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
|
||||
while (!iter.AtEnd()) {
|
||||
UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
|
||||
exit->MayResolvePromise();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot();
|
||||
if (!root || !root->FullscreenStackTop()) {
|
||||
// If a document was detached before exiting from fullscreen, it is
|
||||
|
@ -10852,12 +10964,13 @@ DispatchFullscreenNewOriginEvent(nsIDocument* aDoc)
|
|||
}
|
||||
|
||||
void
|
||||
nsIDocument::RestorePreviousFullscreenState()
|
||||
nsIDocument::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit)
|
||||
{
|
||||
NS_ASSERTION(!FullscreenStackTop() || !FullscreenRoots::IsEmpty(),
|
||||
"Should have at least 1 fullscreen root when fullscreen!");
|
||||
|
||||
if (!FullscreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) {
|
||||
aExit->MayRejectPromise();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -10898,6 +11011,7 @@ nsIDocument::RestorePreviousFullscreenState()
|
|||
lastDoc->mFullscreenStack.Length() == 1) {
|
||||
// If we are fully exiting fullscreen, don't touch anything here,
|
||||
// just wait for the window to get out from fullscreen first.
|
||||
PendingFullscreenChangeList::Add(std::move(aExit));
|
||||
AskWindowToExitFullscreen(this);
|
||||
return;
|
||||
}
|
||||
|
@ -10925,6 +11039,7 @@ nsIDocument::RestorePreviousFullscreenState()
|
|||
for (Element* e : Reversed(exitElements)) {
|
||||
DispatchFullscreenChange(e->OwnerDoc(), e);
|
||||
}
|
||||
aExit->MayResolvePromise();
|
||||
|
||||
MOZ_ASSERT(newFullscreenDoc, "If we were going to exit from fullscreen on "
|
||||
"all documents in this doctree, we should've asked the window to "
|
||||
|
@ -11133,7 +11248,8 @@ nsresult nsIDocument::RemoteFrameFullscreenChanged(Element* aFrameElement)
|
|||
|
||||
nsresult nsIDocument::RemoteFrameFullscreenReverted()
|
||||
{
|
||||
RestorePreviousFullscreenState();
|
||||
UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
|
||||
RestorePreviousFullscreenState(std::move(exit));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -11244,104 +11360,6 @@ nsIDocument::FullscreenElementReadyCheck(const FullscreenRequest& aRequest)
|
|||
return true;
|
||||
}
|
||||
|
||||
// Any fullscreen request waiting for the widget to finish being full-
|
||||
// screen is queued here. This is declared static instead of a member
|
||||
// of nsDocument because in the majority of time, there would be at most
|
||||
// one document requesting fullscreen. We shouldn't waste the space to
|
||||
// hold for it in every document.
|
||||
class PendingFullscreenRequestList
|
||||
{
|
||||
public:
|
||||
static void Add(UniquePtr<FullscreenRequest> aRequest)
|
||||
{
|
||||
sList.insertBack(aRequest.release());
|
||||
}
|
||||
|
||||
static const FullscreenRequest* GetLast()
|
||||
{
|
||||
return sList.getLast();
|
||||
}
|
||||
|
||||
enum IteratorOption
|
||||
{
|
||||
// When we are committing fullscreen changes or preparing for
|
||||
// that, we generally want to iterate all requests in the same
|
||||
// window with eDocumentsWithSameRoot option.
|
||||
eDocumentsWithSameRoot,
|
||||
// If we are removing a document from the tree, we would only
|
||||
// want to remove the requests from the given document and its
|
||||
// descendants. For that case, use eInclusiveDescendants.
|
||||
eInclusiveDescendants
|
||||
};
|
||||
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
explicit Iterator(nsIDocument* aDoc, IteratorOption aOption)
|
||||
: mCurrent(PendingFullscreenRequestList::sList.getFirst())
|
||||
, mRootShellForIteration(aDoc->GetDocShell())
|
||||
{
|
||||
if (mCurrent) {
|
||||
if (mRootShellForIteration && aOption == eDocumentsWithSameRoot) {
|
||||
mRootShellForIteration->
|
||||
GetRootTreeItem(getter_AddRefs(mRootShellForIteration));
|
||||
}
|
||||
SkipToNextMatch();
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<FullscreenRequest> TakeAndNext()
|
||||
{
|
||||
auto thisRequest = TakeAndNextInternal();
|
||||
SkipToNextMatch();
|
||||
return thisRequest;
|
||||
}
|
||||
bool AtEnd() const { return mCurrent == nullptr; }
|
||||
|
||||
private:
|
||||
UniquePtr<FullscreenRequest> TakeAndNextInternal()
|
||||
{
|
||||
UniquePtr<FullscreenRequest> thisRequest(mCurrent);
|
||||
mCurrent = mCurrent->removeAndGetNext();
|
||||
return thisRequest;
|
||||
}
|
||||
void SkipToNextMatch()
|
||||
{
|
||||
while (mCurrent) {
|
||||
nsCOMPtr<nsIDocShellTreeItem>
|
||||
docShell = mCurrent->Document()->GetDocShell();
|
||||
if (!docShell) {
|
||||
// Always automatically drop fullscreen requests which is from
|
||||
// a document detached from the doc shell.
|
||||
UniquePtr<FullscreenRequest> request = TakeAndNextInternal();
|
||||
request->MayRejectPromise();
|
||||
} else {
|
||||
while (docShell && docShell != mRootShellForIteration) {
|
||||
docShell->GetParent(getter_AddRefs(docShell));
|
||||
}
|
||||
if (!docShell) {
|
||||
// We've gone over the root, but haven't find the target
|
||||
// ancestor, so skip this item.
|
||||
mCurrent = mCurrent->getNext();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FullscreenRequest* mCurrent;
|
||||
nsCOMPtr<nsIDocShellTreeItem> mRootShellForIteration;
|
||||
};
|
||||
|
||||
private:
|
||||
PendingFullscreenRequestList() = delete;
|
||||
|
||||
static LinkedList<FullscreenRequest> sList;
|
||||
};
|
||||
|
||||
/* static */ LinkedList<FullscreenRequest> PendingFullscreenRequestList::sList;
|
||||
|
||||
static nsCOMPtr<nsPIDOMWindowOuter>
|
||||
GetRootWindow(nsIDocument* aDoc)
|
||||
{
|
||||
|
@ -11373,8 +11391,8 @@ ShouldApplyFullscreenDirectly(nsIDocument* aDoc,
|
|||
// pending fullscreen request relates to this document. We have to
|
||||
// push the request to the pending queue so requests are handled
|
||||
// in the correct order.
|
||||
PendingFullscreenRequestList::Iterator
|
||||
iter(aDoc, PendingFullscreenRequestList::eDocumentsWithSameRoot);
|
||||
PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
|
||||
aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
|
||||
if (!iter.AtEnd()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -11417,7 +11435,7 @@ nsIDocument::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest)
|
|||
return;
|
||||
}
|
||||
|
||||
PendingFullscreenRequestList::Add(std::move(aRequest));
|
||||
PendingFullscreenChangeList::Add(std::move(aRequest));
|
||||
if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
||||
// If we are not the top level process, dispatch an event to make
|
||||
// our parent process go fullscreen first.
|
||||
|
@ -11434,8 +11452,8 @@ nsIDocument::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest)
|
|||
nsIDocument::HandlePendingFullscreenRequests(nsIDocument* aDoc)
|
||||
{
|
||||
bool handled = false;
|
||||
PendingFullscreenRequestList::Iterator iter(
|
||||
aDoc, PendingFullscreenRequestList::eDocumentsWithSameRoot);
|
||||
PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
|
||||
aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
|
||||
while (!iter.AtEnd()) {
|
||||
UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
|
||||
nsIDocument* doc = request->Document();
|
||||
|
@ -11449,8 +11467,8 @@ nsIDocument::HandlePendingFullscreenRequests(nsIDocument* aDoc)
|
|||
static void
|
||||
ClearPendingFullscreenRequests(nsIDocument* aDoc)
|
||||
{
|
||||
PendingFullscreenRequestList::Iterator iter(
|
||||
aDoc, PendingFullscreenRequestList::eInclusiveDescendants);
|
||||
PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
|
||||
aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
|
||||
while (!iter.AtEnd()) {
|
||||
UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
|
||||
request->MayRejectPromise();
|
||||
|
|
|
@ -126,7 +126,8 @@ class Encoding;
|
|||
class ErrorResult;
|
||||
class EventStates;
|
||||
class EventListenerManager;
|
||||
struct FullscreenRequest;
|
||||
class FullscreenExit;
|
||||
class FullscreenRequest;
|
||||
class PendingAnimationTracker;
|
||||
class ServoStyleSet;
|
||||
template<typename> class OwningNonNull;
|
||||
|
@ -1811,7 +1812,8 @@ public:
|
|||
* is no former fullscreen element, this exits fullscreen, moving the
|
||||
* top-level browser window out of fullscreen mode.
|
||||
*/
|
||||
void RestorePreviousFullscreenState();
|
||||
void RestorePreviousFullscreenState(
|
||||
mozilla::UniquePtr<mozilla::FullscreenExit>);
|
||||
|
||||
/**
|
||||
* Returns true if this document is a fullscreen leaf document, i.e. it
|
||||
|
@ -3293,7 +3295,7 @@ public:
|
|||
{
|
||||
return !!GetFullscreenElement();
|
||||
}
|
||||
void ExitFullscreen();
|
||||
already_AddRefed<mozilla::dom::Promise> ExitFullscreen(ErrorResult&);
|
||||
void ExitPointerLock()
|
||||
{
|
||||
UnlockPointer(this);
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
|
||||
class nsGenericHTMLElement;
|
||||
class nsIJSID;
|
||||
class nsIDocument;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -53,6 +54,7 @@ namespace dom {
|
|||
class CustomElementReactionsStack;
|
||||
class MessageManagerGlobal;
|
||||
template<typename KeyType, typename ValueType> class Record;
|
||||
class Location;
|
||||
|
||||
nsresult
|
||||
UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src, const nsIID& iid,
|
||||
|
@ -1060,8 +1062,30 @@ struct CheckWrapperCacheTracing<T, true>
|
|||
void
|
||||
AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector,
|
||||
JS::Handle<JSObject*> aGivenProto);
|
||||
|
||||
#endif // DEBUG
|
||||
|
||||
template <class T>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
CrashIfDocumentOrLocationWrapFailed()
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
template<>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
CrashIfDocumentOrLocationWrapFailed<nsIDocument>()
|
||||
{
|
||||
MOZ_CRASH("Looks like bug 1488480/1405521, with WrapObject() on nsIDocument throwing");
|
||||
}
|
||||
|
||||
template<>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
CrashIfDocumentOrLocationWrapFailed<Location>()
|
||||
{
|
||||
MOZ_CRASH("Looks like bug 1488480/1405521, with WrapObject() on Location throwing");
|
||||
}
|
||||
|
||||
template <class T, GetOrCreateReflectorWrapBehavior wrapBehavior>
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
DoGetOrCreateDOMReflector(JSContext* cx, T* value,
|
||||
|
@ -1084,6 +1108,7 @@ DoGetOrCreateDOMReflector(JSContext* cx, T* value,
|
|||
// At this point, obj is null, so just return false.
|
||||
// Callers seem to be testing JS_IsExceptionPending(cx) to
|
||||
// figure out whether WrapObject() threw.
|
||||
CrashIfDocumentOrLocationWrapFailed<T>();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1113,19 +1138,35 @@ DoGetOrCreateDOMReflector(JSContext* cx, T* value,
|
|||
rval.set(JS::ObjectValue(*obj));
|
||||
|
||||
if (js::GetObjectCompartment(obj) == js::GetContextCompartment(cx)) {
|
||||
return TypeNeedsOuterization<T>::value ? TryToOuterize(rval) : true;
|
||||
if (!TypeNeedsOuterization<T>::value) {
|
||||
return true;
|
||||
}
|
||||
if (TryToOuterize(rval)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wrapBehavior == eDontWrapIntoContextCompartment) {
|
||||
if (TypeNeedsOuterization<T>::value) {
|
||||
JSAutoRealm ar(cx, obj);
|
||||
return TryToOuterize(rval);
|
||||
if (TryToOuterize(rval)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return JS_WrapValue(cx, rval);
|
||||
if (JS_WrapValue(cx, rval)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_CRASH("Looks like bug 1488480/1405521, with JS_WrapValue failing");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace binding_detail
|
||||
|
|
|
@ -4015,6 +4015,14 @@ class CGClearCachedValueMethod(CGAbstractMethod):
|
|||
saveMember = (
|
||||
"JS::Rooted<JS::Value> oldValue(aCx, js::GetReservedSlot(obj, %s));\n" %
|
||||
slotIndex)
|
||||
if self.descriptor.name == "Document" or self.descriptor.name == "Location":
|
||||
maybeCrash = dedent(
|
||||
"""
|
||||
MOZ_CRASH("Looks like bug 1488480/1405521, with the getter failing");
|
||||
""")
|
||||
else:
|
||||
maybeCrash = ""
|
||||
|
||||
regetMember = fill(
|
||||
"""
|
||||
JS::Rooted<JS::Value> temp(aCx);
|
||||
|
@ -4022,12 +4030,14 @@ class CGClearCachedValueMethod(CGAbstractMethod):
|
|||
JSAutoRealm ar(aCx, obj);
|
||||
if (!get_${name}(aCx, obj, aObject, args)) {
|
||||
js::SetReservedSlot(obj, ${slotIndex}, oldValue);
|
||||
$*{maybeCrash}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
""",
|
||||
name=self.member.identifier.name,
|
||||
slotIndex=slotIndex)
|
||||
slotIndex=slotIndex,
|
||||
maybeCrash=maybeCrash)
|
||||
else:
|
||||
declObj = "JSObject* obj;\n"
|
||||
noopRetval = ""
|
||||
|
@ -6681,6 +6691,11 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
|
|||
# threw an exception.
|
||||
failed = ("MOZ_ASSERT(JS_IsExceptionPending(cx));\n" +
|
||||
exceptionCode)
|
||||
|
||||
if descriptor.name == "Document" or descriptor.name == "Location":
|
||||
failed = (
|
||||
'MOZ_CRASH("Looks like bug 1488480/1405521, with getting the reflector failing");\n' +
|
||||
failed)
|
||||
else:
|
||||
if descriptor.notflattened:
|
||||
getIID = "&NS_GET_IID(%s), " % descriptor.nativeType
|
||||
|
@ -7836,6 +7851,14 @@ class CGPerSignatureCall(CGThing):
|
|||
"args.rval().isObject()")
|
||||
postConversionSteps += freezeValue.define()
|
||||
|
||||
if self.descriptor.name == "Window":
|
||||
maybeCrash = dedent(
|
||||
"""
|
||||
MOZ_CRASH("Looks like bug 1488480/1405521, with the other MaybeWrap failing");
|
||||
""")
|
||||
else:
|
||||
maybeCrash = ""
|
||||
|
||||
# slotStorageSteps are steps that run once we have entered the
|
||||
# slotStorage compartment.
|
||||
slotStorageSteps= fill(
|
||||
|
@ -7843,11 +7866,13 @@ class CGPerSignatureCall(CGThing):
|
|||
// Make a copy so that we don't do unnecessary wrapping on args.rval().
|
||||
JS::Rooted<JS::Value> storedVal(cx, args.rval());
|
||||
if (!${maybeWrap}(cx, &storedVal)) {
|
||||
$*{maybeCrash}
|
||||
return false;
|
||||
}
|
||||
js::SetReservedSlot(slotStorage, slotIndex, storedVal);
|
||||
""",
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type))
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type),
|
||||
maybeCrash=maybeCrash)
|
||||
|
||||
checkForXray = mayUseXrayExpandoSlots(self.descriptor, self.idlNode)
|
||||
|
||||
|
@ -7885,6 +7910,14 @@ class CGPerSignatureCall(CGThing):
|
|||
else:
|
||||
conversionScope = "slotStorage"
|
||||
|
||||
if self.descriptor.name == "Window":
|
||||
maybeCrash = dedent(
|
||||
"""
|
||||
MOZ_CRASH("Looks like bug 1488480/1405521, with the third MaybeWrap failing");
|
||||
""")
|
||||
else:
|
||||
maybeCrash = ""
|
||||
|
||||
wrapCode = fill(
|
||||
"""
|
||||
{
|
||||
|
@ -7900,13 +7933,18 @@ class CGPerSignatureCall(CGThing):
|
|||
$*{slotStorageSteps}
|
||||
}
|
||||
// And now make sure args.rval() is in the caller realm.
|
||||
return ${maybeWrap}(cx, args.rval());
|
||||
if (${maybeWrap}(cx, args.rval())) {
|
||||
return true;
|
||||
}
|
||||
$*{maybeCrash}
|
||||
return false;
|
||||
""",
|
||||
conversionScope=conversionScope,
|
||||
wrapCode=wrapCode,
|
||||
postConversionSteps=postConversionSteps,
|
||||
slotStorageSteps=slotStorageSteps,
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type))
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type),
|
||||
maybeCrash=maybeCrash)
|
||||
return wrapCode
|
||||
|
||||
def define(self):
|
||||
|
@ -8938,6 +8976,14 @@ class CGSpecializedGetter(CGAbstractStaticMethod):
|
|||
""",
|
||||
slotIndex=memberReservedSlot(self.attr, self.descriptor))
|
||||
|
||||
if self.descriptor.name == "Window":
|
||||
maybeCrash = dedent(
|
||||
"""
|
||||
MOZ_CRASH("Looks like bug 1488480/1405521, with cached value wrapping failing");
|
||||
""")
|
||||
else:
|
||||
maybeCrash = ""
|
||||
|
||||
prefix += fill(
|
||||
"""
|
||||
MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(js::GetObjectClass(slotStorage)) > slotIndex);
|
||||
|
@ -8948,12 +8994,17 @@ class CGSpecializedGetter(CGAbstractStaticMethod):
|
|||
args.rval().set(cachedVal);
|
||||
// The cached value is in the compartment of slotStorage,
|
||||
// so wrap into the caller compartment as needed.
|
||||
return ${maybeWrap}(cx, args.rval());
|
||||
if (${maybeWrap}(cx, args.rval())) {
|
||||
return true;
|
||||
}
|
||||
$*{maybeCrash}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
""",
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.attr.type))
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.attr.type),
|
||||
maybeCrash=maybeCrash)
|
||||
else:
|
||||
prefix = ""
|
||||
|
||||
|
|
|
@ -194,7 +194,7 @@ nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel)
|
|||
}
|
||||
|
||||
static bool
|
||||
IS_TABLE_CELL(LayoutFrameType frameType)
|
||||
IsTableCell(LayoutFrameType frameType)
|
||||
{
|
||||
return LayoutFrameType::TableCell == frameType ||
|
||||
LayoutFrameType::BCTableCell == frameType;
|
||||
|
@ -205,7 +205,7 @@ IsOffsetParent(nsIFrame* aFrame)
|
|||
{
|
||||
LayoutFrameType frameType = aFrame->Type();
|
||||
|
||||
if (IS_TABLE_CELL(frameType) || frameType == LayoutFrameType::Table) {
|
||||
if (IsTableCell(frameType) || frameType == LayoutFrameType::Table) {
|
||||
// Per the IDL for Element, only td, th, and table are acceptable offsetParents
|
||||
// apart from body or positioned elements; we need to check the content type as
|
||||
// well as the frame type so we ignore anonymous tables created by an element
|
||||
|
|
|
@ -109,7 +109,7 @@ function enter2(event) {
|
|||
// the full-screen element to the previous full-screen element. This causes
|
||||
// a fullscreenchange event.
|
||||
addFullscreenChangeContinuation("exit", exit2);
|
||||
document.exitFullscreen();
|
||||
promise = document.exitFullscreen();
|
||||
}
|
||||
|
||||
function exit2(event) {
|
||||
|
@ -117,6 +117,7 @@ function exit2(event) {
|
|||
"Full-screen element should have rolled back.");
|
||||
is(iframe.contentDocument.fullscreenElement, null,
|
||||
"Full-screen element in subframe should be null");
|
||||
assertPromiseResolved(promise, "in exit2");
|
||||
|
||||
addFullscreenChangeContinuation("enter", enter3);
|
||||
promise = FULLSCREEN_ELEMENT.requestFullscreen();
|
||||
|
|
|
@ -3381,34 +3381,17 @@ ContentParent::KillHard(const char* aReason)
|
|||
mCrashReporter->AddAnnotation(CrashReporter::Annotation::ipc_channel_error,
|
||||
reason);
|
||||
|
||||
Telemetry::Accumulate(Telemetry::SUBPROCESS_KILL_HARD, reason, 1);
|
||||
|
||||
RefPtr<ContentParent> self = this;
|
||||
std::function<void(bool)> callback = [self](bool aResult) {
|
||||
self->OnGenerateMinidumpComplete(aResult);
|
||||
};
|
||||
// Generate the report and insert into the queue for submittal.
|
||||
mCrashReporter->GenerateMinidumpAndPair(Process(),
|
||||
nullptr,
|
||||
NS_LITERAL_CSTRING("browser"),
|
||||
std::move(callback),
|
||||
true);
|
||||
return;
|
||||
if (mCrashReporter->GenerateMinidumpAndPair(this,
|
||||
nullptr,
|
||||
NS_LITERAL_CSTRING("browser")))
|
||||
{
|
||||
mCreatedPairedMinidumps = mCrashReporter->FinalizeCrashReport();
|
||||
}
|
||||
|
||||
Telemetry::Accumulate(Telemetry::SUBPROCESS_KILL_HARD, reason, 1);
|
||||
}
|
||||
|
||||
OnGenerateMinidumpComplete(false);
|
||||
}
|
||||
|
||||
void
|
||||
ContentParent::OnGenerateMinidumpComplete(bool aDumpResult)
|
||||
{
|
||||
if (mCrashReporter && aDumpResult) {
|
||||
// CrashReporterHost::GenerateMinidumpAndPair() is successful.
|
||||
mCreatedPairedMinidumps = mCrashReporter->FinalizeCrashReport();
|
||||
}
|
||||
|
||||
Unused << aDumpResult; // Don't care about result if no minidump was requested.
|
||||
|
||||
ProcessHandle otherProcessHandle;
|
||||
if (!base::OpenProcessHandle(OtherPid(), &otherProcessHandle)) {
|
||||
NS_ERROR("Failed to open child process when attempting kill.");
|
||||
|
|
|
@ -862,8 +862,6 @@ private:
|
|||
// Start the force-kill timer on shutdown.
|
||||
void StartForceKillTimer();
|
||||
|
||||
void OnGenerateMinidumpComplete(bool aDumpResult);
|
||||
|
||||
// Ensure that the permissions for the giben Permission key are set in the
|
||||
// content process.
|
||||
//
|
||||
|
|
|
@ -264,8 +264,6 @@ private:
|
|||
void SendHangNotification(const HangData& aHangData,
|
||||
const nsString& aBrowserDumpId,
|
||||
bool aTakeMinidump);
|
||||
void OnTakeFullMinidumpComplete(const HangData& aHangData,
|
||||
const nsString& aDumpId);
|
||||
|
||||
void ClearHangNotification();
|
||||
|
||||
|
@ -738,48 +736,25 @@ HangMonitorParent::SendHangNotification(const HangData& aHangData,
|
|||
// chrome process, main thread
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsString dumpId;
|
||||
if ((aHangData.type() == HangData::TPluginHangData) && aTakeMinidump) {
|
||||
// We've been handed a partial minidump; complete it with plugin and
|
||||
// content process dumps.
|
||||
const PluginHangData& phd = aHangData.get_PluginHangData();
|
||||
|
||||
WeakPtr<HangMonitorParent> self = this;
|
||||
std::function<void(nsString)> callback =
|
||||
[self, aHangData](nsString aResult) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!self) {
|
||||
// Don't report hang since the process has already shut down.
|
||||
return;
|
||||
}
|
||||
|
||||
self->UpdateMinidump(aHangData.get_PluginHangData().pluginId(),
|
||||
aResult);
|
||||
self->OnTakeFullMinidumpComplete(aHangData, aResult);
|
||||
};
|
||||
|
||||
plugins::TakeFullMinidump(phd.pluginId(),
|
||||
phd.contentProcessId(),
|
||||
aBrowserDumpId,
|
||||
std::move(callback),
|
||||
true);
|
||||
plugins::TakeFullMinidump(phd.pluginId(), phd.contentProcessId(),
|
||||
aBrowserDumpId, dumpId);
|
||||
UpdateMinidump(phd.pluginId(), dumpId);
|
||||
} else {
|
||||
// We already have a full minidump; go ahead and use it.
|
||||
OnTakeFullMinidumpComplete(aHangData, aBrowserDumpId);
|
||||
dumpId = aBrowserDumpId;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HangMonitorParent::OnTakeFullMinidumpComplete(const HangData& aHangData,
|
||||
const nsString& aDumpId)
|
||||
{
|
||||
mProcess->SetHangData(aHangData, aDumpId);
|
||||
mProcess->SetHangData(aHangData, dumpId);
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
observerService->NotifyObservers(mProcess,
|
||||
"process-hang-report",
|
||||
nullptr);
|
||||
observerService->NotifyObservers(mProcess, "process-hang-report", nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1117,20 +1092,12 @@ HangMonitoredProcess::TerminatePlugin()
|
|||
// Use the multi-process crash report generated earlier.
|
||||
uint32_t id = mHangData.get_PluginHangData().pluginId();
|
||||
base::ProcessId contentPid = mHangData.get_PluginHangData().contentProcessId();
|
||||
plugins::TerminatePlugin(id, contentPid, NS_LITERAL_CSTRING("HangMonitor"),
|
||||
mDumpId);
|
||||
|
||||
RefPtr<HangMonitoredProcess> self{this};
|
||||
std::function<void(bool)> callback =
|
||||
[self, id](bool aResult) {
|
||||
if (self->mActor) {
|
||||
self->mActor->CleanupPluginHang(id, false);
|
||||
}
|
||||
};
|
||||
|
||||
plugins::TerminatePlugin(id,
|
||||
contentPid,
|
||||
NS_LITERAL_CSTRING("HangMonitor"),
|
||||
mDumpId,
|
||||
std::move(callback));
|
||||
if (mActor) {
|
||||
mActor->CleanupPluginHang(id, false);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#ifndef mozilla_plugins_PluginBridge_h
|
||||
#define mozilla_plugins_PluginBridge_h
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "base/process.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -41,15 +39,14 @@ void
|
|||
TakeFullMinidump(uint32_t aPluginId,
|
||||
base::ProcessId aContentProcessId,
|
||||
const nsAString& aBrowserDumpId,
|
||||
std::function<void(nsString)>&& aCallback,
|
||||
bool aAsync);
|
||||
nsString& aDumpId);
|
||||
|
||||
void
|
||||
TerminatePlugin(uint32_t aPluginId,
|
||||
base::ProcessId aContentProcessId,
|
||||
const nsCString& aMonitorDescription,
|
||||
const nsAString& aDumpId,
|
||||
std::function<void(bool)>&& aCallback);
|
||||
const nsAString& aDumpId);
|
||||
|
||||
} // namespace plugins
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -355,13 +355,10 @@ PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse)
|
|||
int responseCode;
|
||||
if (aResponse & HANGUI_USER_RESPONSE_STOP) {
|
||||
// User clicked Stop
|
||||
std::function<void(bool)> callback = [](bool aResult) { };
|
||||
mModule->TerminateChildProcess(mMainThreadMessageLoop,
|
||||
mozilla::ipc::kInvalidProcessId,
|
||||
NS_LITERAL_CSTRING("ModalHangUI"),
|
||||
EmptyString(),
|
||||
mModule->DummyCallback<bool>(),
|
||||
/* aAsync = */ false);
|
||||
EmptyString());
|
||||
responseCode = 1;
|
||||
} else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) {
|
||||
mModule->OnHangUIContinue();
|
||||
|
|
|
@ -357,19 +357,13 @@ void
|
|||
mozilla::plugins::TakeFullMinidump(uint32_t aPluginId,
|
||||
base::ProcessId aContentProcessId,
|
||||
const nsAString& aBrowserDumpId,
|
||||
std::function<void(nsString)>&& aCallback,
|
||||
bool aAsync)
|
||||
nsString& aDumpId)
|
||||
{
|
||||
PluginModuleChromeParent* chromeParent =
|
||||
PluginModuleChromeParentForId(aPluginId);
|
||||
|
||||
if (chromeParent) {
|
||||
chromeParent->TakeFullMinidump(aContentProcessId,
|
||||
aBrowserDumpId,
|
||||
std::move(aCallback),
|
||||
aAsync);
|
||||
} else {
|
||||
aCallback(EmptyString());
|
||||
chromeParent->TakeFullMinidump(aContentProcessId, aBrowserDumpId, aDumpId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,8 +371,7 @@ void
|
|||
mozilla::plugins::TerminatePlugin(uint32_t aPluginId,
|
||||
base::ProcessId aContentProcessId,
|
||||
const nsCString& aMonitorDescription,
|
||||
const nsAString& aDumpId,
|
||||
std::function<void(bool)>&& aCallback)
|
||||
const nsAString& aDumpId)
|
||||
{
|
||||
PluginModuleChromeParent* chromeParent =
|
||||
PluginModuleChromeParentForId(aPluginId);
|
||||
|
@ -387,11 +380,7 @@ mozilla::plugins::TerminatePlugin(uint32_t aPluginId,
|
|||
chromeParent->TerminateChildProcess(MessageLoop::current(),
|
||||
aContentProcessId,
|
||||
aMonitorDescription,
|
||||
aDumpId,
|
||||
std::move(aCallback),
|
||||
true); // Always runs asynchronously.
|
||||
} else {
|
||||
aCallback(true);
|
||||
aDumpId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -587,7 +576,7 @@ PluginModuleChromeParent::InitCrashReporter()
|
|||
}
|
||||
|
||||
{
|
||||
mozilla::RecursiveMutexAutoLock lock(mCrashReporterMutex);
|
||||
mozilla::MutexAutoLock lock(mCrashReporterMutex);
|
||||
mCrashReporter = MakeUnique<ipc::CrashReporterHost>(
|
||||
GeckoProcessType_Plugin,
|
||||
shmem,
|
||||
|
@ -727,7 +716,7 @@ void
|
|||
PluginModuleChromeParent::WriteExtraDataForMinidump()
|
||||
{
|
||||
// mCrashReporterMutex is already held by the caller
|
||||
mCrashReporterMutex.AssertCurrentThreadIn();
|
||||
mCrashReporterMutex.AssertCurrentThreadOwns();
|
||||
|
||||
typedef nsDependentCString cstring;
|
||||
|
||||
|
@ -1066,14 +1055,10 @@ PluginModuleChromeParent::ShouldContinueFromReplyTimeout()
|
|||
FinishHangUI();
|
||||
#endif // XP_WIN
|
||||
|
||||
// Terminate the child process synchronously because this function can be
|
||||
// called in sync IPC.
|
||||
TerminateChildProcess(MessageLoop::current(),
|
||||
mozilla::ipc::kInvalidProcessId,
|
||||
NS_LITERAL_CSTRING("ModalHangUI"),
|
||||
EmptyString(),
|
||||
DummyCallback<bool>(),
|
||||
/* aAsync = */ false);
|
||||
EmptyString());
|
||||
GetIPCChannel()->CloseWithTimeout();
|
||||
return false;
|
||||
}
|
||||
|
@ -1098,141 +1083,55 @@ PluginModuleContentParent::OnExitedSyncSend()
|
|||
void
|
||||
PluginModuleChromeParent::TakeFullMinidump(base::ProcessId aContentPid,
|
||||
const nsAString& aBrowserDumpId,
|
||||
std::function<void(nsString)>&& aCallback,
|
||||
bool aAsync)
|
||||
nsString& aDumpId)
|
||||
{
|
||||
mozilla::RecursiveMutexAutoLock lock(mCrashReporterMutex);
|
||||
mozilla::MutexAutoLock lock(mCrashReporterMutex);
|
||||
|
||||
if (!mCrashReporter || !mTakeFullMinidumpCallback.IsEmpty()) {
|
||||
aCallback(EmptyString());
|
||||
if (!mCrashReporter) {
|
||||
return;
|
||||
}
|
||||
mTakeFullMinidumpCallback.Init(std::move(aCallback), aAsync);
|
||||
|
||||
nsString browserDumpId{aBrowserDumpId};
|
||||
bool reportsReady = false;
|
||||
|
||||
// Check to see if we already have a browser dump id - with e10s plugin
|
||||
// hangs we take this earlier (see ProcessHangMonitor) from a background
|
||||
// thread. We do this before we message the main thread about the hang
|
||||
// since the posted message will trash our browser stack state.
|
||||
nsCOMPtr<nsIFile> browserDumpFile;
|
||||
if (CrashReporter::GetMinidumpForID(aBrowserDumpId,
|
||||
getter_AddRefs(mBrowserDumpFile))) {
|
||||
|
||||
// Hold a ref to mPlugin to keep *this* alive until the callback runs.
|
||||
RetainPluginRef();
|
||||
std::function<void(bool)> callback =
|
||||
[this, aContentPid, browserDumpId, aAsync](bool aResult) {
|
||||
if (aAsync) {
|
||||
this->mCrashReporterMutex.Lock();
|
||||
}
|
||||
|
||||
this->TakeBrowserAndPluginMinidumps(aResult,
|
||||
aContentPid,
|
||||
browserDumpId,
|
||||
aAsync);
|
||||
if (aAsync) {
|
||||
this->mCrashReporterMutex.Unlock();
|
||||
}
|
||||
|
||||
this->ReleasePluginRef();
|
||||
};
|
||||
getter_AddRefs(browserDumpFile))) {
|
||||
// We have a single browser report, generate a new plugin process parent
|
||||
// report and pair it up with the browser report handed in.
|
||||
mCrashReporter->GenerateMinidumpAndPair(Process(), mBrowserDumpFile,
|
||||
NS_LITERAL_CSTRING("browser"),
|
||||
std::move(callback), aAsync);
|
||||
} else {
|
||||
TakeBrowserAndPluginMinidumps(false, aContentPid, browserDumpId, aAsync);
|
||||
}
|
||||
}
|
||||
reportsReady = mCrashReporter->GenerateMinidumpAndPair(
|
||||
this,
|
||||
browserDumpFile,
|
||||
NS_LITERAL_CSTRING("browser"));
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::RetainPluginRef()
|
||||
{
|
||||
if (!mPlugin) {
|
||||
return;
|
||||
if (!reportsReady) {
|
||||
browserDumpFile = nullptr;
|
||||
CrashReporter::DeleteMinidumpFilesForID(aBrowserDumpId);
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
mPlugin->AddRef();
|
||||
} else {
|
||||
// XXX We can't sync-dispatch to the main thread because doing that
|
||||
// deadlocks when we are called from
|
||||
// PluginHangUIParent::RecvUserResponse().
|
||||
Unused << NS_DispatchToMainThread(
|
||||
NewNonOwningRunnableMethod("nsNPAPIPlugin::AddRef",
|
||||
mPlugin, &nsNPAPIPlugin::AddRef));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::ReleasePluginRef()
|
||||
{
|
||||
if (!mPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
mPlugin->Release();
|
||||
} else {
|
||||
// Async release the reference to mPlugin.
|
||||
Unused << NS_DispatchToMainThread(
|
||||
NewNonOwningRunnableMethod("nsNPAPIPlugin::Release",
|
||||
mPlugin, &nsNPAPIPlugin::Release));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::TakeBrowserAndPluginMinidumps(bool aReportsReady,
|
||||
base::ProcessId aContentPid,
|
||||
const nsAString& aBrowserDumpId,
|
||||
bool aAsync)
|
||||
{
|
||||
mCrashReporterMutex.AssertCurrentThreadIn();
|
||||
|
||||
// Generate crash report including plugin and browser process minidumps.
|
||||
// The plugin process is the parent report with additional dumps including
|
||||
// the browser process, content process when running under e10s, and
|
||||
// various flash subprocesses if we're the flash module.
|
||||
if (!aReportsReady) {
|
||||
mBrowserDumpFile = nullptr;
|
||||
CrashReporter::DeleteMinidumpFilesForID(aBrowserDumpId);
|
||||
|
||||
nsString browserDumpId{aBrowserDumpId};
|
||||
|
||||
RetainPluginRef();
|
||||
std::function<void(bool)> callback =
|
||||
[this, aContentPid, browserDumpId](bool aResult) {
|
||||
this->OnTakeFullMinidumpComplete(aResult,
|
||||
aContentPid,
|
||||
browserDumpId);
|
||||
this->ReleasePluginRef();
|
||||
};
|
||||
mCrashReporter->GenerateMinidumpAndPair(Process(),
|
||||
nullptr, // Pair with a dump of this process and thread.
|
||||
NS_LITERAL_CSTRING("browser"),
|
||||
std::move(callback),
|
||||
aAsync);
|
||||
} else {
|
||||
OnTakeFullMinidumpComplete(aReportsReady, aContentPid, aBrowserDumpId);
|
||||
if (!reportsReady) {
|
||||
reportsReady = mCrashReporter->GenerateMinidumpAndPair(
|
||||
this,
|
||||
nullptr, // Pair with a dump of this process and thread.
|
||||
NS_LITERAL_CSTRING("browser"));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::OnTakeFullMinidumpComplete(bool aReportsReady,
|
||||
base::ProcessId aContentPid,
|
||||
const nsAString& aBrowserDumpId)
|
||||
{
|
||||
mCrashReporterMutex.AssertCurrentThreadIn();
|
||||
|
||||
if (aReportsReady) {
|
||||
nsString dumpId = mCrashReporter->MinidumpID();
|
||||
if (reportsReady) {
|
||||
aDumpId = mCrashReporter->MinidumpID();
|
||||
PLUGIN_LOG_DEBUG(
|
||||
("generated paired browser/plugin minidumps: %s)",
|
||||
NS_ConvertUTF16toUTF8(dumpId).get()));
|
||||
("generated paired browser/plugin minidumps: %s)",
|
||||
NS_ConvertUTF16toUTF8(aDumpId).get()));
|
||||
nsAutoCString additionalDumps("browser");
|
||||
nsCOMPtr<nsIFile> pluginDumpFile;
|
||||
if (GetMinidumpForID(dumpId, getter_AddRefs(pluginDumpFile))) {
|
||||
if (GetMinidumpForID(aDumpId, getter_AddRefs(pluginDumpFile))) {
|
||||
#ifdef MOZ_CRASHREPORTER_INJECTOR
|
||||
// If we have handles to the flash sandbox processes on Windows,
|
||||
// include those minidumps as well.
|
||||
|
@ -1256,10 +1155,7 @@ PluginModuleChromeParent::OnTakeFullMinidumpComplete(bool aReportsReady,
|
|||
}
|
||||
mCrashReporter->AddAnnotation(Annotation::additional_minidumps,
|
||||
additionalDumps);
|
||||
|
||||
mTakeFullMinidumpCallback.Invoke(mCrashReporter->MinidumpID());
|
||||
} else {
|
||||
mTakeFullMinidumpCallback.Invoke(EmptyString());
|
||||
NS_WARNING("failed to capture paired minidumps from hang");
|
||||
}
|
||||
}
|
||||
|
@ -1268,52 +1164,20 @@ void
|
|||
PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop,
|
||||
base::ProcessId aContentPid,
|
||||
const nsCString& aMonitorDescription,
|
||||
const nsAString& aDumpId,
|
||||
std::function<void(bool)>&& aCallback,
|
||||
bool aAsync)
|
||||
const nsAString& aDumpId)
|
||||
{
|
||||
if (!mTerminateChildProcessCallback.IsEmpty()) {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
mTerminateChildProcessCallback.Init(std::move(aCallback), aAsync);
|
||||
|
||||
// Start by taking a full minidump if necessary, this is done early
|
||||
// because it also needs to lock the mCrashReporterMutex and Mutex doesn't
|
||||
// support recursive locking.
|
||||
nsAutoString dumpId;
|
||||
if (aDumpId.IsEmpty()) {
|
||||
|
||||
RetainPluginRef();
|
||||
std::function<void(nsString)> callback =
|
||||
[this, aMsgLoop, aMonitorDescription, aAsync](nsString aResult) {
|
||||
if (aAsync) {
|
||||
this->mCrashReporterMutex.Lock();
|
||||
}
|
||||
this->TerminateChildProcessOnDumpComplete(aMsgLoop,
|
||||
aMonitorDescription);
|
||||
if (aAsync) {
|
||||
this->mCrashReporterMutex.Unlock();
|
||||
}
|
||||
|
||||
this->ReleasePluginRef();
|
||||
};
|
||||
|
||||
TakeFullMinidump(aContentPid, EmptyString(), std::move(callback), aAsync);
|
||||
} else {
|
||||
TerminateChildProcessOnDumpComplete(aMsgLoop, aMonitorDescription);
|
||||
TakeFullMinidump(aContentPid, EmptyString(), dumpId);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::TerminateChildProcessOnDumpComplete(MessageLoop* aMsgLoop,
|
||||
const nsCString& aMonitorDescription)
|
||||
{
|
||||
mCrashReporterMutex.AssertCurrentThreadIn();
|
||||
|
||||
mozilla::MutexAutoLock lock(mCrashReporterMutex);
|
||||
if (!mCrashReporter) {
|
||||
// If mCrashReporter is null then the hang has ended, the plugin module
|
||||
// is shutting down. There's nothing to do here.
|
||||
mTerminateChildProcessCallback.Invoke(true);
|
||||
return;
|
||||
}
|
||||
mCrashReporter->AddAnnotation(Annotation::PluginHang, true);
|
||||
|
@ -1370,8 +1234,6 @@ PluginModuleChromeParent::TerminateChildProcessOnDumpComplete(MessageLoop* aMsgL
|
|||
if (!childOpened || !KillProcess(geckoChildProcess, 1, false)) {
|
||||
NS_WARNING("failed to kill subprocess!");
|
||||
}
|
||||
|
||||
mTerminateChildProcessCallback.Invoke(true);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -1522,7 +1384,7 @@ RemoveMinidump(nsIFile* minidump)
|
|||
void
|
||||
PluginModuleChromeParent::ProcessFirstMinidump()
|
||||
{
|
||||
mozilla::RecursiveMutexAutoLock lock(mCrashReporterMutex);
|
||||
mozilla::MutexAutoLock lock(mCrashReporterMutex);
|
||||
|
||||
if (!mCrashReporter)
|
||||
return;
|
||||
|
|
|
@ -11,13 +11,11 @@
|
|||
#include "mozilla/FileUtils.h"
|
||||
#include "mozilla/HangAnnotations.h"
|
||||
#include "mozilla/PluginLibrary.h"
|
||||
#include "mozilla/ipc/CrashReporterHost.h"
|
||||
#include "mozilla/plugins/PluginProcessParent.h"
|
||||
#include "mozilla/plugins/PPluginModuleParent.h"
|
||||
#include "mozilla/plugins/PluginMessageUtils.h"
|
||||
#include "mozilla/plugins/PluginTypes.h"
|
||||
#include "mozilla/ipc/TaskFactory.h"
|
||||
#include "mozilla/RecursiveMutex.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "npapi.h"
|
||||
|
@ -34,6 +32,9 @@ class nsPluginTag;
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
class CrashReporterHost;
|
||||
} // namespace ipc
|
||||
namespace layers {
|
||||
class TextureClientRecycleAllocator;
|
||||
} // namespace layers
|
||||
|
@ -318,11 +319,9 @@ protected:
|
|||
* This mutex protects the crash reporter when the Plugin Hang UI event
|
||||
* handler is executing off main thread. It is intended to protect both
|
||||
* the mCrashReporter variable in addition to the CrashReporterHost object
|
||||
* that mCrashReporter refers to. Sometimes asynchronous crash reporter
|
||||
* callbacks are dispatched synchronously while the caller is still holding
|
||||
* the mutex. This requires recursive locking support in the mutex.
|
||||
* that mCrashReporter refers to.
|
||||
*/
|
||||
mozilla::RecursiveMutex mCrashReporterMutex;
|
||||
mozilla::Mutex mCrashReporterMutex;
|
||||
UniquePtr<ipc::CrashReporterHost> mCrashReporter;
|
||||
};
|
||||
|
||||
|
@ -359,10 +358,6 @@ class PluginModuleChromeParent
|
|||
, public mozilla::BackgroundHangAnnotator
|
||||
{
|
||||
friend class mozilla::ipc::CrashReporterHost;
|
||||
using TerminateChildProcessCallback =
|
||||
mozilla::ipc::CrashReporterHost::CallbackWrapper<bool>;
|
||||
using TakeFullMinidumpCallback =
|
||||
mozilla::ipc::CrashReporterHost::CallbackWrapper<nsString>;
|
||||
public:
|
||||
/**
|
||||
* LoadModule
|
||||
|
@ -387,16 +382,12 @@ class PluginModuleChromeParent
|
|||
* provided TakeFullMinidump will use this dump file instead of
|
||||
* generating a new one. If not provided a browser dump will be taken at
|
||||
* the time of this call.
|
||||
* @param aCallback a callback invoked when the operation completes. The ID
|
||||
* of the newly generated crash dump is provided in the callback argument.
|
||||
* An empty string will be provided upon failure.
|
||||
* @param aAsync whether to perform the dump asynchronously.
|
||||
* @param aDumpId Returns the ID of the newly generated crash dump. Left
|
||||
* untouched upon failure.
|
||||
*/
|
||||
void
|
||||
TakeFullMinidump(base::ProcessId aContentPid,
|
||||
const nsAString& aBrowserDumpId,
|
||||
std::function<void(nsString)>&& aCallback,
|
||||
bool aAsync);
|
||||
void TakeFullMinidump(base::ProcessId aContentPid,
|
||||
const nsAString& aBrowserDumpId,
|
||||
nsString& aDumpId);
|
||||
|
||||
/*
|
||||
* Terminates the plugin process associated with this plugin module. Also
|
||||
|
@ -414,46 +405,11 @@ class PluginModuleChromeParent
|
|||
* TerminateChildProcess will use this dump file instead of generating a
|
||||
* multi-process crash report. If not provided a multi-process dump will
|
||||
* be taken at the time of this call.
|
||||
* @param aCallback a callback invoked when the operation completes. The
|
||||
* argument denotes whether the operation succeeded.
|
||||
* @param aAsync whether to perform the operation asynchronously.
|
||||
*/
|
||||
void
|
||||
TerminateChildProcess(MessageLoop* aMsgLoop,
|
||||
base::ProcessId aContentPid,
|
||||
const nsCString& aMonitorDescription,
|
||||
const nsAString& aDumpId,
|
||||
std::function<void(bool)>&& aCallback,
|
||||
bool aAsync);
|
||||
|
||||
/**
|
||||
* Helper for passing a dummy callback in calling the above function if it
|
||||
* is called synchronously and the caller doesn't care about the callback
|
||||
* result.
|
||||
*/
|
||||
template<typename T>
|
||||
static std::function<void(T)> DummyCallback()
|
||||
{
|
||||
return std::function<void(T)>([](T aResult) { });
|
||||
}
|
||||
|
||||
private:
|
||||
// The following methods are callbacks invoked after calling
|
||||
// TakeFullMinidump(). The methods are invoked in the following order:
|
||||
void TakeBrowserAndPluginMinidumps(bool aReportsReady,
|
||||
base::ProcessId aContentPid,
|
||||
const nsAString& aBrowserDumpId,
|
||||
bool aAsync);
|
||||
void OnTakeFullMinidumpComplete(bool aReportsReady,
|
||||
base::ProcessId aContentPid,
|
||||
const nsAString& aBrowserDumpId);
|
||||
|
||||
|
||||
// The following method is the callback invoked after calling
|
||||
// TerminateChidlProcess().
|
||||
void TerminateChildProcessOnDumpComplete(MessageLoop* aMsgLoop,
|
||||
const nsCString& aMonitorDescription);
|
||||
public:
|
||||
void TerminateChildProcess(MessageLoop* aMsgLoop,
|
||||
base::ProcessId aContentPid,
|
||||
const nsCString& aMonitorDescription,
|
||||
const nsAString& aDumpId);
|
||||
|
||||
#ifdef XP_WIN
|
||||
/**
|
||||
|
@ -486,8 +442,6 @@ private:
|
|||
|
||||
void ProcessFirstMinidump();
|
||||
void WriteExtraDataForMinidump();
|
||||
void RetainPluginRef();
|
||||
void ReleasePluginRef();
|
||||
|
||||
PluginProcessParent* Process() const { return mSubprocess; }
|
||||
base::ProcessHandle ChildProcessHandle() { return mSubprocess->GetChildProcessHandle(); }
|
||||
|
@ -603,12 +557,6 @@ private:
|
|||
|
||||
nsCOMPtr<nsIObserver> mPluginOfflineObserver;
|
||||
bool mIsBlocklisted;
|
||||
|
||||
nsCOMPtr<nsIFile> mBrowserDumpFile;
|
||||
TakeFullMinidumpCallback mTakeFullMinidumpCallback;
|
||||
|
||||
TerminateChildProcessCallback mTerminateChildProcessCallback;
|
||||
|
||||
bool mIsCleaningFromTimeout;
|
||||
};
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче