Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
|
@ -35,6 +35,10 @@ browser/extensions/mortar/ppapi/.*
|
|||
db/sqlite3/src/.*
|
||||
devtools/client/sourceeditor/codemirror/.*
|
||||
devtools/client/sourceeditor/tern/.*
|
||||
dom/media/gmp/widevine-adapter/content_decryption_module.h
|
||||
dom/media/gmp/widevine-adapter/content_decryption_module_export.h
|
||||
dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
|
||||
dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h
|
||||
editor/libeditor/tests/browserscope/lib/richtext/.*
|
||||
editor/libeditor/tests/browserscope/lib/richtext2/.*
|
||||
extensions/spellcheck/hunspell/src/.*
|
||||
|
|
|
@ -120,8 +120,6 @@ devtools/shared/qrcode/tests/mochitest/test_decode.html
|
|||
devtools/shared/tests/mochitest/*.html
|
||||
devtools/shared/webconsole/test/test_*.html
|
||||
|
||||
# Soon to be removed
|
||||
devtools/client/commandline/**
|
||||
# Soon to be removed, the new/ directory is explicitly excluded below due to
|
||||
# also being an imported repository.
|
||||
devtools/client/debugger/**
|
||||
|
@ -136,6 +134,9 @@ devtools/client/webide/preferences/**
|
|||
devtools/shared/preferences/**
|
||||
devtools/startup/preferences/devtools-startup.js
|
||||
|
||||
# Ignore devtools generated code
|
||||
devtools/shared/css/generated/properties-db.js
|
||||
|
||||
# Ignore devtools third-party libs
|
||||
devtools/shared/jsbeautify/*
|
||||
devtools/shared/acorn/*
|
||||
|
|
|
@ -760,7 +760,7 @@ DocAccessibleParent::SendParentCOMProxy()
|
|||
}
|
||||
|
||||
#if defined(MOZ_CONTENT_SANDBOX)
|
||||
mParentProxyStream = std::move(holder.GetPreservedStream());
|
||||
mParentProxyStream = holder.GetPreservedStream();
|
||||
#endif // defined(MOZ_CONTENT_SANDBOX)
|
||||
}
|
||||
|
||||
|
|
|
@ -247,7 +247,7 @@ HandlerProvider::BuildDynamicIA2Data(DynamicIA2Data* aOutIA2Data)
|
|||
return FAILED(hr);
|
||||
};
|
||||
|
||||
auto cleanup = [this, aOutIA2Data]() -> void {
|
||||
auto cleanup = [aOutIA2Data]() -> void {
|
||||
CleanupDynamicIA2Data(*aOutIA2Data);
|
||||
};
|
||||
|
||||
|
|
|
@ -70,14 +70,14 @@ PlatformChild::PlatformChild()
|
|||
|
||||
UniquePtr<mozilla::mscom::RegisteredProxy> customProxy;
|
||||
mozilla::mscom::EnsureMTA([&customProxy]() -> void {
|
||||
customProxy = std::move(mozilla::mscom::RegisterProxy());
|
||||
customProxy = mozilla::mscom::RegisterProxy();
|
||||
});
|
||||
mCustomProxy = std::move(customProxy);
|
||||
|
||||
// IA2 needs to be registered in both the main thread's STA as well as the MTA
|
||||
UniquePtr<mozilla::mscom::RegisteredProxy> ia2ProxyMTA;
|
||||
mozilla::mscom::EnsureMTA([&ia2ProxyMTA]() -> void {
|
||||
ia2ProxyMTA = std::move(mozilla::mscom::RegisterProxy(L"ia2marshal.dll"));
|
||||
ia2ProxyMTA = mozilla::mscom::RegisterProxy(L"ia2marshal.dll");
|
||||
});
|
||||
mIA2ProxyMTA = std::move(ia2ProxyMTA);
|
||||
}
|
||||
|
|
|
@ -244,7 +244,7 @@ ProxyAccessible::BoundsInCSSPixels()
|
|||
}
|
||||
|
||||
nsIntRect rect;
|
||||
HRESULT hr = custom->get_boundsInCSSPixels(&rect.x, &rect.y, &rect.width, &rect.height);
|
||||
Unused << custom->get_boundsInCSSPixels(&rect.x, &rect.y, &rect.width, &rect.height);
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
@ -416,7 +416,7 @@ ProxyAccessible::RelationByType(RelationType aType) const
|
|||
}
|
||||
CoTaskMemFree(targets);
|
||||
|
||||
return std::move(proxies);
|
||||
return proxies;
|
||||
}
|
||||
|
||||
double
|
||||
|
|
|
@ -1770,7 +1770,7 @@ AccessibleWrap::InvalidateHandlers()
|
|||
if (hr == CO_E_OBJNOTCONNECTED || hr == kErrorServerDied) {
|
||||
sHandlerControllers->RemoveElement(controller);
|
||||
} else {
|
||||
NS_WARN_IF(FAILED(hr));
|
||||
Unused << NS_WARN_IF(FAILED(hr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1737,10 +1737,6 @@ pref("app.shield.optoutstudies.enabled", true);
|
|||
pref("app.shield.optoutstudies.enabled", false);
|
||||
#endif
|
||||
|
||||
// Savant Shield study preferences
|
||||
pref("shield.savant.enabled", false);
|
||||
pref("shield.savant.loglevel", "warn");
|
||||
|
||||
// Multi-lingual preferences
|
||||
pref("intl.multilingual.enabled", false);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
|
||||
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
|
||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
|
||||
CFRPageActions: "resource://activity-stream/lib/CFRPageActions.jsm",
|
||||
CharsetMenu: "resource://gre/modules/CharsetMenu.jsm",
|
||||
Color: "resource://gre/modules/Color.jsm",
|
||||
ContentSearch: "resource:///modules/ContentSearch.jsm",
|
||||
|
@ -4817,6 +4818,8 @@ var XULBrowserWindow = {
|
|||
CustomizationHandler.isCustomizing()) {
|
||||
gCustomizeMode.exit();
|
||||
}
|
||||
|
||||
CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
|
||||
}
|
||||
UpdateBackForwardCommands(gBrowser.webNavigation);
|
||||
ReaderParent.updateReaderButton(gBrowser.selectedBrowser);
|
||||
|
|
|
@ -634,6 +634,16 @@ xmlns="http://www.w3.org/1999/xhtml"
|
|||
accesskey="&syncSyncNowItem.accesskey;"
|
||||
id="syncedTabsRefreshFilter"/>
|
||||
</menupopup>
|
||||
|
||||
<hbox id="statuspanel" inactive="true" layer="true">
|
||||
<hbox id="statuspanel-inner">
|
||||
<label id="statuspanel-label"
|
||||
role="status"
|
||||
aria-live="off"
|
||||
flex="1"
|
||||
crop="end"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</popupset>
|
||||
<box id="appMenu-viewCache" hidden="true"/>
|
||||
|
||||
|
@ -1261,37 +1271,7 @@ xmlns="http://www.w3.org/1999/xhtml"
|
|||
<tabbox id="tabbrowser-tabbox"
|
||||
flex="1" eventnode="document" tabcontainer="tabbrowser-tabs">
|
||||
<tabpanels id="tabbrowser-tabpanels"
|
||||
flex="1" class="plain" selectedIndex="0">
|
||||
<notificationbox flex="1" notificationside="top">
|
||||
<!-- Set large flex to allow the devtools toolbox to set a flex attribute.
|
||||
We don't want the toolbox to actually take up free space, but we do want it to collapse when the window shrinks, and with flex=0 it can't.
|
||||
When the toolbox is on the bottom it's a sibling of browserSidebarContainer,
|
||||
and when it's on the side it's a sibling of browserContainer. -->
|
||||
<hbox flex="10000" class="browserSidebarContainer">
|
||||
<vbox flex="10000" class="browserContainer">
|
||||
<stack flex="1" class="browserStack">
|
||||
<browser id="tabbrowser-initialBrowser" type="content"
|
||||
message="true" messagemanagergroup="browsers"
|
||||
primary="true" blank="true"
|
||||
tooltip="aHTMLTooltip"
|
||||
contextmenu="contentAreaContextMenu"
|
||||
autocompletepopup="PopupAutoComplete"
|
||||
selectmenulist="ContentSelectDropdown"
|
||||
datetimepicker="DateTimePickerPanel"/>
|
||||
</stack>
|
||||
<hbox id="statuspanel" inactive="true" layer="true">
|
||||
<hbox id="statuspanel-inner">
|
||||
<label id="statuspanel-label"
|
||||
role="status"
|
||||
aria-live="off"
|
||||
flex="1"
|
||||
crop="end"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</notificationbox>
|
||||
</tabpanels>
|
||||
flex="1" class="plain" selectedIndex="0"/>
|
||||
</tabbox>
|
||||
</vbox>
|
||||
<vbox id="browser-border-end" hidden="true" layer="true"/>
|
||||
|
|
|
@ -282,50 +282,40 @@ window._gBrowser = {
|
|||
return this._selectedBrowser;
|
||||
},
|
||||
|
||||
get initialBrowser() {
|
||||
delete this.initialBrowser;
|
||||
return this.initialBrowser = document.getElementById("tabbrowser-initialBrowser");
|
||||
},
|
||||
|
||||
_setupInitialBrowserAndTab() {
|
||||
let browser = this.initialBrowser;
|
||||
this._selectedBrowser = browser;
|
||||
|
||||
browser.permanentKey = {};
|
||||
// Bug 1362774 will adjust this to only set `uriIsAboutBlank` when
|
||||
// necessary. For now, we always pass it.
|
||||
let browser = this._createBrowser({uriIsAboutBlank: true});
|
||||
browser.setAttribute("primary", "true");
|
||||
browser.setAttribute("blank", "true");
|
||||
browser.droppedLinkHandler = handleDroppedLink;
|
||||
browser.loadURI = _loadURI.bind(null, browser);
|
||||
|
||||
let autoScrollPopup = browser._createAutoScrollPopup();
|
||||
autoScrollPopup.id = "autoscroller";
|
||||
document.getElementById("mainPopupSet").appendChild(autoScrollPopup);
|
||||
browser.setAttribute("autoscrollpopup", autoScrollPopup.id);
|
||||
|
||||
this._defaultBrowserAttributes = {
|
||||
autoscrollpopup: "",
|
||||
contextmenu: "",
|
||||
datetimepicker: "",
|
||||
message: "",
|
||||
messagemanagergroup: "",
|
||||
selectmenulist: "",
|
||||
tooltip: "",
|
||||
type: "",
|
||||
};
|
||||
for (let attribute in this._defaultBrowserAttributes) {
|
||||
this._defaultBrowserAttributes[attribute] = browser.getAttribute(attribute);
|
||||
}
|
||||
let uniqueId = this._generateUniquePanelID();
|
||||
let notificationbox = this.getNotificationBox(browser);
|
||||
notificationbox.id = uniqueId;
|
||||
this.tabpanels.appendChild(notificationbox);
|
||||
|
||||
let tab = this.tabs[0];
|
||||
this._selectedTab = tab;
|
||||
|
||||
let uniqueId = this._generateUniquePanelID();
|
||||
this.tabpanels.children[0].id = uniqueId;
|
||||
tab.linkedPanel = uniqueId;
|
||||
this._selectedTab = tab;
|
||||
this._selectedBrowser = browser;
|
||||
tab.permanentKey = browser.permanentKey;
|
||||
tab._tPos = 0;
|
||||
tab._fullyOpen = true;
|
||||
tab.linkedBrowser = browser;
|
||||
this._tabForBrowser.set(browser, tab);
|
||||
|
||||
this._appendStatusPanel();
|
||||
|
||||
this.initialBrowser = browser;
|
||||
|
||||
let autoScrollPopup = browser._createAutoScrollPopup();
|
||||
autoScrollPopup.id = "autoscroller";
|
||||
document.getElementById("mainPopupSet").appendChild(autoScrollPopup);
|
||||
browser.setAttribute("autoscrollpopup", autoScrollPopup.id);
|
||||
this._autoScrollPopup = autoScrollPopup;
|
||||
|
||||
// Hook the browser up with a progress listener.
|
||||
let tabListener = new TabProgressListener(tab, browser, true, false);
|
||||
let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
|
||||
|
@ -1014,7 +1004,6 @@ window._gBrowser = {
|
|||
}
|
||||
});
|
||||
newTab.dispatchEvent(event);
|
||||
Services.telemetry.recordEvent("savant", "tab", "select", null, { subcategory: "frame" });
|
||||
|
||||
this._tabAttrModified(oldTab, ["selected"]);
|
||||
this._tabAttrModified(newTab, ["selected"]);
|
||||
|
@ -1812,8 +1801,17 @@ window._gBrowser = {
|
|||
let b = document.createXULElement("browser");
|
||||
b.permanentKey = {};
|
||||
|
||||
for (let attribute in this._defaultBrowserAttributes) {
|
||||
b.setAttribute(attribute, this._defaultBrowserAttributes[attribute]);
|
||||
const defaultBrowserAttributes = {
|
||||
contextmenu: "contentAreaContextMenu",
|
||||
datetimepicker: "DateTimePickerPanel",
|
||||
message: "true",
|
||||
messagemanagergroup: "browsers",
|
||||
selectmenulist: "ContentSelectDropdown",
|
||||
tooltip: "aHTMLTooltip",
|
||||
type: "content",
|
||||
};
|
||||
for (let attribute in defaultBrowserAttributes) {
|
||||
b.setAttribute(attribute, defaultBrowserAttributes[attribute]);
|
||||
}
|
||||
|
||||
if (userContextId) {
|
||||
|
@ -1843,6 +1841,10 @@ window._gBrowser = {
|
|||
if (!isPreloadBrowser) {
|
||||
b.setAttribute("autocompletepopup", "PopupAutoComplete");
|
||||
}
|
||||
if (this._autoScrollPopup) {
|
||||
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This attribute is meant to describe if the browser is the
|
||||
|
@ -1889,13 +1891,17 @@ window._gBrowser = {
|
|||
stack.appendChild(b);
|
||||
stack.setAttribute("flex", "1");
|
||||
|
||||
// Create the browserContainer
|
||||
// We set large flex on both containers to allow the devtools toolbox to
|
||||
// set a flex attribute. We don't want the toolbox to actually take up free
|
||||
// space, but we do want it to collapse when the window shrinks, and with
|
||||
// flex=0 it can't. When the toolbox is on the bottom it's a sibling of
|
||||
// browserSidebarContainer, and when it's on the side it's a sibling of
|
||||
// browserContainer.
|
||||
let browserContainer = document.createXULElement("vbox");
|
||||
browserContainer.className = "browserContainer";
|
||||
browserContainer.appendChild(stack);
|
||||
browserContainer.setAttribute("flex", "10000");
|
||||
|
||||
// Create the sidebar container
|
||||
let browserSidebarContainer = document.createXULElement("hbox");
|
||||
browserSidebarContainer.className = "browserSidebarContainer";
|
||||
browserSidebarContainer.appendChild(browserContainer);
|
||||
|
@ -2472,7 +2478,6 @@ window._gBrowser = {
|
|||
// even if the event listener opens or closes tabs.
|
||||
let evt = new CustomEvent("TabOpen", { bubbles: true, detail: eventDetail || {} });
|
||||
t.dispatchEvent(evt);
|
||||
Services.telemetry.recordEvent("savant", "tab", "open", null, { subcategory: "frame" });
|
||||
|
||||
if (!usingPreloadedContent && originPrincipal && aURI) {
|
||||
let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
|
||||
|
@ -2886,7 +2891,6 @@ window._gBrowser = {
|
|||
// inspect the tab that's about to close.
|
||||
var evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: aAdoptedByTab } });
|
||||
aTab.dispatchEvent(evt);
|
||||
Services.telemetry.recordEvent("savant", "tab", "close", null, { subcategory: "frame" });
|
||||
|
||||
if (this.tabs.length == 2) {
|
||||
// We're closing one of our two open tabs, inform the other tab that its
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="context-fill" d="M14.5 8c-.971 0-1 1-1.75 1a.765.765 0 0 1-.75-.75V5a1 1 0 0 0-1-1H7.75A.765.765 0 0 1 7 3.25c0-.75 1-.779 1-1.75C8 .635 7.1 0 6 0S4 .635 4 1.5c0 .971 1 1 1 1.75a.765.765 0 0 1-.75.75H1a1 1 0 0 0-1 1v2.25A.765.765 0 0 0 .75 8c.75 0 .779-1 1.75-1C3.365 7 4 7.9 4 9s-.635 2-1.5 2c-.971 0-1-1-1.75-1a.765.765 0 0 0-.75.75V15a1 1 0 0 0 1 1h3.25a.765.765 0 0 0 .75-.75c0-.75-1-.779-1-1.75 0-.865.9-1.5 2-1.5s2 .635 2 1.5c0 .971-1 1-1 1.75a.765.765 0 0 0 .75.75H11a1 1 0 0 0 1-1v-3.25a.765.765 0 0 1 .75-.75c.75 0 .779 1 1.75 1 .865 0 1.5-.9 1.5-2s-.635-2-1.5-2z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.5 8c-.971 0-1 1-1.75 1a.765.765 0 0 1-.75-.75V5a1 1 0 0 0-1-1H7.75A.765.765 0 0 1 7 3.25c0-.75 1-.779 1-1.75C8 .635 7.1 0 6 0S4 .635 4 1.5c0 .971 1 1 1 1.75a.765.765 0 0 1-.75.75H1a1 1 0 0 0-1 1v2.25A.765.765 0 0 0 .75 8c.75 0 .779-1 1.75-1C3.365 7 4 7.9 4 9s-.635 2-1.5 2c-.971 0-1-1-1.75-1a.765.765 0 0 0-.75.75V15a1 1 0 0 0 1 1h3.25a.765.765 0 0 0 .75-.75c0-.75-1-.779-1-1.75 0-.865.9-1.5 2-1.5s2 .635 2 1.5c0 .971-1 1-1 1.75a.765.765 0 0 0 .75.75H11a1 1 0 0 0 1-1v-3.25a.765.765 0 0 1 .75-.75c.75 0 .779 1 1.75 1 .865 0 1.5-.9 1.5-2s-.635-2-1.5-2z"/></svg>
|
До Ширина: | Высота: | Размер: 653 B После Ширина: | Высота: | Размер: 689 B |
|
@ -0,0 +1,247 @@
|
|||
/* 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 POPUP_NOTIFICATION_ID = "contextual-feature-recommendation";
|
||||
|
||||
const DELAY_BEFORE_EXPAND_MS = 1000;
|
||||
const DURATION_OF_EXPAND_MS = 5000;
|
||||
|
||||
/**
|
||||
* A WeakMap from browsers to {host, recommendation} pairs. Recommendations are
|
||||
* defined in the ExtensionDoorhanger.schema.json.
|
||||
*
|
||||
* A recommendation is specific to a browser and host and is active until the
|
||||
* given browser is closed or the user navigates (within that browser) away from
|
||||
* the host.
|
||||
*/
|
||||
const RecommendationMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* A WeakMap from windows to their CFR PageAction.
|
||||
*/
|
||||
const PageActionMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* We need one PageAction for each window
|
||||
*/
|
||||
class PageAction {
|
||||
constructor(win, dispatchToASRouter) {
|
||||
this.window = win;
|
||||
this.urlbar = win.document.getElementById("urlbar");
|
||||
this.container = win.document.getElementById("contextual-feature-recommendation");
|
||||
this.button = win.document.getElementById("cfr-button");
|
||||
this.label = win.document.getElementById("cfr-label");
|
||||
|
||||
this._dispatchToASRouter = dispatchToASRouter;
|
||||
this._popupStateChange = this._popupStateChange.bind(this);
|
||||
this._collapse = this._collapse.bind(this);
|
||||
this._handleClick = this._handleClick.bind(this);
|
||||
|
||||
// Saved timeout IDs for scheduled state changes, so they can be cancelled
|
||||
this.stateTransitionTimeoutIDs = [];
|
||||
|
||||
this.container.onclick = this._handleClick;
|
||||
}
|
||||
|
||||
async show(notificationText, shouldExpand = false) {
|
||||
this.container.hidden = false;
|
||||
|
||||
this.label.value = notificationText;
|
||||
|
||||
// Wait for layout to flush to avoid a synchronous reflow then calculate the
|
||||
// label width. We can safely get the width even though the recommendation is
|
||||
// collapsed; the label itself remains full width (with its overflow hidden)
|
||||
await this.window.promiseDocumentFlushed;
|
||||
const [{width}] = this.label.getClientRects();
|
||||
this.urlbar.style.setProperty("--cfr-label-width", `${width}px`);
|
||||
|
||||
if (shouldExpand) {
|
||||
this._clearScheduledStateChanges();
|
||||
|
||||
// After one second, expand
|
||||
this._expand(DELAY_BEFORE_EXPAND_MS);
|
||||
|
||||
// Five seconds later, collapse again
|
||||
this._collapse(DELAY_BEFORE_EXPAND_MS + DURATION_OF_EXPAND_MS);
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.container.hidden = true;
|
||||
this._clearScheduledStateChanges();
|
||||
this.urlbar.removeAttribute("cfr-recommendation-state");
|
||||
}
|
||||
|
||||
_expand(delay = 0) {
|
||||
if (!delay) {
|
||||
// Non-delayed state change overrides any scheduled state changes
|
||||
this._clearScheduledStateChanges();
|
||||
this.urlbar.setAttribute("cfr-recommendation-state", "expanded");
|
||||
} else {
|
||||
this.stateTransitionTimeoutIDs.push(this.window.setTimeout(() => {
|
||||
this.urlbar.setAttribute("cfr-recommendation-state", "expanded");
|
||||
}, delay));
|
||||
}
|
||||
}
|
||||
|
||||
_collapse(delay = 0) {
|
||||
if (!delay) {
|
||||
// Non-delayed state change overrides any scheduled state changes
|
||||
this._clearScheduledStateChanges();
|
||||
if (this.urlbar.getAttribute("cfr-recommendation-state") === "expanded") {
|
||||
this.urlbar.setAttribute("cfr-recommendation-state", "collapsed");
|
||||
}
|
||||
} else {
|
||||
this.stateTransitionTimeoutIDs.push(this.window.setTimeout(() => {
|
||||
if (this.urlbar.getAttribute("cfr-recommendation-state") === "expanded") {
|
||||
this.urlbar.setAttribute("cfr-recommendation-state", "collapsed");
|
||||
}
|
||||
}, delay));
|
||||
}
|
||||
}
|
||||
|
||||
_clearScheduledStateChanges() {
|
||||
while (this.stateTransitionTimeoutIDs.length > 0) {
|
||||
// clearTimeout is safe even with invalid/expired IDs
|
||||
this.window.clearTimeout(this.stateTransitionTimeoutIDs.pop());
|
||||
}
|
||||
}
|
||||
|
||||
// This is called when the popup closes as a result of interaction _outside_
|
||||
// the popup, e.g. by hitting <esc>
|
||||
_popupStateChange(state) {
|
||||
if (["dismissed", "removed"].includes(state)) {
|
||||
this._collapse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to a user click on the recommendation by showing a doorhanger/
|
||||
* popup notification
|
||||
*/
|
||||
_handleClick(event) {
|
||||
const browser = this.window.gBrowser.selectedBrowser;
|
||||
if (!RecommendationMap.has(browser)) {
|
||||
// There's no recommendation for this browser, so the user shouldn't have
|
||||
// been able to click
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
const {content} = RecommendationMap.get(browser);
|
||||
|
||||
// The recommendation should remain either collapsed or expanded while the
|
||||
// doorhanger is showing
|
||||
this._clearScheduledStateChanges();
|
||||
|
||||
// A hacky way of setting the popup anchor outside the usual url bar icon box
|
||||
// See https://searchfox.org/mozilla-central/rev/847b64cc28b74b44c379f9bff4f415b97da1c6d7/toolkit/modules/PopupNotifications.jsm#42
|
||||
browser.cfrpopupnotificationanchor = this.container;
|
||||
|
||||
const {primary, secondary} = content.buttons;
|
||||
|
||||
const mainAction = {
|
||||
label: primary.label,
|
||||
accessKey: primary.accessKey,
|
||||
callback: () => this._dispatchToASRouter(primary.action)
|
||||
};
|
||||
|
||||
const secondaryActions = [{
|
||||
label: secondary.label,
|
||||
accessKey: secondary.accessKey,
|
||||
callback: this._collapse
|
||||
}];
|
||||
|
||||
const options = {
|
||||
popupIconURL: content.addon.icon,
|
||||
hideClose: true,
|
||||
eventCallback: this._popupStateChange
|
||||
};
|
||||
|
||||
this.window.PopupNotifications.show(
|
||||
browser,
|
||||
POPUP_NOTIFICATION_ID,
|
||||
content.text,
|
||||
"cfr",
|
||||
mainAction,
|
||||
secondaryActions,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isHostMatch(browser, host) {
|
||||
return (browser.documentURI.scheme.startsWith("http") &&
|
||||
browser.documentURI.host === host);
|
||||
}
|
||||
|
||||
const CFRPageActions = {
|
||||
// For testing purposes
|
||||
RecommendationMap,
|
||||
PageActionMap,
|
||||
|
||||
/**
|
||||
* To be called from browser.js on a location change, passing in the browser
|
||||
* that's been updated
|
||||
*/
|
||||
updatePageActions(browser) {
|
||||
const win = browser.ownerGlobal;
|
||||
const pageAction = PageActionMap.get(win);
|
||||
if (!pageAction || browser !== win.gBrowser.selectedBrowser) {
|
||||
return;
|
||||
}
|
||||
if (RecommendationMap.has(browser)) {
|
||||
const {host, content} = RecommendationMap.get(browser);
|
||||
if (isHostMatch(browser, host)) {
|
||||
// The browser has a recommendation specified with this host, so show
|
||||
// the page action
|
||||
pageAction.show(content.notification_text);
|
||||
} else {
|
||||
// The user has navigated away from the specified host in the given
|
||||
// browser, so the recommendation is no longer valid and should be removed
|
||||
RecommendationMap.delete(browser);
|
||||
pageAction.hide();
|
||||
}
|
||||
} else {
|
||||
// There's no recommendation specified for this browser, so hide the page action
|
||||
pageAction.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a recommendation specific to the given browser and host.
|
||||
* @param browser The browser for the recommendation
|
||||
* @param host The host for the recommendation
|
||||
* @param recommendation The recommendation to show
|
||||
* @param dispatchToASRouter A function to dispatch resulting actions to
|
||||
* @param force Force the recommendation to appear if the host doesn't match
|
||||
* @return Did adding the recommendation succeed?
|
||||
*/
|
||||
async addRecommendation(browser, host, recommendation, dispatchToASRouter, force = false) {
|
||||
const win = browser.ownerGlobal;
|
||||
if (browser !== win.gBrowser.selectedBrowser || !(force || isHostMatch(browser, host))) {
|
||||
return false;
|
||||
}
|
||||
const {id, content} = recommendation;
|
||||
RecommendationMap.set(browser, {id, host, content});
|
||||
if (!PageActionMap.has(win)) {
|
||||
PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
|
||||
}
|
||||
await PageActionMap.get(win).show(recommendation.content.notification_text, true);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all recommendations and hide all PageActions
|
||||
*/
|
||||
clearRecommendations() {
|
||||
for (const [win, pageAction] of PageActionMap) {
|
||||
pageAction.hide();
|
||||
PageActionMap.delete(win);
|
||||
}
|
||||
RecommendationMap.clear();
|
||||
}
|
||||
};
|
||||
|
||||
const EXPORTED_SYMBOLS = ["CFRPageActions"];
|
|
@ -429,7 +429,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
RemoteSettings: "resource://services-settings/remote-settings.js",
|
||||
SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
|
||||
Sanitizer: "resource:///modules/Sanitizer.jsm",
|
||||
SavantShieldStudy: "resource:///modules/SavantShieldStudy.jsm",
|
||||
SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
|
||||
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
|
||||
ShellService: "resource:///modules/ShellService.jsm",
|
||||
|
@ -525,9 +524,6 @@ const listeners = {
|
|||
"RemoteLogins:autoCompleteLogins": ["LoginManagerParent"],
|
||||
"RemoteLogins:removeLogin": ["LoginManagerParent"],
|
||||
"RemoteLogins:insecureLoginFormPresent": ["LoginManagerParent"],
|
||||
// For Savant Shield study, bug 1465685. Study on desktop only.
|
||||
"LoginStats:LoginFillSuccessful": ["LoginManagerParent"],
|
||||
"LoginStats:LoginEncountered": ["LoginManagerParent"],
|
||||
// PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
|
||||
"WCCR:registerProtocolHandler": ["Feeds"],
|
||||
"rtcpeer:CancelRequest": ["webrtcUI"],
|
||||
|
@ -1400,8 +1396,6 @@ BrowserGlue.prototype = {
|
|||
}
|
||||
|
||||
Normandy.uninit();
|
||||
|
||||
SavantShieldStudy.uninit();
|
||||
},
|
||||
|
||||
// All initial windows have opened.
|
||||
|
@ -1558,10 +1552,6 @@ BrowserGlue.prototype = {
|
|||
Services.tm.idleDispatchToMainThread(() => {
|
||||
Blocklist.loadBlocklistAsync();
|
||||
});
|
||||
|
||||
Services.tm.idleDispatchToMainThread(() => {
|
||||
SavantShieldStudy.init();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,18 +7,29 @@
|
|||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
ChromeUtils.import("resource://services-sync/SyncedTabs.jsm");
|
||||
ChromeUtils.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js");
|
||||
ChromeUtils.import("resource:///actors/LightweightThemeChild.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
var syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts});
|
||||
|
||||
let themeListener;
|
||||
|
||||
let onLoaded = () => {
|
||||
themeListener = new LightweightThemeChild({
|
||||
content: window,
|
||||
chromeOuterWindowID: window.top.windowUtils.outerWindowID,
|
||||
docShell: window.docShell,
|
||||
});
|
||||
syncedTabsDeckComponent.init();
|
||||
document.getElementById("template-container").appendChild(syncedTabsDeckComponent.container);
|
||||
};
|
||||
|
||||
let onUnloaded = () => {
|
||||
if (themeListener) {
|
||||
themeListener.cleanup();
|
||||
}
|
||||
removeEventListener("DOMContentLoaded", onLoaded);
|
||||
removeEventListener("unload", onUnloaded);
|
||||
syncedTabsDeckComponent.uninit();
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<head>
|
||||
<script src="chrome://browser/content/syncedtabs/sidebar.js" type="application/javascript"></script>
|
||||
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/syncedtabs/sidebar.css"/>
|
||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/"/>
|
||||
|
|
|
@ -75,7 +75,7 @@ var Translation = {
|
|||
openProviderAttribution() {
|
||||
let attribution = this.supportedEngines[this.translationEngine];
|
||||
ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
|
||||
BrowserWindowTracker.getTopWindow().openUILinkIn(attribution, "tab");
|
||||
BrowserWindowTracker.getTopWindow().openWebLinkIn(attribution, "tab");
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -366,10 +366,6 @@ let urlbarListener = {
|
|||
Services.telemetry
|
||||
.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE")
|
||||
.add(actionType, idx);
|
||||
if (actionType === "bookmark" || actionType === "history") {
|
||||
Services.telemetry.recordEvent("savant", "follow_urlbar_link", actionType, null,
|
||||
{ subcategory: "navigation" });
|
||||
}
|
||||
} else {
|
||||
Cu.reportError("Unknown FX_URLBAR_SELECTED_RESULT_TYPE type: " +
|
||||
actionType);
|
||||
|
@ -503,9 +499,6 @@ let BrowserUsageTelemetry = {
|
|||
scalarKey, 1);
|
||||
Services.telemetry.recordEvent("navigation", "search", source, action,
|
||||
{ engine: getSearchEngineId(engine) });
|
||||
Services.telemetry.recordEvent("savant", "search", source, action,
|
||||
{ subcategory: "navigation",
|
||||
engine: getSearchEngineId(engine) });
|
||||
},
|
||||
|
||||
_handleSearchAction(engine, source, details) {
|
||||
|
|
|
@ -1,530 +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/. */
|
||||
|
||||
/* eslint semi: error */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SavantShieldStudy"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AddonManager: "resource://gre/modules/AddonManager.jsm",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm"
|
||||
});
|
||||
|
||||
// See LOG_LEVELS in Console.jsm. Examples: "all", "info", "warn", & "error".
|
||||
const PREF_LOG_LEVEL = "shield.savant.loglevel";
|
||||
|
||||
// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
|
||||
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
|
||||
let consoleOptions = {
|
||||
maxLogLevelPref: PREF_LOG_LEVEL,
|
||||
prefix: "SavantShieldStudy",
|
||||
};
|
||||
return new ConsoleAPI(consoleOptions);
|
||||
});
|
||||
|
||||
class SavantShieldStudyClass {
|
||||
constructor() {
|
||||
this.STUDY_PREF = "shield.savant.enabled";
|
||||
this.STUDY_TELEMETRY_CATEGORY = "savant";
|
||||
this.ALWAYS_PRIVATE_BROWSING_PREF = "browser.privatebrowsing.autostart";
|
||||
this.STUDY_DURATION_OVERRIDE_PREF = "shield.savant.duration_override";
|
||||
this.STUDY_EXPIRATION_DATE_PREF = "shield.savant.expiration_date";
|
||||
// ms = 'x' weeks * 7 days/week * 24 hours/day * 60 minutes/hour
|
||||
// * 60 seconds/minute * 1000 milliseconds/second
|
||||
this.DEFAULT_STUDY_DURATION_MS = 4 * 7 * 24 * 60 * 60 * 1000;
|
||||
// If on startupStudy(), user is ineligible or study has expired,
|
||||
// no probe listeners from this module have been added yet
|
||||
this.shouldRemoveListeners = true;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.telemetryEvents = new TelemetryEvents(this.STUDY_TELEMETRY_CATEGORY);
|
||||
this.addonListener = new AddonListener(this.STUDY_TELEMETRY_CATEGORY);
|
||||
this.bookmarkObserver = new BookmarkObserver(this.STUDY_TELEMETRY_CATEGORY);
|
||||
this.menuListener = new MenuListener(this.STUDY_TELEMETRY_CATEGORY);
|
||||
|
||||
// check the pref in case Normandy flipped it on before we could add the pref listener
|
||||
this.shouldCollect = Services.prefs.getBoolPref(this.STUDY_PREF);
|
||||
if (this.shouldCollect) {
|
||||
this.startupStudy();
|
||||
}
|
||||
Services.prefs.addObserver(this.STUDY_PREF, this);
|
||||
}
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (topic === "nsPref:changed" && data === this.STUDY_PREF) {
|
||||
// toggle state of the pref
|
||||
this.shouldCollect = !this.shouldCollect;
|
||||
if (this.shouldCollect) {
|
||||
this.startupStudy();
|
||||
} else {
|
||||
// The pref has been turned off
|
||||
this.endStudy("study_disable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startupStudy() {
|
||||
// enable before any possible calls to endStudy, since it sends an 'end_study' event
|
||||
this.telemetryEvents.enableCollection();
|
||||
|
||||
if (!this.isEligible()) {
|
||||
this.shouldRemoveListeners = false;
|
||||
this.endStudy("ineligible");
|
||||
return;
|
||||
}
|
||||
|
||||
this.initStudyDuration();
|
||||
|
||||
if (this.isStudyExpired()) {
|
||||
log.debug("Study expired in between this and the previous session.");
|
||||
this.shouldRemoveListeners = false;
|
||||
this.endStudy("expired");
|
||||
}
|
||||
|
||||
this.addonListener.init();
|
||||
this.bookmarkObserver.init();
|
||||
this.menuListener.init();
|
||||
}
|
||||
|
||||
isEligible() {
|
||||
const isAlwaysPrivateBrowsing = Services.prefs.getBoolPref(this.ALWAYS_PRIVATE_BROWSING_PREF);
|
||||
if (isAlwaysPrivateBrowsing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
initStudyDuration() {
|
||||
if (Services.prefs.getStringPref(this.STUDY_EXPIRATION_DATE_PREF, "")) {
|
||||
return;
|
||||
}
|
||||
Services.prefs.setStringPref(
|
||||
this.STUDY_EXPIRATION_DATE_PREF,
|
||||
this.getExpirationDateString()
|
||||
);
|
||||
}
|
||||
|
||||
getDurationFromPref() {
|
||||
return Services.prefs.getIntPref(this.STUDY_DURATION_OVERRIDE_PREF, 0);
|
||||
}
|
||||
|
||||
getExpirationDateString() {
|
||||
const now = Date.now();
|
||||
const studyDurationInMs =
|
||||
this.getDurationFromPref()
|
||||
|| this.DEFAULT_STUDY_DURATION_MS;
|
||||
const expirationDateInt = now + studyDurationInMs;
|
||||
return new Date(expirationDateInt).toISOString();
|
||||
}
|
||||
|
||||
isStudyExpired() {
|
||||
const expirationDateInt =
|
||||
Date.parse(Services.prefs.getStringPref(
|
||||
this.STUDY_EXPIRATION_DATE_PREF,
|
||||
this.getExpirationDateString()
|
||||
));
|
||||
|
||||
if (isNaN(expirationDateInt)) {
|
||||
log.error(
|
||||
`The value for the preference ${this.STUDY_EXPIRATION_DATE_PREF} is invalid.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Date.now() > expirationDateInt) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
endStudy(reason) {
|
||||
log.debug(`Ending the study due to reason: ${ reason }`);
|
||||
const isStudyEnding = true;
|
||||
Services.telemetry.recordEvent(this.STUDY_TELEMETRY_CATEGORY, "end_study", reason, null,
|
||||
{ subcategory: "shield" });
|
||||
this.telemetryEvents.disableCollection();
|
||||
this.uninit(isStudyEnding);
|
||||
// These prefs needs to persist between restarts, so only reset on endStudy
|
||||
Services.prefs.clearUserPref(this.STUDY_PREF);
|
||||
Services.prefs.clearUserPref(this.STUDY_EXPIRATION_DATE_PREF);
|
||||
}
|
||||
|
||||
// Called on every Firefox shutdown and endStudy
|
||||
uninit(isStudyEnding = false) {
|
||||
// if just shutting down, check for expiration, so the endStudy event can
|
||||
// be sent along with this session's main ping.
|
||||
if (!isStudyEnding && this.isStudyExpired()) {
|
||||
log.debug("Study expired during this session.");
|
||||
this.endStudy("expired");
|
||||
return;
|
||||
}
|
||||
|
||||
this.addonListener.uninit();
|
||||
this.bookmarkObserver.uninit();
|
||||
this.menuListener.uninit();
|
||||
|
||||
Services.prefs.removeObserver(this.ALWAYS_PRIVATE_BROWSING_PREF, this);
|
||||
Services.prefs.removeObserver(this.STUDY_PREF, this);
|
||||
Services.prefs.removeObserver(this.STUDY_DURATION_OVERRIDE_PREF, this);
|
||||
Services.prefs.clearUserPref(PREF_LOG_LEVEL);
|
||||
Services.prefs.clearUserPref(this.STUDY_DURATION_OVERRIDE_PREF);
|
||||
}
|
||||
}
|
||||
|
||||
// References:
|
||||
// - https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/normandy/lib/TelemetryEvents.jsm
|
||||
// - https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/normandy/lib/PreferenceExperiments.jsm#l357
|
||||
class TelemetryEvents {
|
||||
constructor(studyCategory) {
|
||||
this.STUDY_TELEMETRY_CATEGORY = studyCategory;
|
||||
}
|
||||
|
||||
enableCollection() {
|
||||
log.debug("Study has been enabled; turning ON data collection.");
|
||||
Services.telemetry.setEventRecordingEnabled(this.STUDY_TELEMETRY_CATEGORY, true);
|
||||
}
|
||||
|
||||
disableCollection() {
|
||||
log.debug("Study has been disabled; turning OFF data collection.");
|
||||
Services.telemetry.setEventRecordingEnabled(this.STUDY_TELEMETRY_CATEGORY, false);
|
||||
}
|
||||
}
|
||||
|
||||
class AddonListener {
|
||||
constructor(studyCategory) {
|
||||
this.STUDY_TELEMETRY_CATEGORY = studyCategory;
|
||||
this.METHOD = "addon";
|
||||
this.EXTRA_SUBCATEGORY = "customize";
|
||||
}
|
||||
|
||||
init() {
|
||||
this.listener = {
|
||||
onInstalling: (addon, needsRestart) => {
|
||||
const addon_id = addon.id;
|
||||
this.recordEvent("install_start", addon_id);
|
||||
},
|
||||
|
||||
onInstalled: (addon) => {
|
||||
const addon_id = addon.id;
|
||||
this.recordEvent("install_finish", addon_id);
|
||||
},
|
||||
|
||||
onEnabled: (addon) => {
|
||||
const addon_id = addon.id;
|
||||
this.recordEvent("enable", addon_id);
|
||||
},
|
||||
|
||||
onDisabled: (addon) => {
|
||||
const addon_id = addon.id;
|
||||
this.recordEvent("disable", addon_id);
|
||||
},
|
||||
|
||||
onUninstalling: (addon, needsRestart) => {
|
||||
const addon_id = addon.id;
|
||||
this.recordEvent("remove_start", addon_id);
|
||||
},
|
||||
|
||||
onUninstalled: (addon) => {
|
||||
const addon_id = addon.id;
|
||||
this.recordEvent("remove_finish", addon_id);
|
||||
}
|
||||
};
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
AddonManager.addAddonListener(this.listener);
|
||||
}
|
||||
|
||||
recordEvent(event, addon_id) {
|
||||
log.debug(`Addon ID: ${addon_id}; event: ${ event }`);
|
||||
Services.telemetry.recordEvent(this.STUDY_TELEMETRY_CATEGORY,
|
||||
this.METHOD,
|
||||
event,
|
||||
addon_id,
|
||||
{ subcategory: this.EXTRA_SUBCATEGORY });
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
AddonManager.removeAddonListener(this.listener);
|
||||
}
|
||||
|
||||
uninit() {
|
||||
if (SavantShieldStudy.shouldRemoveListeners) {
|
||||
this.removeListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BookmarkObserver {
|
||||
constructor(studyCategory) {
|
||||
this.STUDY_TELEMETRY_CATEGORY = studyCategory;
|
||||
// there are two probes: bookmark and follow_bookmark
|
||||
this.METHOD_1 = "bookmark";
|
||||
this.EXTRA_SUBCATEGORY_1 = "feature";
|
||||
this.METHOD_2 = "follow_bookmark";
|
||||
this.EXTRA_SUBCATEGORY_2 = "navigation";
|
||||
this.TYPE_BOOKMARK = Ci.nsINavBookmarksService.TYPE_BOOKMARK;
|
||||
// Ignore "fake" bookmarks created for bookmark tags
|
||||
this.skipTags = true;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.addObservers();
|
||||
}
|
||||
|
||||
addObservers() {
|
||||
PlacesUtils.bookmarks.addObserver(this);
|
||||
}
|
||||
|
||||
onItemAdded(itemID, parentID, index, itemType, uri, title, dateAdded, guid, parentGUID, source) {
|
||||
this.handleItemAddRemove(itemType, uri, source, "save");
|
||||
}
|
||||
|
||||
onItemRemoved(itemID, parentID, index, itemType, uri, guid, parentGUID, source) {
|
||||
this.handleItemAddRemove(itemType, uri, source, "remove");
|
||||
}
|
||||
|
||||
handleItemAddRemove(itemType, uri, source, event) {
|
||||
/*
|
||||
* "place:query" uris are used to create containers like Most Visited or
|
||||
* Recently Bookmarked. These are added as default bookmarks.
|
||||
*/
|
||||
if (itemType === this.TYPE_BOOKMARK && !uri.schemeIs("place")
|
||||
&& source === PlacesUtils.bookmarks.SOURCE_DEFAULT) {
|
||||
const isBookmarkProbe = true;
|
||||
this.recordEvent(event, isBookmarkProbe);
|
||||
}
|
||||
}
|
||||
|
||||
// This observer is only fired for TYPE_BOOKMARK items.
|
||||
onItemVisited(itemID, visitID, time, transitionType, uri, parentID, guid, parentGUID) {
|
||||
const isBookmarkProbe = false;
|
||||
this.recordEvent("open", isBookmarkProbe);
|
||||
}
|
||||
|
||||
recordEvent(event, isBookmarkProbe) {
|
||||
const method = isBookmarkProbe ? this.METHOD_1 : this.METHOD_2;
|
||||
const subcategory = isBookmarkProbe ? this.EXTRA_SUBCATEGORY_1 : this.EXTRA_SUBCATEGORY_2;
|
||||
Services.telemetry.recordEvent(this.STUDY_TELEMETRY_CATEGORY, method, event, null,
|
||||
{
|
||||
subcategory
|
||||
});
|
||||
}
|
||||
|
||||
removeObservers() {
|
||||
PlacesUtils.bookmarks.removeObserver(this);
|
||||
}
|
||||
|
||||
uninit() {
|
||||
if (SavantShieldStudy.shouldRemoveListeners) {
|
||||
this.removeObservers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MenuListener {
|
||||
constructor(studyCategory) {
|
||||
this.STUDY_TELEMETRY_CATEGORY = studyCategory;
|
||||
this.NAVIGATOR_TOOLBOX_ID = "navigator-toolbox";
|
||||
this.OVERFLOW_PANEL_ID = "widget-overflow";
|
||||
this.LIBRARY_PANELVIEW_ID = "appMenu-libraryView";
|
||||
this.HAMBURGER_PANEL_ID = "appMenu-popup";
|
||||
this.DOTDOTDOT_PANEL_ID = "pageActionPanel";
|
||||
this.windowWatcher = new WindowWatcher();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.windowWatcher.init(this.loadIntoWindow.bind(this),
|
||||
this.unloadFromWindow.bind(this),
|
||||
this.onWindowError.bind(this));
|
||||
}
|
||||
|
||||
loadIntoWindow(win) {
|
||||
this.addListeners(win);
|
||||
}
|
||||
|
||||
unloadFromWindow(win) {
|
||||
this.removeListeners(win);
|
||||
}
|
||||
|
||||
onWindowError(msg) {
|
||||
log.error(msg);
|
||||
}
|
||||
|
||||
addListeners(win) {
|
||||
const doc = win.document;
|
||||
const navToolbox = doc.getElementById(this.NAVIGATOR_TOOLBOX_ID);
|
||||
const overflowPanel = doc.getElementById(this.OVERFLOW_PANEL_ID);
|
||||
const hamburgerPanel = doc.getElementById(this.HAMBURGER_PANEL_ID);
|
||||
const dotdotdotPanel = doc.getElementById(this.DOTDOTDOT_PANEL_ID);
|
||||
|
||||
/*
|
||||
* the library menu "ViewShowing" event bubbles up on the navToolbox in its
|
||||
* default location. A separate listener is needed if it is moved to the
|
||||
* overflow panel via Hamburger > Customize
|
||||
*/
|
||||
navToolbox.addEventListener("ViewShowing", this);
|
||||
overflowPanel.addEventListener("ViewShowing", this);
|
||||
hamburgerPanel.addEventListener("popupshown", this);
|
||||
dotdotdotPanel.addEventListener("popupshown", this);
|
||||
}
|
||||
|
||||
handleEvent(evt) {
|
||||
switch (evt.type) {
|
||||
case "ViewShowing":
|
||||
if (evt.target.id === this.LIBRARY_PANELVIEW_ID) {
|
||||
log.debug("Library panel opened.");
|
||||
this.recordEvent("library_menu");
|
||||
}
|
||||
break;
|
||||
case "popupshown":
|
||||
switch (evt.target.id) {
|
||||
case this.HAMBURGER_PANEL_ID:
|
||||
log.debug("Hamburger panel opened.");
|
||||
this.recordEvent("hamburger_menu");
|
||||
break;
|
||||
case this.DOTDOTDOT_PANEL_ID:
|
||||
log.debug("Dotdotdot panel opened.");
|
||||
this.recordEvent("dotdotdot_menu");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
recordEvent(method) {
|
||||
Services.telemetry.recordEvent(this.STUDY_TELEMETRY_CATEGORY, method, "open", null,
|
||||
{ subcategory: "menu" });
|
||||
}
|
||||
|
||||
removeListeners(win) {
|
||||
const doc = win.document;
|
||||
const navToolbox = doc.getElementById(this.NAVIGATOR_TOOLBOX_ID);
|
||||
const overflowPanel = doc.getElementById(this.OVERFLOW_PANEL_ID);
|
||||
const hamburgerPanel = doc.getElementById(this.HAMBURGER_PANEL_ID);
|
||||
const dotdotdotPanel = doc.getElementById(this.DOTDOTDOT_PANEL_ID);
|
||||
|
||||
try {
|
||||
navToolbox.removeEventListener("ViewShowing", this);
|
||||
overflowPanel.removeEventListener("ViewShowing", this);
|
||||
hamburgerPanel.removeEventListener("popupshown", this);
|
||||
dotdotdotPanel.removeEventListener("popupshown", this);
|
||||
} catch (err) {
|
||||
// Firefox is shutting down; elements have already been removed.
|
||||
}
|
||||
}
|
||||
|
||||
uninit() {
|
||||
if (SavantShieldStudy.shouldRemoveListeners) {
|
||||
this.windowWatcher.uninit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The WindowWatcher is used to add/remove listeners from MenuListener
|
||||
* to/from all windows.
|
||||
*/
|
||||
class WindowWatcher {
|
||||
constructor() {
|
||||
this._isActive = false;
|
||||
this._loadCallback = null;
|
||||
this._unloadCallback = null;
|
||||
this._errorCallback = null;
|
||||
}
|
||||
|
||||
// It is expected that loadCallback, unloadCallback, and errorCallback are bound
|
||||
// to a `this` value.
|
||||
init(loadCallback, unloadCallback, errorCallback) {
|
||||
if (this._isActive) {
|
||||
errorCallback("Called init, but WindowWatcher was already running");
|
||||
return;
|
||||
}
|
||||
|
||||
this._isActive = true;
|
||||
this._loadCallback = loadCallback;
|
||||
this._unloadCallback = unloadCallback;
|
||||
this._errorCallback = errorCallback;
|
||||
|
||||
// Add loadCallback to existing windows
|
||||
for (const win of Services.wm.getEnumerator("navigator:browser")) {
|
||||
try {
|
||||
this._loadCallback(win);
|
||||
} catch (ex) {
|
||||
this._errorCallback(`WindowWatcher code loading callback failed: ${ ex }`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add loadCallback to future windows
|
||||
// This will call the observe method on WindowWatcher
|
||||
Services.ww.registerNotification(this);
|
||||
}
|
||||
|
||||
uninit() {
|
||||
if (!this._isActive) {
|
||||
this._errorCallback("Called uninit, but WindowWatcher was already uninited");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const win of Services.wm.getEnumerator("navigator:browser")) {
|
||||
try {
|
||||
this._unloadCallback(win);
|
||||
} catch (ex) {
|
||||
this._errorCallback(`WindowWatcher code unloading callback failed: ${ ex }`);
|
||||
}
|
||||
}
|
||||
|
||||
Services.ww.unregisterNotification(this);
|
||||
|
||||
this._loadCallback = null;
|
||||
this._unloadCallback = null;
|
||||
this._errorCallback = null;
|
||||
this._isActive = false;
|
||||
}
|
||||
|
||||
observe(win, topic) {
|
||||
switch (topic) {
|
||||
case "domwindowopened":
|
||||
this._onWindowOpened(win);
|
||||
break;
|
||||
case "domwindowclosed":
|
||||
this._onWindowClosed(win);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_onWindowOpened(win) {
|
||||
win.addEventListener("load", this, { once: true });
|
||||
}
|
||||
|
||||
// only one event type expected: "load"
|
||||
handleEvent(evt) {
|
||||
const win = evt.target.ownerGlobal;
|
||||
|
||||
// make sure we only add window listeners to a DOMWindow (browser.xul)
|
||||
const winType = win.document.documentElement.getAttribute("windowtype");
|
||||
if (winType === "navigator:browser") {
|
||||
this._loadCallback(win);
|
||||
}
|
||||
}
|
||||
|
||||
_onWindowClosed(win) {
|
||||
this._unloadCallback(win);
|
||||
}
|
||||
}
|
||||
|
||||
const SavantShieldStudy = new SavantShieldStudyClass();
|
|
@ -138,7 +138,6 @@ EXTRA_JS_MODULES += [
|
|||
'ReaderParent.jsm',
|
||||
'RemotePrompt.jsm',
|
||||
'Sanitizer.jsm',
|
||||
'SavantShieldStudy.jsm',
|
||||
'SchedulePressure.jsm',
|
||||
'SiteDataManager.jsm',
|
||||
'SitePermissions.jsm',
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
/* These styles are intended to mimic XUL trees and the XUL search box. */
|
||||
|
||||
.content-container {
|
||||
body:not([lwt-sidebar]) .content-container {
|
||||
-moz-appearance: -moz-mac-source-list;
|
||||
-moz-font-smoothing-background-color: -moz-mac-source-list;
|
||||
}
|
||||
|
|
|
@ -871,6 +871,10 @@ panelview .toolbarbutton-1,
|
|||
outline: 0;
|
||||
}
|
||||
|
||||
.subviewbutton[disabled="true"] {
|
||||
color: var(--panel-disabled-color);
|
||||
}
|
||||
|
||||
.subviewbutton > .toolbarbutton-text {
|
||||
padding: 0;
|
||||
padding-inline-start: 24px; /* This is 16px for the icon + 8px for the padding as defined below. */
|
||||
|
|
|
@ -69,7 +69,6 @@ body {
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
outline: none;
|
||||
color: -moz-FieldText;
|
||||
}
|
||||
|
||||
.item.selected > .item-title-container {
|
||||
|
@ -296,3 +295,26 @@ body {
|
|||
.filtered .textbox-search-icons .textbox-search-clear {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Themed sidebars */
|
||||
|
||||
body[lwt-sidebar] {
|
||||
background-color: var(--lwt-sidebar-background-color);
|
||||
color: var(--lwt-sidebar-text-color);
|
||||
}
|
||||
|
||||
body[lwt-sidebar] .item.selected > .item-title-container {
|
||||
background-color: hsla(0,0%,80%,.3);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
body[lwt-sidebar-brighttext] .item.selected > .item-title-container {
|
||||
-moz-appearance: none;
|
||||
background-color: rgba(249,249,250,.1);
|
||||
}
|
||||
|
||||
body[lwt-sidebar-highlight] .item.selected:focus > .item-title-container {
|
||||
-moz-appearance: none;
|
||||
background-color: var(--lwt-sidebar-highlight-background-color);
|
||||
color: var(--lwt-sidebar-highlight-text-color);
|
||||
}
|
||||
|
|
|
@ -8,12 +8,16 @@ external_dirs = []
|
|||
|
||||
DIRS += [
|
||||
'lgpllibs',
|
||||
'prio',
|
||||
'sqlite',
|
||||
]
|
||||
if not CONFIG['MOZ_SYSTEM_JPEG']:
|
||||
external_dirs += ['media/libjpeg']
|
||||
|
||||
if CONFIG['MOZ_LIBPRIO']:
|
||||
DIRS += [
|
||||
'prio',
|
||||
]
|
||||
|
||||
# There's no "native" brotli or woff2 yet, but probably in the future...
|
||||
external_dirs += ['modules/brotli']
|
||||
external_dirs += ['modules/woff2']
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ['/third_party/msgpack']
|
||||
if CONFIG['MOZ_LIBPRIO']:
|
||||
DIRS += ['/third_party/msgpack']
|
||||
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
# 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 += ['../../../third_party/prio']
|
||||
DIRS += ['/third_party/prio']
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
@import "resource://devtools/client/aboutdebugging-new/src/components/App.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/ConnectPage.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/Sidebar.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/SidebarItem.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.css";
|
||||
@import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/SidebarItem.css";
|
||||
|
||||
:root {
|
||||
/* Import css variables from common.css */
|
||||
|
|
|
@ -13,7 +13,7 @@ const { PAGES } = require("../constants");
|
|||
|
||||
const ConnectPage = createFactory(require("./ConnectPage"));
|
||||
const RuntimePage = createFactory(require("./RuntimePage"));
|
||||
const Sidebar = createFactory(require("./Sidebar"));
|
||||
const Sidebar = createFactory(require("./sidebar/Sidebar"));
|
||||
|
||||
class App extends PureComponent {
|
||||
static get propTypes() {
|
||||
|
|
|
@ -142,8 +142,11 @@ class ConnectPage extends PureComponent {
|
|||
{
|
||||
className: "connect-page__network-form",
|
||||
onSubmit: (e) => {
|
||||
addNetworkLocation(this.state.locationInputValue);
|
||||
this.setState({ locationInputValue: "" });
|
||||
const locationInputValue = this.state.locationInputValue;
|
||||
if (locationInputValue) {
|
||||
addNetworkLocation(locationInputValue);
|
||||
this.setState({ locationInputValue: "" });
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
@ -155,9 +158,7 @@ class ConnectPage extends PureComponent {
|
|||
value: this.state.locationInputValue,
|
||||
onChange: (e) => {
|
||||
const locationInputValue = e.target.value;
|
||||
if (locationInputValue) {
|
||||
this.setState({ locationInputValue });
|
||||
}
|
||||
this.setState({ locationInputValue });
|
||||
}
|
||||
}),
|
||||
dom.button({
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
DIRS += [
|
||||
'debugtarget',
|
||||
'sidebar',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
|
@ -14,8 +15,4 @@ DevToolsModules(
|
|||
'RuntimeInfo.css',
|
||||
'RuntimeInfo.js',
|
||||
'RuntimePage.js',
|
||||
'Sidebar.css',
|
||||
'Sidebar.js',
|
||||
'SidebarItem.css',
|
||||
'SidebarItem.js',
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/
|
|||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
const { PAGES } = require("../constants");
|
||||
const { PAGES } = require("../../constants");
|
||||
|
||||
const SidebarItem = createFactory(require("./SidebarItem"));
|
||||
const FIREFOX_ICON = "chrome://devtools/skin/images/aboutdebugging-firefox-logo.svg";
|
|
@ -8,7 +8,7 @@ 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 Actions = require("../actions/index");
|
||||
const Actions = require("../../actions/index");
|
||||
|
||||
/**
|
||||
* This component displays an item of the Sidebar component.
|
|
@ -0,0 +1,10 @@
|
|||
# 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(
|
||||
'Sidebar.css',
|
||||
'Sidebar.js',
|
||||
'SidebarItem.css',
|
||||
'SidebarItem.js',
|
||||
)
|
|
@ -33,6 +33,7 @@ loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer
|
|||
loader.lazyRequireGetter(this, "CommandState", "devtools/shared/gcli/command-state", true);
|
||||
loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
|
||||
loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
|
||||
loader.lazyRequireGetter(this, "getScreenshotFront", "resource://devtools/shared/fronts/screenshot", true);
|
||||
|
||||
const {MultiLocalizationHelper} = require("devtools/shared/l10n");
|
||||
const L10N = new MultiLocalizationHelper(
|
||||
|
@ -106,14 +107,6 @@ Tools.webConsole = {
|
|||
accesskey: l10n("webConsoleCmd.accesskey"),
|
||||
ordinal: 2,
|
||||
url: "chrome://devtools/content/webconsole/index.html",
|
||||
get browserConsoleUsesHTML() {
|
||||
return Services.prefs.getBoolPref("devtools.browserconsole.html");
|
||||
},
|
||||
get browserConsoleURL() {
|
||||
return this.browserConsoleUsesHTML ?
|
||||
"chrome://devtools/content/webconsole/index.html" :
|
||||
"chrome://devtools/content/webconsole/browserconsole.xul";
|
||||
},
|
||||
icon: "chrome://devtools/skin/images/tool-webconsole.svg",
|
||||
label: l10n("ToolboxTabWebconsole.label"),
|
||||
menuLabel: l10n("MenuWebconsole.label"),
|
||||
|
@ -594,16 +587,17 @@ exports.ToolboxButtons = [
|
|||
},
|
||||
{ id: "command-button-screenshot",
|
||||
description: l10n("toolbox.buttons.screenshot"),
|
||||
isTargetSupported: target => target.isLocalTab,
|
||||
onClick(event, toolbox) {
|
||||
isTargetSupported: target => !target.chrome && target.hasActor("screenshot"),
|
||||
async onClick(event, toolbox) {
|
||||
// Special case for screenshot button to check for clipboard preference
|
||||
const clipboardEnabled = Services.prefs
|
||||
.getBoolPref("devtools.screenshot.clipboard.enabled");
|
||||
let args = "--fullpage --file";
|
||||
const args = { fullpage: true, file: true };
|
||||
if (clipboardEnabled) {
|
||||
args += " --clipboard";
|
||||
args.clipboard = true;
|
||||
}
|
||||
CommandUtils.executeOnTarget(toolbox.target, "screenshot " + args);
|
||||
const screenshotFront = getScreenshotFront(toolbox.target);
|
||||
await screenshotFront.captureAndSave(toolbox.win, args);
|
||||
}
|
||||
},
|
||||
{ id: "command-button-rulers",
|
||||
|
|
|
@ -10,12 +10,12 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
|||
const { KeyCodes } = require("devtools/client/shared/keycodes");
|
||||
|
||||
// Milliseconds between auto-increment interval iterations.
|
||||
const AUTOINCREMENT_DELAY = 300;
|
||||
const AUTOINCREMENT_DELAY = 1000;
|
||||
|
||||
class FontPropertyValue extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
allowAutoIncrement: PropTypes.bool,
|
||||
autoIncrement: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
defaultValue: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
|
||||
label: PropTypes.string.isRequired,
|
||||
|
@ -23,7 +23,7 @@ class FontPropertyValue extends PureComponent {
|
|||
max: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
step: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
|
||||
step: PropTypes.number,
|
||||
unit: PropTypes.oneOfType([ PropTypes.string, null ]),
|
||||
unitOptions: PropTypes.array,
|
||||
value: PropTypes.number,
|
||||
|
@ -32,7 +32,9 @@ class FontPropertyValue extends PureComponent {
|
|||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
autoIncrement: false,
|
||||
className: "",
|
||||
step: 1,
|
||||
unitOptions: []
|
||||
};
|
||||
}
|
||||
|
@ -59,6 +61,7 @@ class FontPropertyValue extends PureComponent {
|
|||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onKeyUp = this.onKeyUp.bind(this);
|
||||
this.onMouseDown = this.onMouseDown.bind(this);
|
||||
this.onMouseMove = this.onMouseMove.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
this.onUnitChange = this.onUnitChange.bind(this);
|
||||
this.stopAutoIncrement = this.stopAutoIncrement.bind(this);
|
||||
|
@ -100,7 +103,7 @@ class FontPropertyValue extends PureComponent {
|
|||
/**
|
||||
* Check if the given value is valid according to the constraints of this component.
|
||||
* Ensure it is a number and that it does not go outside the min/max limits, unless
|
||||
* allowed by the `allowAutoIncrement` props flag.
|
||||
* allowed by the `autoIncrement` props flag.
|
||||
*
|
||||
* @param {Number} value
|
||||
* Numeric value
|
||||
|
@ -108,7 +111,7 @@ class FontPropertyValue extends PureComponent {
|
|||
* Whether the value conforms to the components contraints.
|
||||
*/
|
||||
isValueValid(value) {
|
||||
const { allowAutoIncrement, min, max } = this.props;
|
||||
const { autoIncrement, min, max } = this.props;
|
||||
|
||||
if (typeof value !== "number" || isNaN(value)) {
|
||||
return false;
|
||||
|
@ -119,7 +122,7 @@ class FontPropertyValue extends PureComponent {
|
|||
}
|
||||
|
||||
// Ensure it does not exceed maximum value, unless auto-incrementing is permitted.
|
||||
if (max !== undefined && value > this.props.max && !allowAutoIncrement) {
|
||||
if (max !== undefined && value > this.props.max && !autoIncrement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -243,29 +246,33 @@ class FontPropertyValue extends PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for "keydown" events from the sider and input fields.
|
||||
* Toggles on the "interactive" state. @See toggleInteractiveState();
|
||||
* Begins auto-incrementing if the value is already at the upper bound.
|
||||
*
|
||||
* @param {Event} e
|
||||
* MouseDown event.
|
||||
*/
|
||||
onMouseDown(e) {
|
||||
// Begin auto-incrementing if the value is already at the upper bound.
|
||||
if (this.isAtUpperBound(this.props.value) && e.target.type === "range") {
|
||||
this.startAutoIncrement();
|
||||
}
|
||||
onMouseDown() {
|
||||
this.toggleInteractiveState(true);
|
||||
}
|
||||
|
||||
onMouseUp(e) {
|
||||
/**
|
||||
* Handler for "mousemove" event from range input. If the user is actively interacting
|
||||
* by dragging the slider thumb, start or stop the auto-incrementing behavior depending
|
||||
* on whether the input value is at the upper bound or not.
|
||||
*
|
||||
* @param {MouseEvent} e
|
||||
*/
|
||||
onMouseMove(e) {
|
||||
if (this.state.interactive && e.buttons) {
|
||||
this.isAtUpperBound(this.props.value)
|
||||
? this.startAutoIncrement()
|
||||
: this.stopAutoIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
this.stopAutoIncrement();
|
||||
this.toggleInteractiveState(false);
|
||||
}
|
||||
|
||||
startAutoIncrement() {
|
||||
// Do not set auto-increment interval if not allowed to or if one is already set.
|
||||
if (!this.props.allowAutoIncrement || this.interval) {
|
||||
if (!this.props.autoIncrement || this.interval) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -360,7 +367,7 @@ class FontPropertyValue extends PureComponent {
|
|||
onBlur: this.onBlur,
|
||||
onChange: this.onChange,
|
||||
onFocus: this.onFocus,
|
||||
step: this.props.step || 1,
|
||||
step: this.props.step,
|
||||
// While interacting with the range and number inputs, prevent updating value from
|
||||
// outside props which is debounced and causes jitter on successive renders.
|
||||
value: this.state.interactive
|
||||
|
@ -374,6 +381,7 @@ class FontPropertyValue extends PureComponent {
|
|||
onKeyDown: this.onKeyDown,
|
||||
onKeyUp: this.onKeyUp,
|
||||
onMouseDown: this.onMouseDown,
|
||||
onMouseMove: this.onMouseMove,
|
||||
onMouseUp: this.onMouseUp,
|
||||
className: "font-value-slider",
|
||||
name: this.props.name,
|
||||
|
@ -386,7 +394,7 @@ class FontPropertyValue extends PureComponent {
|
|||
{
|
||||
...defaults,
|
||||
// Remove upper limit from number input if it is allowed to auto-increment.
|
||||
max: this.props.allowAutoIncrement ? null : this.props.max,
|
||||
max: this.props.autoIncrement ? null : this.props.max,
|
||||
name: this.props.name,
|
||||
className: "font-value-input",
|
||||
type: "number",
|
||||
|
|
|
@ -58,7 +58,7 @@ class FontSize extends PureComponent {
|
|||
: max;
|
||||
|
||||
return FontPropertyValue({
|
||||
allowAutoIncrement: true,
|
||||
autoIncrement: true,
|
||||
label: getStr("fontinspector.fontSizeLabel"),
|
||||
min: 0,
|
||||
max: this.historicMax[unit],
|
||||
|
|
|
@ -61,7 +61,7 @@ class LineHeight extends PureComponent {
|
|||
: max;
|
||||
|
||||
return FontPropertyValue({
|
||||
allowAutoIncrement: true,
|
||||
autoIncrement: true,
|
||||
label: getStr("fontinspector.lineHeightLabel"),
|
||||
min: 0,
|
||||
max: this.historicMax[unit],
|
||||
|
|
|
@ -33,9 +33,10 @@ loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-consta
|
|||
loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
|
||||
loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
|
||||
loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
|
||||
loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
|
||||
loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
|
||||
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.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
|
||||
|
||||
|
@ -2318,21 +2319,22 @@ Inspector.prototype = {
|
|||
* Initiate gcli screenshot command on selected node.
|
||||
*/
|
||||
async screenshotNode() {
|
||||
const command = Services.prefs.getBoolPref("devtools.screenshot.clipboard.enabled") ?
|
||||
"screenshot --file --clipboard --selector" :
|
||||
"screenshot --file --selector";
|
||||
|
||||
// Bug 1332936 - it's possible to call `screenshotNode` while the BoxModel highlighter
|
||||
// is still visible, therefore showing it in the picture.
|
||||
// To avoid that, we have to hide it before taking the screenshot. The `hideBoxModel`
|
||||
// will do that, calling `hide` for the highlighter only if previously shown.
|
||||
await this.highlighter.hideBoxModel();
|
||||
|
||||
// Bug 1180314 - CssSelector might contain white space so need to make sure it is
|
||||
// passed to screenshot as a single parameter. More work *might* be needed if
|
||||
// CssSelector could contain escaped single- or double-quotes, backslashes, etc.
|
||||
CommandUtils.executeOnTarget(this._target,
|
||||
`${command} '${this.selectionCssSelector}'`);
|
||||
const clipboardEnabled = Services.prefs
|
||||
.getBoolPref("devtools.screenshot.clipboard.enabled");
|
||||
const args = {
|
||||
file: true,
|
||||
selector: this.selectionCssSelector,
|
||||
clipboard: clipboardEnabled
|
||||
};
|
||||
const screenshotFront = getScreenshotFront(this.target);
|
||||
const screenshot = await screenshotFront.capture(args);
|
||||
await saveScreenshot(this.panelWin, args, screenshot);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -149,7 +149,6 @@ devtools.jar:
|
|||
skin/images/editor-error.png (themes/images/editor-error.png)
|
||||
skin/images/breakpoint.svg (themes/images/breakpoint.svg)
|
||||
skin/webconsole.css (themes/webconsole.css)
|
||||
skin/images/webconsole.svg (themes/images/webconsole.svg)
|
||||
skin/images/webconsole/alert.svg (themes/images/webconsole/alert.svg)
|
||||
skin/images/webconsole/info.svg (themes/images/webconsole/info.svg)
|
||||
skin/images/webconsole/input.svg (themes/images/webconsole/input.svg)
|
||||
|
|
|
@ -64,7 +64,7 @@ pref("devtools.new-animationinspector.enabled", true);
|
|||
// Enable the Font Editor
|
||||
pref("devtools.inspector.fonteditor.enabled", true);
|
||||
// Enable the font highlight-on-hover feature
|
||||
pref("devtools.inspector.fonthighlighter.enabled", false);
|
||||
pref("devtools.inspector.fonthighlighter.enabled", true);
|
||||
|
||||
// Flexbox preferences
|
||||
// Enable the Flexbox highlighter in Nightly
|
||||
|
|
|
@ -4465,7 +4465,17 @@ class Tree extends Component {
|
|||
if (focused || !nativeEvent || !this.treeRef) {
|
||||
return;
|
||||
}
|
||||
this._focus(traversal[0].item);
|
||||
|
||||
const { explicitOriginalTarget } = nativeEvent;
|
||||
// Only set default focus to the first tree node if the focus came
|
||||
// from outside the tree (e.g. by tabbing to the tree from other
|
||||
// external elements).
|
||||
if (
|
||||
explicitOriginalTarget !== this.treeRef &&
|
||||
!this.treeRef.contains(explicitOriginalTarget)
|
||||
) {
|
||||
this._focus(traversal[0].item);
|
||||
}
|
||||
},
|
||||
onBlur: this._onBlur,
|
||||
"aria-label": this.props.label,
|
||||
|
|
|
@ -31,16 +31,17 @@
|
|||
|
||||
.dbg-wasm-item .icon {
|
||||
display: block;
|
||||
background-image: url(chrome://devtools/skin/images/webconsole.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 72px 60px;
|
||||
/* show warning icon */
|
||||
background-position: -24px -24px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
margin-inline-start: -15px;
|
||||
margin-top: 3px;
|
||||
/* show warning icon */
|
||||
background-image: var(--theme-console-alert-image);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
fill: #ec8633;
|
||||
}
|
||||
|
||||
.dbg-breakpoint-line {
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="72" height="60" viewBox="0 0 72 60">
|
||||
<defs>
|
||||
<rect id="glyphShape-colorSwatch" width="8" height="8" ry="2" rx="2"/>
|
||||
<rect id="glyphShape-colorSwatch-border" width="10" height="10" ry="2" rx="2"/>
|
||||
<polygon id="glyphShape-errorX" points="9.9,8.5 8.5,9.9 6,7.4 3.6,9.8 2.2,8.4 4.6,6 2.2,3.6 3.6,2.2 6,4.6 8.4,2.2 9.8,3.6 7.4,6"/>
|
||||
<path id="glyphShape-warningTriangle" d="M9.9,8.6l-3.1-6C6.6,2.2,6.3,2,6,2C5.7,2,5.4,2.2,5.2,2.5l-3.1,6C2,8.9,2,9.3,2.1,9.6C2.3,9.8,2.6,10,2.9,10 h6.1c0.4,0,0.6-0.2,0.8-0.4C10,9.3,10,8.9,9.9,8.6z"/>
|
||||
<path id="glyphShape-exclamationPoint" d="M6,7.7c-0.6,0-1,0.4-1,0.8C5,9,5.4,9.3,6,9.3c0.6,0,1-0.4,1-0.8 C7,8.1,6.6,7.7,6,7.7z M6,7c0.6,0,1-0.4,1-1V5c0-0.6-0.4-1-1-1S5,4.4,5,5v1C5,6.6,5.4,7,6,7z"/>
|
||||
<circle id="glyphShape-infoCircle" cx="6" cy="6" r="4"/>
|
||||
<path id="glyphShape-infoGlyph" d="M6,6C5.4,6,5,6.4,5,7v1c0,0.6,0.4,1,1,1s1-0.4,1-1V7C7,6.4,6.6,6,6,6z M6,5c0.6,0,1-0.4,1-1S6.6,3,6,3S5,3.4,5,4S5.4,5,6,5z"/>
|
||||
<style>
|
||||
.icon-colorSwatch-border {
|
||||
fill: #fff;
|
||||
fill-opacity: .7;
|
||||
}
|
||||
.icon-colorSwatch-network {
|
||||
fill: #000;
|
||||
}
|
||||
.icon-colorSwatch-css {
|
||||
fill: #00b6f0;
|
||||
}
|
||||
.icon-colorSwatch-js {
|
||||
fill: #fb9500;
|
||||
}
|
||||
.icon-colorSwatch-logging {
|
||||
fill: #808080;
|
||||
}
|
||||
.icon-colorSwatch-security {
|
||||
fill: #ec1e0d;
|
||||
}
|
||||
.icon-glyphOverlay {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
#icon-indicator-input {
|
||||
fill: #8fa1b2;
|
||||
}
|
||||
#icon-indicator-output {
|
||||
fill: #667380;
|
||||
}
|
||||
#light-icons:target #icon-indicator-input {
|
||||
fill: #45494d;
|
||||
}
|
||||
#light-icons:target #icon-indicator-output {
|
||||
fill: #8a9199;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="icon-colorSwatch-network">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-network" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-css" transform="translate(0 12)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-css" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-js" transform="translate(0 24)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-js" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-logging" transform="translate(0 36)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-logging" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-security" transform="translate(0 48)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-security" x="2" y="2"/>
|
||||
</g>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-network" class="icon-colorSwatch-network" transform="translate(12)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-css" class="icon-colorSwatch-css" transform="translate(12 12)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-js" class="icon-colorSwatch-js" transform="translate(12 24)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-logging" class="icon-colorSwatch-logging" transform="translate(12 36)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-security" class="icon-colorSwatch-security" transform="translate(12 48)"/>
|
||||
<g id="icon-warningTriangle-css" transform="translate(24 12)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-css"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-warningTriangle-js" transform="translate(24 24)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-js"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-warningTriangle-logging" transform="translate(24 36)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-logging"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-warningTriangle-security" transform="translate(24 48)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-security"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-infoCircle-logging" transform="translate(36 36)">
|
||||
<use xlink:href="#glyphShape-infoCircle" class="icon-colorSwatch-logging"/>
|
||||
<use xlink:href="#glyphShape-infoGlyph" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="light-icons">
|
||||
<path id="icon-indicator-input" d="M6.5,1.2L5.4,2.3L9,6L5.3,9.7l1.1,1.1L11,6L6.5,1.2z M1.5,1.2 L0.4,2.3L4,6L0.3,9.7l1.1,1.1L6,6L1.5,1.2z" transform="translate(48 36)"/>
|
||||
<polygon id="icon-indicator-output" points="10,5 4.3,5 6.8,2.4 5.5,1.2 1,6 5.5,10.8 6.9,9.6 4.3,7 10,7" transform="translate(60 36)"/>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 5.4 KiB |
|
@ -3,5 +3,5 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" fill="context-fill #0b0b0b">
|
||||
<path d="M1.12 9.4L5 1.6c.41-.81 1.58-.81 2 0l3.88 7.78a1.11 1.11 0 0 1-1 1.61H2.11a1.11 1.11 0 0 1-1-1.6zM6 8.4c-.55 0-1 .4-1 .9s.45.9 1 .9 1-.4 1-.9-.45-.9-1-.9zm0-4.9c-.55 0-1 .36-1 .8v2.4c0 .44.45.8 1 .8s1-.36 1-.8V4.3c0-.44-.45-.8-1-.8z"/>
|
||||
<path d="M1.12 9.4L5 1.6c.41-.81 1.58-.81 2 0l3.88 7.78a1.11 1.11 0 0 1-1 1.61H2.11a1.11 1.11 0 0 1-1-1.6zM6 7.8a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm0-4.4a1 1 0 0 0-1 1V6a1 1 0 1 0 2 0V4.4a1 1 0 0 0-1-1z" />
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 594 B После Ширина: | Высота: | Размер: 550 B |
|
@ -3,5 +3,5 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" fill="context-fill #0b0b0b">
|
||||
<path d="M6 1a5 5 0 1 1 0 10A5 5 0 0 1 6 1zm0 1.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0 3c.55 0 1 .36 1 .8v2.4c0 .44-.45.8-1 .8s-1-.36-1-.8V6.3c0-.44.45-.8 1-.8z" fill-rule="evenodd"/>
|
||||
<path d="M6 1a5 5 0 1 1 0 10A5 5 0 0 1 6 1zm0 1.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm0 2.8a1 1 0 0 0-1 1V8a1 1 0 1 0 2 0V6.4a1 1 0 0 0-1-1z"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 527 B После Ширина: | Высота: | Размер: 486 B |
|
@ -75,31 +75,24 @@
|
|||
color: var(--theme-highlight-red);
|
||||
}
|
||||
|
||||
.theme-dark .opt-icon::before {
|
||||
background-image: url(chrome://devtools/skin/images/webconsole.svg);
|
||||
}
|
||||
.theme-light .opt-icon::before {
|
||||
background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
|
||||
}
|
||||
|
||||
.opt-icon::before {
|
||||
display: inline-block;
|
||||
content: "";
|
||||
background-repeat: no-repeat;
|
||||
background-size: 72px 60px;
|
||||
/* show grey "i" bubble by default */
|
||||
background-position: -36px -36px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
vertical-align: -2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
max-height: 12px;
|
||||
}
|
||||
|
||||
.opt-icon::before {
|
||||
margin: 1px 6px 0 0;
|
||||
margin-inline-end: 5px;
|
||||
background-image: var(--theme-console-info-image);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
fill: #808080;
|
||||
}
|
||||
|
||||
.opt-icon.warning::before {
|
||||
background-position: -24px -24px;
|
||||
background-image: var(--theme-console-alert-image);
|
||||
fill: #ec8633;
|
||||
}
|
||||
|
||||
/* Frame Component */
|
||||
|
|
|
@ -557,20 +557,16 @@ html, body, #app, #memory-tool {
|
|||
.error::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
vertical-align: -2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
max-height: 12px;
|
||||
background-image: url(chrome://devtools/skin/images/webconsole.svg);
|
||||
background-size: 72px 60px;
|
||||
background-position: -24px -24px;
|
||||
background-repeat: no-repeat;
|
||||
margin: 0px;
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.theme-light .error::before {
|
||||
background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
|
||||
background-image: var(--theme-console-alert-image);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
fill: #ec8633;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -761,19 +761,17 @@ menuitem.marker-color-graphs-grey .menu-iconic-left::after,
|
|||
*/
|
||||
menuitem.experimental-option::before {
|
||||
content: "";
|
||||
background-image: url(chrome://devtools/skin/images/webconsole.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 72px 60px;
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
|
||||
background-position: -24px -24px;
|
||||
margin: 2px 5px 0 0;
|
||||
max-height: 12px;
|
||||
}
|
||||
.theme-light menuitem.experimental-option::before {
|
||||
background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 5px;
|
||||
background-image: var(--theme-console-alert-image);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
#performance-options-menupopup:not(.experimental-enabled) .experimental-option,
|
||||
|
|
|
@ -285,15 +285,18 @@ a {
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.message.network .status {
|
||||
flex: none;
|
||||
margin-inline-start: 6px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.message.network.mixed-content .url {
|
||||
color: var(--theme-highlight-red);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.message .learn-more-link {
|
||||
|
|
|
@ -6,125 +6,39 @@ It can also display network logs, and you can evaluate expressions using the con
|
|||
input, a.k.a. JsTerm. You can read more about it on [MDN](https://developer.mozilla.org/en-US/docs/Tools/Web_Console)
|
||||
to learn all the features and how to use the tool.
|
||||
|
||||
## Old / New frontend
|
||||
|
||||
The current console used in the toolbox is called the new frontend, and the code lives at
|
||||
the root of the `devtools/client/webconsole/` folder.
|
||||
The old console code is located in the `devtools/client/webconsole/old` folder.
|
||||
Both frontends use the same code for the console input, also called JsTerm (see `jsterm.js`).
|
||||
The old frontend is still used for the Browser Console, but is planned to be removed in the
|
||||
near future (see Bug 1381834).
|
||||
|
||||
## Run WebConsole in DevTools panel
|
||||
## Run WebConsole
|
||||
|
||||
If you want to build the WebConsole inside of the DevTools toolbox (Firefox Devtools Panels),
|
||||
follow the [simple Firefox build](http://docs.firefox-dev.tools/getting-started/build.html)
|
||||
document in MDN. Start your compiled firefox and open the Firefox developer tool, you can
|
||||
documentation. Start your compiled firefox and open the Firefox developer tool, you can
|
||||
then see the WebConsole tab.
|
||||
|
||||
## Run WebConsole in a browser tab (experimental)
|
||||
|
||||
### Prerequisite
|
||||
|
||||
If you would like to run the WebConsole in the browser tab, you need following packages:
|
||||
|
||||
* [node](https://nodejs.org/) >= 7.10.0 JavaScript runtime.
|
||||
* [yarn](https://yarnpkg.com/docs/install) >= 1.0.0 the package dependency management tool.
|
||||
* [Firefox](https://www.mozilla.org/firefox/new/) any version or build from the source code.
|
||||
|
||||
### Run WebConsole
|
||||
|
||||
Navigate to the `mozilla-central/devtools/client/webconsole` folder with your terminal.
|
||||
Note that this folder is available after `mozilla-central` was cloned in order to get a
|
||||
local copy of the repository. Then run the following commands:
|
||||
|
||||
```bash
|
||||
# Install packages
|
||||
yarn install
|
||||
|
||||
# Create a dev server instance for hosting webconsole on browser
|
||||
yarn start
|
||||
```
|
||||
|
||||
Open `localhost:8000` to see what we call the [launchpad](https://github.com/devtools-html/devtools-core/tree/master/packages/devtools-launchpad).
|
||||
The UI that let you start a new Firefox window which will be the target (or debuggee).
|
||||
Launchpad will communicate with Firefox (the remote debugging server) and list all opened tabs from Firefox.
|
||||
You can then navigate to a website you want in the target window, and you should see it appears
|
||||
in the `localhost:8000` page. Clicking on it will start the WebConsole in the browser tab.
|
||||
|
||||
### How it works
|
||||
|
||||
The WebConsole uses [webpack](https://webpack.js.org/) and several packages from [devtools-core](https://github.com/devtools-html/devtools-core)
|
||||
to run as a normal web page. The WebConsole uses [Mozilla remote debugging protocol](http://searchfox.org/mozilla-central/source/devtools/docs/backend/protocol.md)
|
||||
to fetch messages and execute commands against Firefox.
|
||||
|
||||
Open `localhost:8000` in any browser to see the
|
||||
interface. Devtools Launchpad will communicate with Firefox (the remote debugging server)
|
||||
and list all opened tabs from Firefox. Click one of the browser tab entry, now you can see
|
||||
the WebConsole runs in a browser tab.
|
||||
|
||||
### DevTools shared modules
|
||||
|
||||
When working on console running via launchpad, you may need to modify code on external modules.
|
||||
Besides the third party modules, here are modules required for the WebConsole
|
||||
(hosted under the `devtools-core` Github repo, which contains modules shared across Devtools).
|
||||
|
||||
* [devtools-config](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-config/#readme) config used in dev server
|
||||
* [devtools-launchpad](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-launchpad/#readme) provide the dev server, landing page and the bootstrap functions to run devtools in the browser tab.
|
||||
* [devtools-modules](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-modules/#readme) Devtools shared and shim modules.
|
||||
* [devtools-source-editor](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-source-editor/#readme) Source Editor component.
|
||||
* [devtools-reps](https://github.com/devtools-html/debugger.html/blob/master/packages/devtools-reps/#readme) remote object formatter for variables representation.
|
||||
|
||||
Changes to those modules need to be done on Github, using the Pull Request workflow.
|
||||
Then, a new version of the modified package need to be released on npm so the version number
|
||||
can be updated in WebConsole's `package.json`. Some modules have a release process,
|
||||
look for `RELEASE.md` file in the module folder, or ask a maintainer if you are
|
||||
unsure about the release process.
|
||||
|
||||
## Code Structure
|
||||
|
||||
Top level files are used to launch the WebConsole inside of the DevTools toolbox or run in
|
||||
the browser tab (experimental). The same code base is used to run in both environments.
|
||||
|
||||
### Run inside of the DevTools toolbox
|
||||
|
||||
Files used to run the WebConsole inside of the DevTools toolbox.
|
||||
Top level files are used to launch the WebConsole inside of the DevTools toolbox.
|
||||
The main files used to run the WebConsole are:
|
||||
|
||||
* `main.js` called by devtools toolbox to launch the WebConsole panel.
|
||||
* `index.html` panel UI and launch scripts.
|
||||
|
||||
### Run in the browser tab (experimental)
|
||||
|
||||
Files used to run the WebConsole in the browser tab
|
||||
|
||||
* `bin/` files to launch test server.
|
||||
* `configs/` dev configs.
|
||||
* `local-dev/index.js` the entry point, equivalent to `index.html`.
|
||||
* `webpack.config.js` the webpack config file, including plenty of module aliases map to shims and polyfills.
|
||||
* `package.json` declare every required packages and available commands.
|
||||
|
||||
To run in the browser tab, the WebConsole needs to get some dependencies from npm module.
|
||||
Check `package.json` to see all dependencies. Check `webpack.config.js` to find the module alias,
|
||||
and check [devtools-core](https://github.com/devtools-html/devtools-core) packages to dive
|
||||
into actual modules used by the WebConsole and other Devtools.
|
||||
|
||||
### UI
|
||||
|
||||
The WebConsole UI is built using [React](http://docs.firefox-dev.tools/frontend/react.html)
|
||||
components (in `components/`).
|
||||
|
||||
The React application is rendered from `webconsole-output-wrapper.js`.
|
||||
It contains 3 top components:
|
||||
It contains 4 top components:
|
||||
* **ConsoleOutput** (in `ConsoleOutput.js`) is the component where messages are rendered.
|
||||
* **FilterBar** (in `FilterBar.js`) is the component for the filter bars (filter input and toggle buttons).
|
||||
* **SideBar** (in `SideBar.js`) is the component that render the sidebar where objects can be placed in.
|
||||
* **JsTerm** (in `JsTerm.js`) is the component that render the console input.
|
||||
|
||||
We prefer stateless component (defined by function) instead of stateful component
|
||||
(defined by class) unless the component has to maintain its internal state.
|
||||
|
||||
### State
|
||||
|
||||
Besides the UI, the WebConsole manages the app state via [Redux](When working on console running via launchpad).
|
||||
Besides the UI, the WebConsole manages the app state via [Redux].
|
||||
The following locations define the app state:
|
||||
|
||||
* `src/constants.js` constants used across the tool including action and event names.
|
||||
|
@ -137,8 +51,12 @@ The redux state is a plain javascript object with the following properties:
|
|||
{
|
||||
// State of the filter input and toggle buttons
|
||||
filters,
|
||||
// State of the input history
|
||||
history,
|
||||
// Console messages data and state (hidden, expanded, groups, …)
|
||||
messages,
|
||||
// State of notifications displayed on the output (e.g. self-XSS warning message)
|
||||
notifications,
|
||||
// Preferences (persist message, message limit, …)
|
||||
prefs,
|
||||
// Interface state (filter bar visible, sidebar visible, …)
|
||||
|
|
|
@ -1,29 +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/. */
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function getConfig() {
|
||||
if (process.env.TARGET === "firefox-panel") {
|
||||
return require("../configs/firefox-panel.json");
|
||||
}
|
||||
|
||||
const developmentConfig = require("../configs/development.json");
|
||||
|
||||
let localConfig = {};
|
||||
if (fs.existsSync(path.resolve(__dirname, "../configs/local.json"))) {
|
||||
localConfig = require("../configs/local.json");
|
||||
}
|
||||
|
||||
return Object.assign({}, developmentConfig, localConfig);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getConfig,
|
||||
};
|
|
@ -1,19 +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/. */
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
"use strict";
|
||||
|
||||
const toolbox = require("devtools-launchpad/index");
|
||||
const feature = require("devtools-config");
|
||||
const { getConfig } = require("./configure");
|
||||
|
||||
const envConfig = getConfig();
|
||||
|
||||
feature.setConfig(envConfig);
|
||||
|
||||
const webpackConfig = require("../webpack.config");
|
||||
|
||||
toolbox.startDevServer(envConfig, webpackConfig, __dirname);
|
|
@ -18,7 +18,7 @@ loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools"
|
|||
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
|
||||
loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
|
||||
loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
|
||||
loader.lazyRequireGetter(this, "processScreenshot", "devtools/shared/webconsole/screenshot-helper");
|
||||
loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
|
||||
|
||||
const l10n = require("devtools/client/webconsole/webconsole-l10n");
|
||||
|
||||
|
@ -473,7 +473,7 @@ class JSTerm extends Component {
|
|||
break;
|
||||
case "screenshotOutput":
|
||||
const { args, value } = helperResult;
|
||||
const results = await processScreenshot(this.hud.window, args, value);
|
||||
const results = await saveScreenshot(this.hud.window, args, value);
|
||||
this.screenshotNotify(results);
|
||||
// early return as screenshot notify has dispatched all necessary messages
|
||||
return null;
|
||||
|
@ -1104,7 +1104,15 @@ class JSTerm extends Component {
|
|||
filterBy = input.substring(input.lastIndexOf(lastNonAlpha) + 1);
|
||||
}
|
||||
|
||||
const newList = this._autocompleteCache.sort().filter(l => l.startsWith(filterBy));
|
||||
const filterByLc = filterBy.toLocaleLowerCase();
|
||||
const looseMatching = !filterBy || filterBy[0].toLocaleLowerCase() === filterBy[0];
|
||||
const newList = this._autocompleteCache.filter(l => {
|
||||
if (looseMatching) {
|
||||
return l.toLocaleLowerCase().startsWith(filterByLc);
|
||||
}
|
||||
|
||||
return l.startsWith(filterBy);
|
||||
});
|
||||
|
||||
this._receiveAutocompleteProperties(null, {
|
||||
matches: newList,
|
||||
|
@ -1146,21 +1154,42 @@ class JSTerm extends Component {
|
|||
this._autocompleteQuery = inputUntilCursor;
|
||||
}
|
||||
|
||||
const matches = message.matches;
|
||||
const lastPart = message.matchProp;
|
||||
const {matches, matchProp} = message;
|
||||
if (!matches.length) {
|
||||
this.clearCompletion();
|
||||
this.emit("autocomplete-updated");
|
||||
return;
|
||||
}
|
||||
|
||||
const items = matches.map(match => ({
|
||||
preLabel: match.substring(0, matchProp.length),
|
||||
label: match
|
||||
}));
|
||||
|
||||
if (items.length > 0) {
|
||||
const suffix = items[0].label.substring(matchProp.length);
|
||||
this.setAutoCompletionText(suffix);
|
||||
}
|
||||
|
||||
const popup = this.autocompletePopup;
|
||||
const items = matches.map(match => ({ preLabel: lastPart, label: match }));
|
||||
popup.setItems(items);
|
||||
|
||||
const minimumAutoCompleteLength = 2;
|
||||
|
||||
if (items.length >= minimumAutoCompleteLength) {
|
||||
// We want to show the autocomplete popup if:
|
||||
// - there are at least 2 matching results
|
||||
// - OR, if there's 1 result, but whose label does not start like the input (this can
|
||||
// happen with insensitive search: `num` will match `Number`).
|
||||
// - OR, if there's 1 result, but we can't show the completionText (because there's
|
||||
// some text after the cursor), unless the text in the popup is the same as the input.
|
||||
if (items.length >= minimumAutoCompleteLength
|
||||
|| (items.length === 1 && items[0].preLabel !== matchProp)
|
||||
|| (
|
||||
items.length === 1
|
||||
&& !this.canDisplayAutoCompletionText()
|
||||
&& items[0].label !== matchProp
|
||||
)
|
||||
) {
|
||||
let popupAlignElement;
|
||||
let xOffset;
|
||||
let yOffset;
|
||||
|
@ -1168,12 +1197,12 @@ class JSTerm extends Component {
|
|||
if (this.editor) {
|
||||
popupAlignElement = this.node.querySelector(".CodeMirror-cursor");
|
||||
// We need to show the popup at the ".".
|
||||
xOffset = -1 * lastPart.length * this._inputCharWidth;
|
||||
xOffset = -1 * matchProp.length * this._inputCharWidth;
|
||||
yOffset = 5;
|
||||
} else if (this.inputNode) {
|
||||
const offset = inputUntilCursor.length -
|
||||
(inputUntilCursor.lastIndexOf("\n") + 1) -
|
||||
lastPart.length;
|
||||
matchProp.length;
|
||||
xOffset = (offset * this._inputCharWidth) + this._chevronWidth;
|
||||
popupAlignElement = this.inputNode;
|
||||
}
|
||||
|
@ -1185,10 +1214,6 @@ class JSTerm extends Component {
|
|||
popup.hidePopup();
|
||||
}
|
||||
|
||||
if (items.length > 0) {
|
||||
const suffix = items[0].label.substring(lastPart.length);
|
||||
this.setAutoCompletionText(suffix);
|
||||
}
|
||||
this.emit("autocomplete-updated");
|
||||
}
|
||||
|
||||
|
@ -1234,22 +1259,21 @@ class JSTerm extends Component {
|
|||
*/
|
||||
acceptProposedCompletion() {
|
||||
let completionText = this.getAutoCompletionText();
|
||||
// In some cases the completion text might not be displayed (e.g. there is some text
|
||||
// just after the cursor so we can't display it). In those case, if the popup is
|
||||
// open and has a selectedItem, we use it for completing the input.
|
||||
if (
|
||||
!completionText
|
||||
&& this.autocompletePopup.isOpen
|
||||
&& this.autocompletePopup.selectedItem
|
||||
) {
|
||||
let numberOfCharsToReplaceCharsBeforeCursor;
|
||||
|
||||
// If the autocompletion popup is open, we always get the selected element from there,
|
||||
// since the autocompletion text might not be enough (e.g. `dOcUmEn` should
|
||||
// autocomplete to `document`, but the autocompletion text only shows `t`).
|
||||
if (this.autocompletePopup.isOpen && this.autocompletePopup.selectedItem) {
|
||||
const {selectedItem} = this.autocompletePopup;
|
||||
completionText = selectedItem.label.substring(selectedItem.preLabel.length);
|
||||
completionText = selectedItem.label;
|
||||
numberOfCharsToReplaceCharsBeforeCursor = selectedItem.preLabel.length;
|
||||
}
|
||||
|
||||
this.clearCompletion();
|
||||
|
||||
if (completionText) {
|
||||
this.insertStringAtCursor(completionText);
|
||||
this.insertStringAtCursor(completionText, numberOfCharsToReplaceCharsBeforeCursor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1270,16 +1294,21 @@ class JSTerm extends Component {
|
|||
* Insert a string into the console at the cursor location,
|
||||
* moving the cursor to the end of the string.
|
||||
*
|
||||
* @param string str
|
||||
* @param {string} str
|
||||
* @param {int} numberOfCharsToReplaceCharsBeforeCursor - defaults to 0
|
||||
*/
|
||||
insertStringAtCursor(str) {
|
||||
insertStringAtCursor(str, numberOfCharsToReplaceCharsBeforeCursor = 0) {
|
||||
const value = this.getInputValue();
|
||||
const prefix = this.getInputValueBeforeCursor();
|
||||
let prefix = this.getInputValueBeforeCursor();
|
||||
const suffix = value.replace(prefix, "");
|
||||
|
||||
if (numberOfCharsToReplaceCharsBeforeCursor) {
|
||||
prefix =
|
||||
prefix.substring(0, prefix.length - numberOfCharsToReplaceCharsBeforeCursor);
|
||||
}
|
||||
|
||||
// We need to retrieve the cursor before setting the new value.
|
||||
const editorCursor = this.editor && this.editor.getCursor();
|
||||
|
||||
this.setInputValue(prefix + str + suffix);
|
||||
|
||||
if (this.inputNode) {
|
||||
|
@ -1289,7 +1318,7 @@ class JSTerm extends Component {
|
|||
// Set the cursor on the same line it was already at, after the autocompleted text
|
||||
this.editor.setCursor({
|
||||
line: editorCursor.line,
|
||||
ch: editorCursor.ch + str.length
|
||||
ch: editorCursor.ch + str.length - numberOfCharsToReplaceCharsBeforeCursor
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ function NetworkEventMessage({
|
|||
const xhr = isXHR
|
||||
? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
|
||||
: null;
|
||||
const requestUrl = dom.a({ className: "url", title: request.url, onClick: toggle },
|
||||
const requestUrl = dom.span({ className: "url", title: request.url, onClick: toggle },
|
||||
request.url);
|
||||
const statusBody = statusInfo
|
||||
? dom.a({ className: "status", onClick: toggle }, statusInfo)
|
||||
|
|
|
@ -1,99 +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/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
const { EventEmitter } = require("devtools-modules");
|
||||
const { Services: { appinfo, pref } } = require("devtools-modules");
|
||||
const { bootstrap } = require("devtools-launchpad");
|
||||
|
||||
EventEmitter.decorate(window);
|
||||
|
||||
require("../../themes/widgets.css");
|
||||
require("../../themes/webconsole.css");
|
||||
require("../../themes/components-frame.css");
|
||||
require("../../themes/light-theme.css");
|
||||
require("../../shared/components/reps/reps.css");
|
||||
require("../../shared/components/tabs/Tabs.css");
|
||||
require("../../shared/components/tabs/TabBar.css");
|
||||
require("../../netmonitor/src/assets/styles/httpi.css");
|
||||
|
||||
pref("devtools.debugger.remote-timeout", 10000);
|
||||
pref("devtools.hud.loglimit", 10000);
|
||||
pref("devtools.webconsole.filter.error", true);
|
||||
pref("devtools.webconsole.filter.warn", true);
|
||||
pref("devtools.webconsole.filter.info", true);
|
||||
pref("devtools.webconsole.filter.log", true);
|
||||
pref("devtools.webconsole.filter.debug", true);
|
||||
pref("devtools.webconsole.filter.css", false);
|
||||
pref("devtools.webconsole.filter.net", false);
|
||||
pref("devtools.webconsole.filter.netxhr", false);
|
||||
pref("devtools.webconsole.ui.filterbar", false);
|
||||
pref("devtools.webconsole.inputHistoryCount", 50);
|
||||
pref("devtools.webconsole.persistlog", false);
|
||||
pref("devtools.webconsole.timestampMessages", false);
|
||||
pref("devtools.webconsole.sidebarToggle", true);
|
||||
|
||||
const WebConsoleOutputWrapper = require("../webconsole-output-wrapper");
|
||||
const WebConsoleFrame = require("../webconsole-frame").WebConsoleFrame;
|
||||
|
||||
// Copied from netmonitor/index.js:
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
for (const link of document.head.querySelectorAll("link")) {
|
||||
link.href = link.href.replace(/(resource|chrome)\:\/\//, "/");
|
||||
}
|
||||
|
||||
if (appinfo.OS === "Darwin") {
|
||||
document.documentElement.setAttribute("platform", "mac");
|
||||
} else if (appinfo.OS === "Linux") {
|
||||
document.documentElement.setAttribute("platform", "linux");
|
||||
} else {
|
||||
document.documentElement.setAttribute("platform", "win");
|
||||
}
|
||||
});
|
||||
|
||||
let consoleFrame;
|
||||
function onConnect(connection) {
|
||||
// If we are on the main dashboard don't render the component
|
||||
if (!connection || !connection.tabConnection || !connection.tabConnection.tabTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replicate the DOM that the root component lives within
|
||||
document.querySelector("#mount").innerHTML = `
|
||||
<div id="app-wrapper" class="theme-body">
|
||||
<div id="output-container" role="document" aria-live="polite" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Stub out properties that are received from hudservice
|
||||
const owner = {
|
||||
iframeWindow: window,
|
||||
chromeWindow: window,
|
||||
hudId: "hud_0",
|
||||
getDebuggerFrames: () => { },
|
||||
getInspectorSelection: () => { },
|
||||
target: connection.tabConnection.tabTarget,
|
||||
_browserConsole: false,
|
||||
WebConsoleOutputWrapper,
|
||||
};
|
||||
consoleFrame = new WebConsoleFrame(owner);
|
||||
consoleFrame.init().then(function() {
|
||||
console.log("WebConsoleFrame initialized");
|
||||
});
|
||||
}
|
||||
|
||||
// This is just a hack until the local dev environment includes jsterm
|
||||
window.evaluateJS = function(input) {
|
||||
consoleFrame.webConsoleClient.evaluateJSAsync(`${input}`, function(r) {
|
||||
consoleFrame.consoleOutput.dispatchMessageAdd(r);
|
||||
}, {});
|
||||
};
|
||||
|
||||
document.documentElement.classList.add("theme-light");
|
||||
bootstrap(React, ReactDOM).then(onConnect);
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"name": "webconsole",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node": ">=7.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=production node bin/dev-server",
|
||||
"dev": " cross-env NODE_ENV=development node bin/dev-server"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-plugin-transform-flow-strip-types": "^6.22.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"cross-env": "^3.1.3",
|
||||
"devtools-config": "0.0.12",
|
||||
"devtools-launchpad": "^0.0.119",
|
||||
"devtools-modules": "0.0.37",
|
||||
"file-loader": "^1.1.6",
|
||||
"netmonitor": "file:../netmonitor",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "=16.2.0",
|
||||
"react-dom": "=16.2.0",
|
||||
"react-prop-types": "=0.4.0",
|
||||
"react-redux": "=5.0.6",
|
||||
"redux": "^3.7.2",
|
||||
"require-hacker": "^2.1.4"
|
||||
}
|
||||
}
|
|
@ -1,88 +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";
|
||||
|
||||
// Objects and functions were cherry-picked from devtools/shared/client/main.js, in
|
||||
// order to make the console launchpad Worker.
|
||||
|
||||
// mock, we only need DebuggerClient.requester
|
||||
const DebuggerClient = function(transport) {};
|
||||
|
||||
/**
|
||||
* A declarative helper for defining methods that send requests to the server.
|
||||
*
|
||||
* @param packetSkeleton
|
||||
* The form of the packet to send. Can specify fields to be filled from
|
||||
* the parameters by using the |arg| function.
|
||||
* @param before
|
||||
* The function to call before sending the packet. Is passed the packet,
|
||||
* and the return value is used as the new packet. The |this| context is
|
||||
* the instance of the client object we are defining a method for.
|
||||
* @param after
|
||||
* The function to call after the response is received. It is passed the
|
||||
* response, and the return value is considered the new response that
|
||||
* will be passed to the callback. The |this| context is the instance of
|
||||
* the client object we are defining a method for.
|
||||
* @return Request
|
||||
* The `Request` object that is a Promise object and resolves once
|
||||
* we receive the response. (See request method for more details)
|
||||
*/
|
||||
DebuggerClient.requester = function(packetSkeleton, config = {}) {
|
||||
const { before, after } = config;
|
||||
return function(...args) {
|
||||
let outgoingPacket = {
|
||||
to: packetSkeleton.to || this.actor
|
||||
};
|
||||
|
||||
let maxPosition = -1;
|
||||
for (const k of Object.keys(packetSkeleton)) {
|
||||
if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
|
||||
const { position } = packetSkeleton[k];
|
||||
outgoingPacket[k] = packetSkeleton[k].getArgument(args);
|
||||
maxPosition = Math.max(position, maxPosition);
|
||||
} else {
|
||||
outgoingPacket[k] = packetSkeleton[k];
|
||||
}
|
||||
}
|
||||
|
||||
if (before) {
|
||||
outgoingPacket = before.call(this, outgoingPacket);
|
||||
}
|
||||
|
||||
return this.request(outgoingPacket, (response) => {
|
||||
if (after) {
|
||||
const { from } = response;
|
||||
response = after.call(this, response);
|
||||
if (!response.from) {
|
||||
response.from = from;
|
||||
}
|
||||
}
|
||||
|
||||
// The callback is always the last parameter.
|
||||
const thisCallback = args[maxPosition + 1];
|
||||
if (thisCallback) {
|
||||
thisCallback(response);
|
||||
}
|
||||
return response;
|
||||
}, "DebuggerClient.requester request callback");
|
||||
};
|
||||
};
|
||||
|
||||
function arg(pos) {
|
||||
return new DebuggerClient.Argument(pos);
|
||||
}
|
||||
|
||||
DebuggerClient.Argument = function(position) {
|
||||
this.position = position;
|
||||
};
|
||||
|
||||
DebuggerClient.Argument.prototype.getArgument = function(params) {
|
||||
if (!(this.position in params)) {
|
||||
throw new Error("Bad index into params: " + this.position);
|
||||
}
|
||||
return params[this.position];
|
||||
};
|
||||
|
||||
module.exports = { arg, DebuggerClient };
|
|
@ -200,6 +200,7 @@ skip-if = verify
|
|||
[browser_jsterm_autocomplete_return_key.js]
|
||||
[browser_jsterm_autocomplete_width.js]
|
||||
[browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
|
||||
[browser_jsterm_completion_case_sensitivity.js]
|
||||
[browser_jsterm_completion.js]
|
||||
[browser_jsterm_content_defined_helpers.js]
|
||||
[browser_jsterm_copy_command.js]
|
||||
|
|
|
@ -49,9 +49,7 @@ async function performTests() {
|
|||
EventUtils.synthesizeKey("KEY_ArrowRight");
|
||||
await onPopupClose;
|
||||
ok(true, "popup was closed");
|
||||
let expectedInput = "dump(window.testB)";
|
||||
is(jsterm.getInputValue(), expectedInput, "input wasn't modified");
|
||||
checkJsTermCursor(jsterm, expectedInput.length, "cursor was moved to the right");
|
||||
checkJsTermValueAndCursor(jsterm, "dump(window.testB)|", "input wasn't modified");
|
||||
|
||||
await setInitialState(jsterm);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
|
@ -69,9 +67,8 @@ async function performTests() {
|
|||
|
||||
// At this point the completion suggestion should be accepted.
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
expectedInput = "dump(window.testBugBB)";
|
||||
is(jsterm.getInputValue(), expectedInput, "completion was successful after VK_TAB");
|
||||
checkJsTermCursor(jsterm, expectedInput.length - 1, "cursor location is correct");
|
||||
checkJsTermValueAndCursor(jsterm, "dump(window.testBugBB|)",
|
||||
"completion was successful after VK_TAB");
|
||||
ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
|
||||
|
||||
info("Test ENTER key when popup is visible with a selected item");
|
||||
|
@ -82,33 +79,40 @@ async function performTests() {
|
|||
await onPopupClose;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
expectedInput = "dump(window.testBugAA)";
|
||||
is(jsterm.getInputValue(), expectedInput, "completion was successful after Enter");
|
||||
checkJsTermCursor(jsterm, expectedInput.length - 1, "cursor location is correct");
|
||||
checkJsTermValueAndCursor(jsterm, "dump(window.testBugAA|)",
|
||||
"completion was successful after Enter");
|
||||
ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
|
||||
|
||||
info("Test TAB key when there is no autocomplete suggestion");
|
||||
info("Test autocomplete inside parens");
|
||||
jsterm.setInputValue("dump()");
|
||||
EventUtils.synthesizeKey("KEY_ArrowLeft");
|
||||
const onAutocompleteUpdated = jsterm.once("autocomplete-updated");
|
||||
EventUtils.sendString("window.testBugA");
|
||||
await onAutocompleteUpdated;
|
||||
ok(popup.isOpen, "popup is open");
|
||||
ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
|
||||
|
||||
info("Matching the completion proposal should close the popup");
|
||||
onPopupClose = popup.once("popup-closed");
|
||||
EventUtils.sendString("A");
|
||||
await onPopupClose;
|
||||
|
||||
info("Test TAB key when there is no autocomplete suggestion");
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
|
||||
|
||||
EventUtils.synthesizeKey("KEY_Tab");
|
||||
|
||||
expectedInput = "dump(window.testBugAA)";
|
||||
is(jsterm.getInputValue(), "dump(window.testBugA\t)", "Tab inserted a tab char");
|
||||
checkJsTermCursor(jsterm, expectedInput.length - 1, "cursor location is correct");
|
||||
checkJsTermValueAndCursor(jsterm, "dump(window.testBugAA\t|)",
|
||||
"completion was successful after Enter");
|
||||
}
|
||||
|
||||
function setInitialState(jsterm) {
|
||||
async function setInitialState(jsterm) {
|
||||
jsterm.focus();
|
||||
jsterm.setInputValue("dump()");
|
||||
EventUtils.synthesizeKey("KEY_ArrowLeft");
|
||||
|
||||
const onPopUpOpen = jsterm.autocompletePopup.once("popup-opened");
|
||||
EventUtils.sendString("window.testB");
|
||||
return onPopUpOpen;
|
||||
checkJsTermValueAndCursor(jsterm, "dump(window.testB|)");
|
||||
await onPopUpOpen;
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ const TEST_URI = `data:text/html;charset=utf-8,
|
|||
/* Create prototype-less object so popup does not contain native
|
||||
* Object prototype properties.
|
||||
*/
|
||||
window.x = Object.create(null, Object.getOwnPropertyDescriptors({
|
||||
window.xx = Object.create(null, Object.getOwnPropertyDescriptors({
|
||||
["y".repeat(10)]: 1,
|
||||
["z".repeat(20)]: 2
|
||||
}));
|
||||
window.xx = 1;
|
||||
window.xxx = 1;
|
||||
</script>
|
||||
</head>
|
||||
<body>Test</body>`;
|
||||
|
@ -36,14 +36,14 @@ async function performTests() {
|
|||
|
||||
const onPopUpOpen = popup.once("popup-opened");
|
||||
|
||||
info(`wait for completion suggestions for "x"`);
|
||||
EventUtils.sendString("x");
|
||||
info(`wait for completion suggestions for "xx"`);
|
||||
EventUtils.sendString("xx");
|
||||
|
||||
await onPopUpOpen;
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
const expectedPopupItems = ["x", "xx"];
|
||||
const expectedPopupItems = ["xx", "xxx"];
|
||||
is(popup.items.map(i => i.label).join("-"), expectedPopupItems.join("-"),
|
||||
"popup has expected items");
|
||||
|
||||
|
@ -51,7 +51,7 @@ async function performTests() {
|
|||
ok(originalWidth > 2 * jsterm._inputCharWidth,
|
||||
"popup is at least wider than the width of the longest list item");
|
||||
|
||||
info(`wait for completion suggestions for "x."`);
|
||||
info(`wait for completion suggestions for "xx."`);
|
||||
let onAutocompleteUpdated = jsterm.once("autocomplete-updated");
|
||||
EventUtils.sendString(".");
|
||||
await onAutocompleteUpdated;
|
||||
|
@ -63,7 +63,7 @@ async function performTests() {
|
|||
ok(newPopupWidth > 20 * jsterm._inputCharWidth,
|
||||
"popup is at least wider than the width of the longest list item");
|
||||
|
||||
info(`wait for completion suggestions for "x"`);
|
||||
info(`wait for completion suggestions for "xx"`);
|
||||
onAutocompleteUpdated = jsterm.once("autocomplete-updated");
|
||||
EventUtils.synthesizeKey("KEY_Backspace");
|
||||
await onAutocompleteUpdated;
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,<p>test code completion";
|
||||
const TEST_URI = `data:text/html;charset=utf8,<p>test code completion
|
||||
<script>
|
||||
foobar = true;
|
||||
</script>`;
|
||||
|
||||
add_task(async function() {
|
||||
// Run test with legacy JsTerm
|
||||
|
@ -23,18 +26,18 @@ async function performTests() {
|
|||
const {autocompletePopup} = jsterm;
|
||||
|
||||
// Test typing 'docu'.
|
||||
await setInputValueForAutocompletion(jsterm, "docu");
|
||||
is(jsterm.getInputValue(), "docu", "'docu' completion (input.value)");
|
||||
checkJsTermCompletionValue(jsterm, " ment", "'docu' completion (completeNode)");
|
||||
await setInputValueForAutocompletion(jsterm, "foob");
|
||||
is(jsterm.getInputValue(), "foob", "'foob' completion (input.value)");
|
||||
checkJsTermCompletionValue(jsterm, " ar", "'foob' completion (completeNode)");
|
||||
is(autocompletePopup.items.length, 1, "autocomplete popup has 1 item");
|
||||
is(autocompletePopup.isOpen, false, "autocomplete popup is not open");
|
||||
|
||||
// Test typing 'docu' and press tab.
|
||||
EventUtils.synthesizeKey("KEY_Tab");
|
||||
is(jsterm.getInputValue(), "document", "'docu' tab completion");
|
||||
is(jsterm.getInputValue(), "foobar", "'foob' tab completion");
|
||||
|
||||
checkJsTermCursor(jsterm, "document".length, "cursor is at the end of 'document'");
|
||||
is(getJsTermCompletionValue(jsterm).replace(/ /g, ""), "", "'docu' completed");
|
||||
checkJsTermCursor(jsterm, "foobar".length, "cursor is at the end of 'foobar'");
|
||||
is(getJsTermCompletionValue(jsterm).replace(/ /g, ""), "", "'foob' completed");
|
||||
|
||||
// Test typing 'window.Ob' and press tab. Just 'window.O' is
|
||||
// ambiguous: could be window.Object, window.Option, etc.
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that code completion works properly in regards to case sensitivity.
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = `data:text/html;charset=utf8,<p>test case-sensitivity completion.
|
||||
<script>
|
||||
fooBar = Object.create(null, Object.getOwnPropertyDescriptors({
|
||||
Foo: 1,
|
||||
test: 2,
|
||||
Test: 3,
|
||||
TEST: 4,
|
||||
}));
|
||||
FooBar = true;
|
||||
</script>`;
|
||||
|
||||
add_task(async function() {
|
||||
// Run test with legacy JsTerm
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", false);
|
||||
await performTests();
|
||||
// And then run it with the CodeMirror-powered one.
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
|
||||
await performTests();
|
||||
});
|
||||
|
||||
async function performTests() {
|
||||
const {jsterm} = await openNewTabAndConsole(TEST_URI);
|
||||
const {autocompletePopup} = jsterm;
|
||||
|
||||
const checkInput = (expected, assertionInfo) =>
|
||||
checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
|
||||
|
||||
info("Check that lowercased input is case-insensitive");
|
||||
let onPopUpOpen = autocompletePopup.once("popup-opened");
|
||||
EventUtils.sendString("foob");
|
||||
await onPopUpOpen;
|
||||
|
||||
is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "fooBar - FooBar",
|
||||
"popup has expected item, in expected order");
|
||||
checkJsTermCompletionValue(jsterm, " ar", "completeNode has expected value");
|
||||
|
||||
info("Check that filtering the autocomplete cache is also case insensitive");
|
||||
let onAutoCompleteUpdated = jsterm.once("autocomplete-updated");
|
||||
// Send "a" to make the input "fooba"
|
||||
EventUtils.sendString("a");
|
||||
await onAutoCompleteUpdated;
|
||||
|
||||
checkInput("fooba|");
|
||||
is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "fooBar - FooBar",
|
||||
"popup cache filtering is also case-insensitive");
|
||||
checkJsTermCompletionValue(jsterm, " r", "completeNode has expected value");
|
||||
|
||||
info("Check that accepting the completion value will change the input casing");
|
||||
let onPopupClose = autocompletePopup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("KEY_Tab");
|
||||
await onPopupClose;
|
||||
checkInput("fooBar|", "The input was completed with the correct casing");
|
||||
checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
|
||||
|
||||
info("Check that the popup is displayed with only 1 matching item");
|
||||
onPopUpOpen = autocompletePopup.once("popup-opened");
|
||||
EventUtils.sendString(".f");
|
||||
await onPopUpOpen;
|
||||
|
||||
// Here we want to match "Foo", and since the completion text will only be "oo", we want
|
||||
// to display the popup so the user knows that we are matching "Foo" and not "foo".
|
||||
checkInput("fooBar.f|");
|
||||
ok(true, "The popup was opened even if there's 1 item matching");
|
||||
is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "Foo",
|
||||
"popup has expected item");
|
||||
checkJsTermCompletionValue(jsterm, " oo", "completeNode has expected value");
|
||||
|
||||
onPopupClose = autocompletePopup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("KEY_Tab");
|
||||
await onPopupClose;
|
||||
checkInput("fooBar.Foo|", "The input was completed with the correct casing");
|
||||
checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
|
||||
|
||||
jsterm.setInputValue("");
|
||||
|
||||
info("Check that filtering the cache works like on the server");
|
||||
onPopUpOpen = autocompletePopup.once("popup-opened");
|
||||
EventUtils.sendString("fooBar.");
|
||||
await onPopUpOpen;
|
||||
is(getAutocompletePopupLabels(autocompletePopup).join(" - "),
|
||||
"test - Foo - TEST - Test", "popup has expected items");
|
||||
|
||||
onAutoCompleteUpdated = jsterm.once("autocomplete-updated");
|
||||
EventUtils.sendString("T");
|
||||
await onAutoCompleteUpdated;
|
||||
is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "TEST - Test",
|
||||
"popup was filtered case-sensitively, as expected");
|
||||
}
|
||||
|
||||
function getAutocompletePopupLabels(autocompletePopup) {
|
||||
return autocompletePopup.items.map(i => i.label);
|
||||
}
|
|
@ -39,7 +39,8 @@ add_task(async function() {
|
|||
});
|
||||
|
||||
info("Expand the last node");
|
||||
lastNode.click();
|
||||
const view = lastNode.ownerDocument.defaultView;
|
||||
EventUtils.synthesizeMouseAtCenter(lastNode, {}, view);
|
||||
await onOiMutation;
|
||||
|
||||
is(scrollTop, outputContainer.scrollTop,
|
||||
|
|
|
@ -24,10 +24,6 @@ const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
|
|||
const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
|
||||
const PREF_SIDEBAR_ENABLED = "devtools.webconsole.sidebarToggle";
|
||||
|
||||
// XXX: This file is incomplete (see bug 1326937).
|
||||
// It's used when loading the webconsole with devtools-launchpad, but will ultimately be
|
||||
// the entry point for the new frontend
|
||||
|
||||
/**
|
||||
* A WebConsoleFrame instance is an interactive console initialized *per target*
|
||||
* that displays console log data as well as provides an interactive terminal to
|
||||
|
@ -236,10 +232,8 @@ WebConsoleFrame.prototype = {
|
|||
|
||||
const toolbox = gDevTools.getToolbox(this.owner.target);
|
||||
|
||||
// Handle both launchpad and toolbox loading
|
||||
const Wrapper = this.owner.WebConsoleOutputWrapper || this.window.WebConsoleOutput;
|
||||
this.consoleOutput =
|
||||
new Wrapper(this.outputNode, this, toolbox, this.owner, this.document);
|
||||
this.consoleOutput = new this.window.WebConsoleOutput(
|
||||
this.outputNode, this, toolbox, this.owner, this.document);
|
||||
// Toggle the timestamp on preference change
|
||||
Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
|
||||
this._onToolboxPrefChanged();
|
||||
|
|
|
@ -1,160 +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/. */
|
||||
|
||||
/* eslint-env node */
|
||||
/* eslint max-len: [0] */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {toolboxConfig} = require("./node_modules/devtools-launchpad/index");
|
||||
const { NormalModuleReplacementPlugin } = require("webpack");
|
||||
const {getConfig} = require("./bin/configure");
|
||||
|
||||
const path = require("path");
|
||||
const projectPath = path.join(__dirname, "local-dev");
|
||||
|
||||
const webpackConfig = {
|
||||
entry: {
|
||||
console: [path.join(projectPath, "index.js")],
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|svg)$/,
|
||||
loader: "file-loader?name=[path][name].[ext]",
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loaders: [
|
||||
/*
|
||||
* The version of webpack used in the launchpad seems to have trouble
|
||||
* with the require("raw!${file}") that we use for the properties
|
||||
* file in l10.js.
|
||||
* This loader goes through the whole code and remove the "raw!" prefix
|
||||
* so the raw-loader declared in devtools-launchpad config can load
|
||||
* those files.
|
||||
*/
|
||||
"rewrite-raw",
|
||||
// Replace all references to this.browserRequire() by require()
|
||||
"rewrite-browser-require",
|
||||
// Replace all references to loader.lazyRequire() by require()
|
||||
"rewrite-lazy-require",
|
||||
// Replace all references to loader.lazyGetter() by require()
|
||||
"rewrite-lazy-getter",
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
path.resolve("./node_modules"),
|
||||
path.resolve("../shared/webpack"),
|
||||
]
|
||||
},
|
||||
|
||||
output: {
|
||||
path: path.join(__dirname, "assets/build"),
|
||||
filename: "[name].js",
|
||||
publicPath: "/assets/build",
|
||||
},
|
||||
|
||||
externals: [
|
||||
{
|
||||
"promise": "var Promise",
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
webpackConfig.resolve = {
|
||||
modules: [
|
||||
// Make sure webpack is always looking for modules in
|
||||
// `webconsole/node_modules` directory first.
|
||||
path.resolve(__dirname, "node_modules"), "node_modules"
|
||||
],
|
||||
alias: {
|
||||
"Services": "devtools-modules/src/Services",
|
||||
|
||||
"devtools/client/webconsole/utils": path.join(__dirname, "test/fixtures/WebConsoleUtils"),
|
||||
|
||||
"devtools/client/shared/vendor/immutable": "immutable",
|
||||
"devtools/client/shared/vendor/react": "react",
|
||||
"devtools/client/shared/vendor/react-dom": "react-dom",
|
||||
"devtools/client/shared/vendor/react-redux": "react-redux",
|
||||
"devtools/client/shared/vendor/redux": "redux",
|
||||
"devtools/client/shared/vendor/reselect": "reselect",
|
||||
|
||||
"resource://gre/modules/AppConstants.jsm": path.join(__dirname, "../../client/shared/webpack/shims/app-constants-stub"),
|
||||
|
||||
"devtools/client/framework/devtools": path.join(__dirname, "../../client/shared/webpack/shims/framework-devtools-shim"),
|
||||
"devtools/client/framework/menu": "devtools-modules/src/menu",
|
||||
"devtools/client/sourceeditor/editor": "devtools-source-editor/src/source-editor",
|
||||
|
||||
"devtools/client/shared/unicode-url": "./node_modules/devtools-modules/src/unicode-url",
|
||||
"devtools/client/shared/zoom-keys": "devtools-modules/src/zoom-keys",
|
||||
|
||||
"devtools/shared/fronts/timeline": path.join(__dirname, "../../client/shared/webpack/shims/fronts-timeline-shim"),
|
||||
"devtools/shared/event-emitter": "devtools-modules/src/utils/event-emitter",
|
||||
"devtools/shared/client/debugger-client": path.join(__dirname, "test/fixtures/DebuggerClient"),
|
||||
"devtools/shared/platform/clipboard": path.join(__dirname, "../../client/shared/webpack/shims/platform-clipboard-stub"),
|
||||
"devtools/shared/platform/stack": path.join(__dirname, "../../client/shared/webpack/shims/platform-stack-stub"),
|
||||
|
||||
// Locales need to be explicitly mapped to the en-US subfolder
|
||||
"toolkit/locales": path.join(__dirname, "../../../toolkit/locales/en-US"),
|
||||
"devtools/client/locales": path.join(__dirname, "../../client/locales/en-US"),
|
||||
"devtools/shared/locales": path.join(__dirname, "../../shared/locales/en-US"),
|
||||
"devtools/startup/locales": path.join(__dirname, "../../shared/locales/en-US"),
|
||||
|
||||
// Unless a path explicitly needs to be rewritten or shimmed, all devtools paths can
|
||||
// be mapped to ../../
|
||||
"devtools": path.join(__dirname, "../../"),
|
||||
}
|
||||
};
|
||||
|
||||
const mappings = [
|
||||
[
|
||||
/utils\/menu/, "devtools-launchpad/src/components/shared/menu"
|
||||
],
|
||||
[
|
||||
/chrome:\/\/devtools\/skin/,
|
||||
(result) => {
|
||||
result.request = result.request
|
||||
.replace("./chrome://devtools/skin", path.join(__dirname, "../themes"));
|
||||
}
|
||||
],
|
||||
[
|
||||
/chrome:\/\/devtools\/content/,
|
||||
(result) => {
|
||||
result.request = result.request
|
||||
.replace("./chrome://devtools/content", path.join(__dirname, ".."));
|
||||
}
|
||||
],
|
||||
[
|
||||
/resource:\/\/devtools/,
|
||||
(result) => {
|
||||
result.request = result.request
|
||||
.replace("./resource://devtools/client", path.join(__dirname, ".."));
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
webpackConfig.plugins = mappings.map(([regex, res]) =>
|
||||
new NormalModuleReplacementPlugin(regex, res));
|
||||
|
||||
const basePath = path.join(__dirname, "../../").replace(/\\/g, "\\\\");
|
||||
|
||||
const config = toolboxConfig(webpackConfig, getConfig(), {
|
||||
// Exclude to transpile all scripts in devtools/ but not for this folder nor netmonitor.
|
||||
babelExcludes: new RegExp(`^${basePath}(.(?!(webconsole|netmonitor)))*$`),
|
||||
disablePostCSS: true,
|
||||
});
|
||||
|
||||
// Remove loaders from devtools-launchpad's webpack.config.js
|
||||
// * For svg-inline loader:
|
||||
// Webconsole uses file loader to bundle image assets instead of svg-inline-loader
|
||||
config.module.rules = config.module.rules
|
||||
.filter((rule) => !["svg-inline-loader"].includes(rule.loader));
|
||||
|
||||
module.exports = config;
|
|
@ -57,6 +57,7 @@ DevToolsModules(
|
|||
'promises.js',
|
||||
'reflow.js',
|
||||
'root.js',
|
||||
'screenshot.js',
|
||||
'source.js',
|
||||
'storage.js',
|
||||
'string.js',
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/* 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 protocol = require("devtools/shared/protocol");
|
||||
const {captureScreenshot} = require("devtools/shared/screenshot/capture");
|
||||
const {screenshotSpec} = require("devtools/shared/specs/screenshot");
|
||||
|
||||
exports.ScreenshotActor = protocol.ActorClassWithSpec(screenshotSpec, {
|
||||
initialize: function(conn, targetActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.document = targetActor.window.document;
|
||||
},
|
||||
|
||||
capture: function(args) {
|
||||
return captureScreenshot(args, this.document);
|
||||
}
|
||||
});
|
|
@ -1175,7 +1175,7 @@ WebConsoleActor.prototype =
|
|||
this.dbg.removeDebuggee(this.evalWindow);
|
||||
}
|
||||
|
||||
matches = result.matches || [];
|
||||
matches = result.matches || new Set();
|
||||
matchProp = result.matchProp;
|
||||
|
||||
// We consider '$' as alphanumeric because it is used in the names of some
|
||||
|
@ -1183,18 +1183,26 @@ WebConsoleActor.prototype =
|
|||
// be seen as break in the evaled string.
|
||||
const lastNonAlphaIsDot = /[.][a-zA-Z0-9$\s]*$/.test(reqText);
|
||||
if (!lastNonAlphaIsDot) {
|
||||
matches = matches.concat(this._getWebConsoleCommandsCache().filter(n =>
|
||||
// filter out `screenshot` command as it is inaccessible without
|
||||
// the `:` prefix
|
||||
n !== "screenshot" && n.startsWith(result.matchProp)
|
||||
));
|
||||
this._getWebConsoleCommandsCache().forEach(n => {
|
||||
// filter out `screenshot` command as it is inaccessible without the `:` prefix
|
||||
if (n !== "screenshot" && n.startsWith(result.matchProp)) {
|
||||
matches.add(n);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we return an array with unique items, since `matches` can hold twice
|
||||
// the same function name if it was defined in the content page and match an helper
|
||||
// function (e.g. $, keys, …).
|
||||
matches = [...new Set(matches)].sort();
|
||||
// Sort the results in order to display lowercased item first (e.g. we want to
|
||||
// display `document` then `Document` as we loosely match the user input if the
|
||||
// first letter they typed was lowercase).
|
||||
matches = Array.from(matches).sort((a, b) => {
|
||||
const lA = a[0].toLocaleLowerCase() === a[0];
|
||||
const lB = b[0].toLocaleLowerCase() === b[0];
|
||||
if (lA === lB) {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
return lA ? -1 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
|
|
|
@ -13,7 +13,6 @@ DevToolsModules(
|
|||
'content-process-forward.js',
|
||||
'eval-with-debugger.js',
|
||||
'message-manager-mock.js',
|
||||
'screenshot.js',
|
||||
'utils.js',
|
||||
'worker-listeners.js',
|
||||
)
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
|
||||
const {Ci, Cu} = require("chrome");
|
||||
|
||||
loader.lazyRequireGetter(this, "screenshot", "devtools/server/actors/webconsole/screenshot", true);
|
||||
|
||||
// Note that this is only used in WebConsoleCommands, see $0 and pprint().
|
||||
// Note that this is only used in WebConsoleCommands, see $0, screenshot and pprint().
|
||||
if (!isWorker) {
|
||||
loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
|
||||
loader.lazyRequireGetter(this, "captureScreenshot", "devtools/shared/screenshot/capture", true);
|
||||
}
|
||||
|
||||
const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [
|
||||
|
@ -599,10 +598,11 @@ WebConsoleCommands._registerOriginal("copy", function(owner, value) {
|
|||
* The arguments to be passed to the screenshot
|
||||
* @return void
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("screenshot", function(owner, args) {
|
||||
WebConsoleCommands._registerOriginal("screenshot", function(owner, args = {}) {
|
||||
owner.helperResult = (async () => {
|
||||
// creates data for saving the screenshot
|
||||
const value = await screenshot(owner, args);
|
||||
// help is handled on the client side
|
||||
const value = await captureScreenshot(args, owner.window.document);
|
||||
return {
|
||||
type: "screenshotOutput",
|
||||
value,
|
||||
|
|
|
@ -426,6 +426,11 @@ var DebuggerServer = {
|
|||
constructor: "AccessibilityActor",
|
||||
type: { target: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/screenshot", {
|
||||
prefix: "screenshot",
|
||||
constructor: "ScreenshotActor",
|
||||
type: { target: true }
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,6 +31,7 @@ DevToolsModules(
|
|||
'preference.js',
|
||||
'promises.js',
|
||||
'reflow.js',
|
||||
'screenshot.js',
|
||||
'storage.js',
|
||||
'string.js',
|
||||
'styles.js',
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/* 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 {screenshotSpec} = require("devtools/shared/specs/screenshot");
|
||||
const saveScreenshot = require("devtools/shared/screenshot/save");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
|
||||
const ScreenshotFront = protocol.FrontClassWithSpec(screenshotSpec, {
|
||||
initialize: function(client, form) {
|
||||
protocol.Front.prototype.initialize.call(this, client);
|
||||
this.actorID = form.screenshotActor;
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
async captureAndSave(window, args) {
|
||||
const screenshot = await this.capture(args);
|
||||
return saveScreenshot(window, args, screenshot);
|
||||
}
|
||||
});
|
||||
|
||||
// A cache of created fronts: WeakMap<Client, Front>
|
||||
const knownFronts = new WeakMap();
|
||||
|
||||
/**
|
||||
* Create a screenshot front only when needed
|
||||
*/
|
||||
function getScreenshotFront(target) {
|
||||
let front = knownFronts.get(target.client);
|
||||
if (front == null && target.form.screenshotActor != null) {
|
||||
front = new ScreenshotFront(target.client, target.form);
|
||||
knownFronts.set(target.client, front);
|
||||
}
|
||||
return front;
|
||||
}
|
||||
|
||||
exports.getScreenshotFront = getScreenshotFront;
|
|
@ -25,6 +25,7 @@ DIRS += [
|
|||
'platform',
|
||||
'pretty-fast',
|
||||
'qrcode',
|
||||
'screenshot',
|
||||
'security',
|
||||
'sourcemap',
|
||||
'sprintfjs',
|
||||
|
|
|
@ -3,25 +3,19 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const { getRect } = require("devtools/shared/layout/utils");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const CONTAINER_FLASHING_DURATION = 500;
|
||||
const STRINGS_URI = "devtools/shared/locales/screenshot.properties";
|
||||
const L10N = new LocalizationHelper(STRINGS_URI);
|
||||
|
||||
exports.screenshot = function takeAsyncScreenshot(owner, args = {}) {
|
||||
if (args.help) {
|
||||
// Early return as help will be handled on the client side.
|
||||
return null;
|
||||
}
|
||||
return captureScreenshot(args, owner.window.document);
|
||||
};
|
||||
loader.lazyRequireGetter(this, "getRect", "devtools/shared/layout/utils", true);
|
||||
|
||||
/**
|
||||
* This function is called to simulate camera effects
|
||||
* @param object document
|
||||
* The target document.
|
||||
*/
|
||||
function simulateCameraFlash(document) {
|
||||
const window = document.defaultView;
|
||||
|
@ -34,21 +28,26 @@ function simulateCameraFlash(document) {
|
|||
* createScreenshotData
|
||||
*/
|
||||
function captureScreenshot(args, document) {
|
||||
if (args.help) {
|
||||
return null;
|
||||
}
|
||||
if (args.delay > 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
document.defaultView.setTimeout(() => {
|
||||
createScreenshotData(document, args).then(resolve, reject);
|
||||
createScreenshotDataURL(document, args).then(resolve, reject);
|
||||
}, args.delay * 1000);
|
||||
});
|
||||
}
|
||||
return createScreenshotData(document, args);
|
||||
return createScreenshotDataURL(document, args);
|
||||
}
|
||||
|
||||
exports.captureScreenshot = captureScreenshot;
|
||||
|
||||
/**
|
||||
* This does the dirty work of creating a base64 string out of an
|
||||
* area of the browser window
|
||||
*/
|
||||
function createScreenshotData(document, args) {
|
||||
function createScreenshotDataURL(document, args) {
|
||||
const window = document.defaultView;
|
||||
let left = 0;
|
||||
let top = 0;
|
||||
|
@ -111,6 +110,8 @@ function createScreenshotData(document, args) {
|
|||
});
|
||||
}
|
||||
|
||||
exports.createScreenshotDataURL = createScreenshotDataURL;
|
||||
|
||||
/**
|
||||
* We may have a filename specified in args, or we might have to generate
|
||||
* one.
|
||||
|
@ -138,3 +139,4 @@ function getFilename(defaultName) {
|
|||
timeString
|
||||
) + ".png";
|
||||
}
|
||||
|
|
@ -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(
|
||||
'capture.js',
|
||||
'save.js',
|
||||
)
|
|
@ -98,8 +98,8 @@ function getFormattedHelpData() {
|
|||
|
||||
/**
|
||||
* Main entry point in this file; Takes the original arguments that `:screenshot` was
|
||||
* called with and the image value from the server, and uses the client window to save
|
||||
* the screenshot to the remote debugging machine's memory or clipboard.
|
||||
* called with and the image value from the server, and uses the client window to add
|
||||
* and audio effect.
|
||||
*
|
||||
* @param object window
|
||||
* The Debugger Client window.
|
||||
|
@ -113,14 +113,14 @@ function getFormattedHelpData() {
|
|||
* @return string[]
|
||||
* Response messages from processing the screenshot
|
||||
*/
|
||||
function processScreenshot(window, args = {}, value) {
|
||||
function saveScreenshot(window, args = {}, value) {
|
||||
if (args.help) {
|
||||
const message = getFormattedHelpData();
|
||||
// Wrap meesage in an array so that the return value is consistant with saveScreenshot
|
||||
// Wrap message in an array so that the return value is consistant with save
|
||||
return [message];
|
||||
}
|
||||
simulateCameraShutter(window.document);
|
||||
return saveScreenshot(window, args, value);
|
||||
simulateCameraShutter(window);
|
||||
return save(args, value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,8 +129,7 @@ function processScreenshot(window, args = {}, value) {
|
|||
* @param object document
|
||||
* The Debugger Client document.
|
||||
*/
|
||||
function simulateCameraShutter(document) {
|
||||
const window = document.defaultView;
|
||||
function simulateCameraShutter(window) {
|
||||
if (Services.prefs.getBoolPref("devtools.screenshot.audio.enabled")) {
|
||||
const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav");
|
||||
audioCamera.play();
|
||||
|
@ -140,9 +139,6 @@ function simulateCameraShutter(document) {
|
|||
/**
|
||||
* Save the captured screenshot to one of several destinations.
|
||||
*
|
||||
* @param object window
|
||||
* The Debugger Client window.
|
||||
*
|
||||
* @param object args
|
||||
* The original args with which the screenshot was called.
|
||||
*
|
||||
|
@ -152,18 +148,18 @@ function simulateCameraShutter(document) {
|
|||
* @return string[]
|
||||
* Response messages from processing the screenshot.
|
||||
*/
|
||||
async function saveScreenshot(window, args, image) {
|
||||
async function save(args, image) {
|
||||
const fileNeeded = args.filename ||
|
||||
!args.clipboard || args.file;
|
||||
const results = [];
|
||||
|
||||
if (args.clipboard) {
|
||||
const result = saveToClipboard(window, image.data);
|
||||
const result = saveToClipboard(image.data);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
if (fileNeeded) {
|
||||
const result = await saveToFile(window, image);
|
||||
const result = await saveToFile(image);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
|
@ -173,16 +169,13 @@ async function saveScreenshot(window, args, image) {
|
|||
* Save the image data to the clipboard. This returns a promise, so it can
|
||||
* be treated exactly like file processing.
|
||||
*
|
||||
* @param object window
|
||||
* The Debugger Client window.
|
||||
*
|
||||
* @param string base64URI
|
||||
* The image data encoded in a base64 URI that was sent from the server.
|
||||
*
|
||||
* @return string
|
||||
* Response message from processing the screenshot.
|
||||
*/
|
||||
function saveToClipboard(window, base64URI) {
|
||||
function saveToClipboard(base64URI) {
|
||||
try {
|
||||
const imageTools = Cc["@mozilla.org/image/tools;1"]
|
||||
.getService(Ci.imgITools);
|
||||
|
@ -212,16 +205,13 @@ function saveToClipboard(window, base64URI) {
|
|||
* Save the screenshot data to disk, returning a promise which is resolved on
|
||||
* completion.
|
||||
*
|
||||
* @param object window
|
||||
* The Debugger Client window.
|
||||
*
|
||||
* @param object image
|
||||
* The image object that was sent from the server.
|
||||
*
|
||||
* @return string
|
||||
* Response message from processing the screenshot.
|
||||
*/
|
||||
async function saveToFile(window, image) {
|
||||
async function saveToFile(image) {
|
||||
let filename = image.filename;
|
||||
|
||||
// Check there is a .png extension to filename
|
||||
|
@ -258,4 +248,4 @@ async function saveToFile(window, image) {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = processScreenshot;
|
||||
module.exports = saveScreenshot;
|
|
@ -42,6 +42,7 @@ DevToolsModules(
|
|||
'promises.js',
|
||||
'property-iterator.js',
|
||||
'reflow.js',
|
||||
'screenshot.js',
|
||||
'script.js',
|
||||
'source.js',
|
||||
'storage.js',
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {RetVal, Arg, generateActorSpec, types} = require("devtools/shared/protocol");
|
||||
|
||||
types.addDictType("screenshot.args", {
|
||||
fullpage: "nullable:boolean",
|
||||
file: "nullable:boolean",
|
||||
clipboard: "nullable:boolean",
|
||||
selector: "nullable:string",
|
||||
dpr: "nullable:string",
|
||||
delay: "nullable:string"
|
||||
});
|
||||
|
||||
const screenshotSpec = generateActorSpec({
|
||||
typeName: "screenshot",
|
||||
|
||||
methods: {
|
||||
capture: {
|
||||
request: {
|
||||
args: Arg(0, "screenshot.args")
|
||||
},
|
||||
response: {
|
||||
value: RetVal("json")}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
exports.screenshotSpec = screenshotSpec;
|
||||
|
|
@ -180,7 +180,7 @@ function findCompletionBeginning(str) {
|
|||
* If no completion valued could be computed, null is returned,
|
||||
* otherwise a object with the following form is returned:
|
||||
* {
|
||||
* matches: [ string, string, string ],
|
||||
* matches: Set<string>
|
||||
* matchProp: Last part of the inputValue that was used to find
|
||||
* the matches-strings.
|
||||
* }
|
||||
|
@ -407,17 +407,25 @@ function getMatchedProps(obj, match) {
|
|||
* Get all properties in the given object (and its parent prototype chain) that
|
||||
* match a given prefix.
|
||||
*
|
||||
* @param mixed obj
|
||||
* @param {Mixed} obj
|
||||
* Object whose properties we want to filter.
|
||||
* @param string match
|
||||
* @param {string} match
|
||||
* Filter for properties that match this string.
|
||||
* @return object
|
||||
* Object that contains the matchProp and the list of names.
|
||||
* @returns {object} which holds the following properties:
|
||||
* - {string} matchProp.
|
||||
* - {Set} matches: List of matched properties.
|
||||
*/
|
||||
function getMatchedPropsImpl(obj, match, {chainIterator, getProperties}) {
|
||||
const matches = new Set();
|
||||
let numProps = 0;
|
||||
|
||||
const insensitiveMatching = match && match[0].toUpperCase() !== match[0];
|
||||
const propertyMatches = prop => {
|
||||
return insensitiveMatching
|
||||
? prop.toLocaleLowerCase().startsWith(match.toLocaleLowerCase())
|
||||
: prop.startsWith(match);
|
||||
};
|
||||
|
||||
// We need to go up the prototype chain.
|
||||
const iter = chainIterator(obj);
|
||||
for (obj of iter) {
|
||||
|
@ -437,7 +445,7 @@ function getMatchedPropsImpl(obj, match, {chainIterator, getProperties}) {
|
|||
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
const prop = props[i];
|
||||
if (prop.indexOf(match) != 0) {
|
||||
if (!propertyMatches(prop)) {
|
||||
continue;
|
||||
}
|
||||
if (prop.indexOf("-") > -1) {
|
||||
|
@ -459,7 +467,7 @@ function getMatchedPropsImpl(obj, match, {chainIterator, getProperties}) {
|
|||
|
||||
return {
|
||||
matchProp: match,
|
||||
matches: [...matches],
|
||||
matches,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,5 @@ DevToolsModules(
|
|||
'client.js',
|
||||
'js-property-provider.js',
|
||||
'network-helper.js',
|
||||
'screenshot-helper.js',
|
||||
'throttle.js',
|
||||
)
|
||||
|
|
|
@ -67,6 +67,14 @@
|
|||
});
|
||||
window.emojiObject = Object.create(null);
|
||||
window.emojiObject["😎"] = "😎";
|
||||
|
||||
window.insensitiveTestCase = Object.create(null, Object.getOwnPropertyDescriptors({
|
||||
PROP: "",
|
||||
Prop: "",
|
||||
prop: "",
|
||||
PRÖP: "",
|
||||
pröp: "",
|
||||
}));
|
||||
`;
|
||||
await state.client.evaluateJSAsync(script);
|
||||
|
||||
|
@ -81,6 +89,7 @@
|
|||
doAutocompleteProxyThrowsOwnKeys,
|
||||
doAutocompleteDotSurroundedBySpaces,
|
||||
doAutocompleteAfterOr,
|
||||
doInsensitiveAutocomplete,
|
||||
];
|
||||
|
||||
if (!isWorker) {
|
||||
|
@ -182,7 +191,8 @@
|
|||
ok(!response.matchProp, "matchProp");
|
||||
let keys = Object.getOwnPropertyNames(Object.prototype).sort();
|
||||
is(response.matches.length, keys.length, "matches.length");
|
||||
checkObject(response.matches, keys);
|
||||
// checkObject(response.matches, keys);
|
||||
is(response.matches.join(" - "), keys.join(" - "));
|
||||
}
|
||||
|
||||
async function doAutocompleteArray(client) {
|
||||
|
@ -242,6 +252,41 @@
|
|||
is(matches.length, 1, "autocomplete returns expected results");
|
||||
is(matches.join("-"), "foobarObject");
|
||||
}
|
||||
|
||||
async function doInsensitiveAutocomplete(client) {
|
||||
info("test autocomplete for 'window.insensitiveTestCase.'");
|
||||
let {matches} = await client.autocomplete("window.insensitiveTestCase.");
|
||||
is(matches.join("-"), "prop-pröp-PROP-PRÖP-Prop",
|
||||
"autocomplete returns the expected items, in the expected order");
|
||||
|
||||
info("test autocomplete for 'window.insensitiveTestCase.p'");
|
||||
matches = (await client.autocomplete("window.insensitiveTestCase.p")).matches;
|
||||
is(matches.join("-"), "prop-pröp-PROP-PRÖP-Prop",
|
||||
"autocomplete is case-insensitive when first letter is lowercased");
|
||||
|
||||
info("test autocomplete for 'window.insensitiveTestCase.pRoP'");
|
||||
matches = (await client.autocomplete("window.insensitiveTestCase.pRoP")).matches;
|
||||
is(matches.join("-"), "prop-PROP-Prop",
|
||||
"autocomplete is case-insensitive when first letter is lowercased");
|
||||
|
||||
info("test autocomplete for 'window.insensitiveTestCase.P'");
|
||||
matches = (await client.autocomplete("window.insensitiveTestCase.P")).matches;
|
||||
is(matches.join("-"), "PROP-PRÖP-Prop",
|
||||
"autocomplete is case-sensitive when first letter is uppercased");
|
||||
|
||||
info("test autocomplete for 'window.insensitiveTestCase.PROP'");
|
||||
matches = (await client.autocomplete("window.insensitiveTestCase.PROP")).matches;
|
||||
is(matches.join("-"), "PROP",
|
||||
"autocomplete is case-sensitive when first letter is uppercased");
|
||||
|
||||
info("test autocomplete for 'window.insensitiveTestCase.prö'");
|
||||
matches = (await client.autocomplete("window.insensitiveTestCase.prö")).matches;
|
||||
is(matches.join("-"), "pröp-PRÖP", "expected result with lowercase diacritic");
|
||||
|
||||
info("test autocomplete for 'window.insensitiveTestCase.PRÖ'");
|
||||
matches = (await client.autocomplete("window.insensitiveTestCase.PRÖ")).matches;
|
||||
is(matches.join("-"), "PRÖP", "expected result with uppercase diacritic");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -187,7 +187,7 @@ function runChecks(dbgObject, dbgEnv, sandbox) {
|
|||
*/
|
||||
function test_has_no_results(results) {
|
||||
Assert.notEqual(results, null);
|
||||
Assert.equal(results.matches.length, 0);
|
||||
Assert.equal(results.matches.size, 0);
|
||||
}
|
||||
/**
|
||||
* A helper that ensures (required) results were found.
|
||||
|
@ -198,6 +198,6 @@ function test_has_no_results(results) {
|
|||
*/
|
||||
function test_has_result(results, requiredSuggestion) {
|
||||
Assert.notEqual(results, null);
|
||||
Assert.ok(results.matches.length > 0);
|
||||
Assert.ok(results.matches.includes(requiredSuggestion));
|
||||
Assert.ok(results.matches.size > 0);
|
||||
Assert.ok(results.matches.has(requiredSuggestion));
|
||||
}
|
||||
|
|
|
@ -144,11 +144,8 @@ IDTracker::Reset(nsIContent* aFromContent,
|
|||
}
|
||||
|
||||
void
|
||||
IDTracker::ResetWithID(nsIContent* aFromContent,
|
||||
nsAtom* aID,
|
||||
bool aWatch)
|
||||
IDTracker::ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch)
|
||||
{
|
||||
MOZ_ASSERT(aFromContent);
|
||||
MOZ_ASSERT(aID);
|
||||
|
||||
if (aWatch) {
|
||||
|
@ -158,7 +155,7 @@ IDTracker::ResetWithID(nsIContent* aFromContent,
|
|||
|
||||
mReferencingImage = false;
|
||||
|
||||
DocumentOrShadowRoot* docOrShadow = DocOrShadowFromContent(*aFromContent);
|
||||
DocumentOrShadowRoot* docOrShadow = DocOrShadowFromContent(aFrom);
|
||||
HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, nsDependentAtomString(aID));
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ public:
|
|||
* changes, so ElementChanged won't fire and get() will always return the same
|
||||
* value, the current element for the ID.
|
||||
*/
|
||||
void ResetWithID(nsIContent* aFrom, nsAtom* aID, bool aWatch = true);
|
||||
void ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch = true);
|
||||
|
||||
/**
|
||||
* Clears the reference. ElementChanged is not triggered. get() will return
|
||||
|
|
|
@ -10042,6 +10042,7 @@ public:
|
|||
parser->BlockParser();
|
||||
mParser = do_GetWeakReference(parser);
|
||||
mDocument = aDocument;
|
||||
mDocument->BlockOnload();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10080,6 +10081,7 @@ private:
|
|||
if (parser == docParser) {
|
||||
parser->UnblockParser();
|
||||
parser->ContinueInterruptedParsingAsync();
|
||||
mDocument->UnblockOnload(false);
|
||||
}
|
||||
}
|
||||
mParser = nullptr;
|
||||
|
|
|
@ -95,9 +95,11 @@ LOCAL_INCLUDES += [
|
|||
'/media/webrtc/signaling/src/common/time_profiling',
|
||||
'/media/webrtc/signaling/src/peerconnection',
|
||||
'/media/webrtc/trunk/',
|
||||
'/third_party/msgpack/include',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_LIBPRIO']:
|
||||
LOCAL_INCLUDES += ['/third_party/msgpack/include']
|
||||
|
||||
DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
|
||||
DEFINES['GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER'] = True
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ WEBIDL_FILES = [
|
|||
'MozStorageStatementParams.webidl',
|
||||
'MozStorageStatementRow.webidl',
|
||||
'PrecompiledScript.webidl',
|
||||
'PrioEncoder.webidl',
|
||||
'PromiseDebugging.webidl',
|
||||
'StructuredCloneHolder.webidl',
|
||||
'WebExtensionContentScript.webidl',
|
||||
|
@ -58,3 +57,9 @@ if CONFIG['MOZ_PLACES']:
|
|||
'PlacesEvent.webidl',
|
||||
'PlacesObservers.webidl',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_LIBPRIO']:
|
||||
WEBIDL_FILES += [
|
||||
'PrioEncoder.webidl',
|
||||
]
|
||||
|
||||
|
|
|
@ -1003,6 +1003,20 @@ Event::DefaultPrevented(CallerType aCallerType) const
|
|||
aCallerType == CallerType::System;
|
||||
}
|
||||
|
||||
bool
|
||||
Event::ReturnValue(CallerType aCallerType) const
|
||||
{
|
||||
return !DefaultPrevented(aCallerType);
|
||||
}
|
||||
|
||||
void
|
||||
Event::SetReturnValue(bool aReturnValue, CallerType aCallerType)
|
||||
{
|
||||
if (!aReturnValue) {
|
||||
PreventDefaultInternal(aCallerType == CallerType::System);
|
||||
}
|
||||
}
|
||||
|
||||
double
|
||||
Event::TimeStamp()
|
||||
{
|
||||
|
|
|
@ -281,6 +281,10 @@ public:
|
|||
return mEvent->mFlags.mMultipleActionsPrevented;
|
||||
}
|
||||
|
||||
bool ReturnValue(CallerType aCallerType) const;
|
||||
|
||||
void SetReturnValue(bool aReturnValue, CallerType aCallerType);
|
||||
|
||||
bool IsTrusted() const
|
||||
{
|
||||
return mEvent->IsTrusted();
|
||||
|
|
|
@ -131,6 +131,7 @@ public:
|
|||
virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override;
|
||||
virtual nsISupports *GetTarget() override;
|
||||
virtual bool IsScriptExecuting() override;
|
||||
virtual void ContinueInterruptedParsingAsync() override;
|
||||
|
||||
// nsIHTMLContentSink
|
||||
NS_IMETHOD OpenContainer(ElementType aNodeType) override;
|
||||
|
@ -175,6 +176,9 @@ protected:
|
|||
void NotifyInsert(nsIContent* aContent,
|
||||
nsIContent* aChildContent);
|
||||
void NotifyRootInsertion();
|
||||
|
||||
private:
|
||||
void ContinueInterruptedParsingIfEnabled();
|
||||
};
|
||||
|
||||
class SinkContext
|
||||
|
@ -1047,3 +1051,23 @@ HTMLContentSink::IsScriptExecuting()
|
|||
{
|
||||
return IsScriptExecutingImpl();
|
||||
}
|
||||
|
||||
void
|
||||
HTMLContentSink::ContinueInterruptedParsingIfEnabled()
|
||||
{
|
||||
if (mParser->IsParserEnabled()) {
|
||||
static_cast<nsIParser*>(mParser.get())->ContinueInterruptedParsing();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLContentSink::ContinueInterruptedParsingAsync()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> ev =
|
||||
NewRunnableMethod("HTMLContentSink::ContinueInterruptedParsingIfEnabled",
|
||||
this,
|
||||
&HTMLContentSink::ContinueInterruptedParsingIfEnabled);
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = do_QueryInterface(mHTMLDocument);
|
||||
doc->Dispatch(mozilla::TaskCategory::Other, ev.forget());
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ DIRS += [
|
|||
'notification',
|
||||
'offline',
|
||||
'power',
|
||||
'prio',
|
||||
'push',
|
||||
'quota',
|
||||
'security',
|
||||
|
@ -105,6 +104,9 @@ DIRS += [
|
|||
'simpledb',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_LIBPRIO']:
|
||||
DIRS += ['prio']
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
DIRS += ['plugins/ipc/hangui']
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
|
|||
|
||||
nsresult
|
||||
nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
|
||||
Element* aContextNode)
|
||||
Element& aContextElement)
|
||||
{
|
||||
nsSMILTimeValueSpecParams params;
|
||||
|
||||
|
@ -80,23 +80,21 @@ nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
|
|||
mParams.mEventSymbol = nsGkAtoms::repeatEvent;
|
||||
}
|
||||
|
||||
ResolveReferences(aContextNode);
|
||||
ResolveReferences(aContextElement);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
|
||||
nsSMILTimeValueSpec::ResolveReferences(Element& aContextElement)
|
||||
{
|
||||
if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased())
|
||||
if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) {
|
||||
return;
|
||||
|
||||
MOZ_ASSERT(aContextNode,
|
||||
"null context node for resolving timing references against");
|
||||
}
|
||||
|
||||
// If we're not bound to the document yet, don't worry, we'll get called again
|
||||
// when that happens
|
||||
if (!aContextNode->IsInComposedDoc())
|
||||
if (!aContextElement.IsInComposedDoc())
|
||||
return;
|
||||
|
||||
// Hold ref to the old element so that it isn't destroyed in between resetting
|
||||
|
@ -105,7 +103,7 @@ nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
|
|||
RefPtr<Element> oldReferencedElement = mReferencedElement.get();
|
||||
|
||||
if (mParams.mDependentElemID) {
|
||||
mReferencedElement.ResetWithID(aContextNode, mParams.mDependentElemID);
|
||||
mReferencedElement.ResetWithID(aContextElement, mParams.mDependentElemID);
|
||||
} else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
|
||||
Element* target = mOwner->GetTargetElement();
|
||||
mReferencedElement.ResetWithElement(target);
|
||||
|
|
|
@ -48,9 +48,9 @@ public:
|
|||
nsSMILTimeValueSpec(nsSMILTimedElement& aOwner, bool aIsBegin);
|
||||
~nsSMILTimeValueSpec();
|
||||
|
||||
nsresult SetSpec(const nsAString& aStringSpec, Element* aContextNode);
|
||||
void ResolveReferences(nsIContent* aContextNode);
|
||||
bool IsEventBased() const;
|
||||
nsresult SetSpec(const nsAString& aStringSpec, Element& aContextElement);
|
||||
void ResolveReferences(Element& aContextElement);
|
||||
bool IsEventBased() const;
|
||||
|
||||
void HandleNewInterval(nsSMILInterval& aInterval,
|
||||
const nsSMILTimeContainer* aSrcContainer);
|
||||
|
|
|
@ -853,20 +853,21 @@ namespace
|
|||
} // namespace
|
||||
|
||||
bool
|
||||
nsSMILTimedElement::SetAttr(nsAtom* aAttribute, const nsAString& aValue,
|
||||
nsSMILTimedElement::SetAttr(nsAtom* aAttribute,
|
||||
const nsAString& aValue,
|
||||
nsAttrValue& aResult,
|
||||
Element* aContextNode,
|
||||
Element& aContextElement,
|
||||
nsresult* aParseResult)
|
||||
{
|
||||
bool foundMatch = true;
|
||||
nsresult parseResult = NS_OK;
|
||||
|
||||
if (aAttribute == nsGkAtoms::begin) {
|
||||
parseResult = SetBeginSpec(aValue, aContextNode, RemoveNonDOM);
|
||||
parseResult = SetBeginSpec(aValue, aContextElement, RemoveNonDOM);
|
||||
} else if (aAttribute == nsGkAtoms::dur) {
|
||||
parseResult = SetSimpleDuration(aValue);
|
||||
} else if (aAttribute == nsGkAtoms::end) {
|
||||
parseResult = SetEndSpec(aValue, aContextNode, RemoveNonDOM);
|
||||
parseResult = SetEndSpec(aValue, aContextElement, RemoveNonDOM);
|
||||
} else if (aAttribute == nsGkAtoms::fill) {
|
||||
parseResult = SetFillMode(aValue);
|
||||
} else if (aAttribute == nsGkAtoms::max) {
|
||||
|
@ -928,10 +929,10 @@ nsSMILTimedElement::UnsetAttr(nsAtom* aAttribute)
|
|||
|
||||
nsresult
|
||||
nsSMILTimedElement::SetBeginSpec(const nsAString& aBeginSpec,
|
||||
Element* aContextNode,
|
||||
Element& aContextElement,
|
||||
RemovalTestFunction aRemove)
|
||||
{
|
||||
return SetBeginOrEndSpec(aBeginSpec, aContextNode, true /*isBegin*/,
|
||||
return SetBeginOrEndSpec(aBeginSpec, aContextElement, true /*isBegin*/,
|
||||
aRemove);
|
||||
}
|
||||
|
||||
|
@ -944,10 +945,10 @@ nsSMILTimedElement::UnsetBeginSpec(RemovalTestFunction aRemove)
|
|||
|
||||
nsresult
|
||||
nsSMILTimedElement::SetEndSpec(const nsAString& aEndSpec,
|
||||
Element* aContextNode,
|
||||
Element& aContextElement,
|
||||
RemovalTestFunction aRemove)
|
||||
{
|
||||
return SetBeginOrEndSpec(aEndSpec, aContextNode, false /*!isBegin*/,
|
||||
return SetBeginOrEndSpec(aEndSpec, aContextElement, false /*!isBegin*/,
|
||||
aRemove);
|
||||
}
|
||||
|
||||
|
@ -1205,7 +1206,7 @@ nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const
|
|||
}
|
||||
|
||||
void
|
||||
nsSMILTimedElement::BindToTree(nsIContent* aContextNode)
|
||||
nsSMILTimedElement::BindToTree(Element& aContextElement)
|
||||
{
|
||||
// Reset previously registered milestone since we may be registering with
|
||||
// a different time container now.
|
||||
|
@ -1225,12 +1226,12 @@ nsSMILTimedElement::BindToTree(nsIContent* aContextNode)
|
|||
// Resolve references to other parts of the tree
|
||||
uint32_t count = mBeginSpecs.Length();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
mBeginSpecs[i]->ResolveReferences(aContextNode);
|
||||
mBeginSpecs[i]->ResolveReferences(aContextElement);
|
||||
}
|
||||
|
||||
count = mEndSpecs.Length();
|
||||
for (uint32_t j = 0; j < count; ++j) {
|
||||
mEndSpecs[j]->ResolveReferences(aContextNode);
|
||||
mEndSpecs[j]->ResolveReferences(aContextElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1304,7 +1305,7 @@ nsSMILTimedElement::Unlink()
|
|||
|
||||
nsresult
|
||||
nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec,
|
||||
Element* aContextNode,
|
||||
Element& aContextElement,
|
||||
bool aIsBegin,
|
||||
RemovalTestFunction aRemove)
|
||||
{
|
||||
|
@ -1323,7 +1324,7 @@ nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec,
|
|||
bool hadFailure = false;
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
auto spec = MakeUnique<nsSMILTimeValueSpec>(*this, aIsBegin);
|
||||
nsresult rv = spec->SetSpec(tokenizer.nextToken(), aContextNode);
|
||||
nsresult rv = spec->SetSpec(tokenizer.nextToken(), aContextElement);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
timeSpecsList.AppendElement(std::move(spec));
|
||||
} else {
|
||||
|
@ -1481,13 +1482,13 @@ nsSMILTimedElement::RebuildTimingState(RemovalTestFunction aRemove)
|
|||
if (mAnimationElement->HasAttr(nsGkAtoms::begin)) {
|
||||
nsAutoString attValue;
|
||||
mAnimationElement->GetAttr(nsGkAtoms::begin, attValue);
|
||||
SetBeginSpec(attValue, mAnimationElement, aRemove);
|
||||
SetBeginSpec(attValue, *mAnimationElement, aRemove);
|
||||
}
|
||||
|
||||
if (mAnimationElement->HasAttr(nsGkAtoms::end)) {
|
||||
nsAutoString attValue;
|
||||
mAnimationElement->GetAttr(nsGkAtoms::end, attValue);
|
||||
SetEndSpec(attValue, mAnimationElement, aRemove);
|
||||
SetEndSpec(attValue, *mAnimationElement, aRemove);
|
||||
}
|
||||
|
||||
mPrevRegisteredMilestone = sMaxMilestone;
|
||||
|
|
|
@ -60,7 +60,7 @@ public:
|
|||
* Returns the element targeted by the animation element. Needed for
|
||||
* registering event listeners against the appropriate element.
|
||||
*/
|
||||
mozilla::dom::Element* GetTargetElement();
|
||||
Element* GetTargetElement();
|
||||
|
||||
/**
|
||||
* Methods for supporting the ElementTimeControl interface.
|
||||
|
@ -265,8 +265,8 @@ public:
|
|||
* @param aValue The attribute value.
|
||||
* @param aResult The nsAttrValue object that may be used for storing the
|
||||
* parsed result.
|
||||
* @param aContextNode The element to use for context when resolving
|
||||
* references to other elements.
|
||||
* @param aContextElement The element to use for context when resolving
|
||||
* references to other elements.
|
||||
* @param[out] aParseResult The result of parsing the attribute. Will be set
|
||||
* to NS_OK if parsing is successful.
|
||||
*
|
||||
|
@ -274,8 +274,8 @@ public:
|
|||
* otherwise.
|
||||
*/
|
||||
bool SetAttr(nsAtom* aAttribute, const nsAString& aValue,
|
||||
nsAttrValue& aResult, Element* aContextNode,
|
||||
nsresult* aParseResult = nullptr);
|
||||
nsAttrValue& aResult, Element& aContextElement,
|
||||
nsresult* aParseResult = nullptr);
|
||||
|
||||
/**
|
||||
* Attempts to unset an attribute on this timed element.
|
||||
|
@ -326,18 +326,17 @@ public:
|
|||
* Called when the timed element has been bound to the document so that
|
||||
* references from this timed element to other elements can be resolved.
|
||||
*
|
||||
* @param aContextNode The node which provides the necessary context for
|
||||
* resolving references. This is typically the element in
|
||||
* the host language that owns this timed element. Should
|
||||
* not be null.
|
||||
* @param aContextElement The element which provides the necessary context for
|
||||
* resolving references. This is typically the element
|
||||
* in the host language that owns this timed element.
|
||||
*/
|
||||
void BindToTree(nsIContent* aContextNode);
|
||||
void BindToTree(Element& aContextElement);
|
||||
|
||||
/**
|
||||
* Called when the target of the animation has changed so that event
|
||||
* registrations can be updated.
|
||||
*/
|
||||
void HandleTargetElementChange(mozilla::dom::Element* aNewTarget);
|
||||
void HandleTargetElementChange(Element* aNewTarget);
|
||||
|
||||
/**
|
||||
* Called when the timed element has been removed from a document so that
|
||||
|
@ -377,10 +376,10 @@ protected:
|
|||
//
|
||||
|
||||
nsresult SetBeginSpec(const nsAString& aBeginSpec,
|
||||
Element* aContextNode,
|
||||
Element& aContextElement,
|
||||
RemovalTestFunction aRemove);
|
||||
nsresult SetEndSpec(const nsAString& aEndSpec,
|
||||
Element* aContextNode,
|
||||
Element& aContextElement,
|
||||
RemovalTestFunction aRemove);
|
||||
nsresult SetSimpleDuration(const nsAString& aDurSpec);
|
||||
nsresult SetMin(const nsAString& aMinSpec);
|
||||
|
@ -401,7 +400,7 @@ protected:
|
|||
void UnsetFillMode();
|
||||
|
||||
nsresult SetBeginOrEndSpec(const nsAString& aSpec,
|
||||
Element* aContextNode,
|
||||
Element& aContextElement,
|
||||
bool aIsBegin,
|
||||
RemovalTestFunction aRemove);
|
||||
void ClearSpecs(TimeValueSpecList& aSpecs,
|
||||
|
|
|
@ -185,7 +185,7 @@ SVGAnimationElement::BindToTree(nsIDocument* aDocument,
|
|||
UpdateHrefTarget(hrefStr);
|
||||
}
|
||||
|
||||
mTimedElement.BindToTree(aParent);
|
||||
mTimedElement.BindToTree(*this);
|
||||
}
|
||||
|
||||
AnimationNeedsResample();
|
||||
|
@ -234,7 +234,7 @@ SVGAnimationElement::ParseAttribute(int32_t aNamespaceID,
|
|||
// try to parse it.
|
||||
if (!foundMatch) {
|
||||
foundMatch =
|
||||
mTimedElement.SetAttr(aAttribute, aValue, aResult, this, &rv);
|
||||
mTimedElement.SetAttr(aAttribute, aValue, aResult, *this, &rv);
|
||||
}
|
||||
|
||||
if (foundMatch) {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<script>
|
||||
function start() {
|
||||
document.replaceChild(id_0, document.childNodes[0]);
|
||||
}
|
||||
</script>
|
||||
<body onload="start()">
|
||||
<svg>
|
||||
<animate id="id_0" begin="s" />
|
||||
<ellipse id="id_1" />
|
||||
</svg>
|
||||
</body>
|