Merge m-c to b2g-inbound. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-08-20 16:21:52 -04:00
commit 296ec7fc20
229 changed files with 5839 additions and 2838 deletions

View File

@ -296,6 +296,7 @@
@BINPATH@/components/toolkit_finalizationwitness.xpt
@BINPATH@/components/toolkit_formautofill.xpt
@BINPATH@/components/toolkit_osfile.xpt
@BINPATH@/components/toolkit_xulstore.xpt
@BINPATH@/components/toolkitprofile.xpt
#ifdef MOZ_ENABLE_XREMOTE
@BINPATH@/components/toolkitremote.xpt
@ -540,6 +541,8 @@
#endif
@BINPATH@/components/TelemetryStartup.js
@BINPATH@/components/TelemetryStartup.manifest
@BINPATH@/components/XULStore.js
@BINPATH@/components/XULStore.manifest
@BINPATH@/components/Webapps.js
@BINPATH@/components/Webapps.manifest
@BINPATH@/components/AppsService.js
@ -692,9 +695,6 @@
@BINPATH@/res/text_caret_tilt_right@1.5x.png
@BINPATH@/res/text_caret_tilt_right@2.25x.png
@BINPATH@/res/text_caret_tilt_right@2x.png
@BINPATH@/res/text_selection_handle.png
@BINPATH@/res/text_selection_handle@1.5.png
@BINPATH@/res/text_selection_handle@2.png
@BINPATH@/res/grabber.gif
#ifdef XP_MACOSX
@BINPATH@/res/cursors/*

View File

@ -9,6 +9,21 @@ var FullScreen = {
delete this._fullScrToggler;
return this._fullScrToggler = document.getElementById("fullscr-toggler");
},
init: function() {
// called when we go into full screen, even if initiated by a web page script
window.addEventListener("fullscreen", this, true);
window.messageManager.addMessageListener("MozEnteredDomFullscreen", this);
if (window.fullScreen)
this.toggle();
},
uninit: function() {
window.messageManager.removeMessageListener("MozEnteredDomFullscreen", this);
this.cleanup();
},
toggle: function (event) {
var enterFS = window.fullScreen;
@ -95,9 +110,12 @@ var FullScreen = {
switch (event.type) {
case "activate":
if (document.mozFullScreen) {
this.showWarning(this.fullscreenDoc);
this.showWarning(this.fullscreenOrigin);
}
break;
case "fullscreen":
this.toggle(event);
break;
case "transitionend":
if (event.propertyName == "opacity")
this.cancelWarning();
@ -105,18 +123,33 @@ var FullScreen = {
}
},
enterDomFullscreen : function(event) {
receiveMessage: function(aMessage) {
if (aMessage.name == "MozEnteredDomFullscreen") {
// If we're a multiprocess browser, then the request to enter fullscreen
// did not bubble up to the root browser document - it stopped at the root
// of the content document. That means we have to kick off the switch to
// fullscreen here at the operating system level in the parent process
// ourselves.
let data = aMessage.data;
let browser = aMessage.target;
if (gMultiProcessBrowser && browser.getAttribute("remote") == "true") {
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.remoteFrameFullscreenChanged(browser, data.origin);
}
this.enterDomFullscreen(browser, data.origin);
}
},
enterDomFullscreen : function(aBrowser, aOrigin) {
if (!document.mozFullScreen)
return;
// However, if we receive a "MozEnteredDomFullScreen" event for a document
// which is not a subdocument of a currently active (ie. visible) browser
// or iframe, we know that we've switched to a different frame since the
// request to enter full-screen was made, so we should exit full-screen
// since the "full-screen document" isn't acutally visible.
if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell).isActive) {
// If we've received a fullscreen notification, we have to ensure that the
// element that's requesting fullscreen belongs to the browser that's currently
// active. If not, we exit fullscreen since the "full-screen document" isn't
// actually visible now.
if (gBrowser.selectedBrowser != aBrowser) {
document.mozCancelFullScreen();
return;
}
@ -136,7 +169,7 @@ var FullScreen = {
if (gFindBarInitialized)
gFindBar.close();
this.showWarning(event.target);
this.showWarning(aOrigin);
// Exit DOM full-screen mode upon open, close, or change tab.
gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
@ -178,7 +211,9 @@ var FullScreen = {
gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
if (!this.useLionFullScreen)
window.removeEventListener("activate", this);
this.fullscreenDoc = null;
window.messageManager
.broadcastAsyncMessage("DOMFullscreen:Cleanup");
}
},
@ -337,7 +372,7 @@ var FullScreen = {
// the permission manager can't handle (documents with URIs without a host).
// We simply require those to be approved every time instead.
let rememberCheckbox = document.getElementById("full-screen-remember-decision");
let uri = this.fullscreenDoc.nodePrincipal.URI;
let uri = BrowserUtils.makeURI(this.fullscreenOrigin);
if (!rememberCheckbox.hidden) {
if (rememberCheckbox.checked)
Services.perms.add(uri,
@ -370,27 +405,29 @@ var FullScreen = {
// If the document has been granted fullscreen, notify Gecko so it can resume
// any pending pointer lock requests, otherwise exit fullscreen; the user denied
// the fullscreen request.
if (isApproved)
Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", "");
else
if (isApproved) {
gBrowser.selectedBrowser
.messageManager
.sendAsyncMessage("DOMFullscreen:Approved");
} else {
document.mozCancelFullScreen();
}
},
warningBox: null,
warningFadeOutTimeout: null,
fullscreenDoc: null,
// Shows the fullscreen approval UI, or if the domain has already been approved
// for fullscreen, shows a warning that the site has entered fullscreen for a short
// duration.
showWarning: function(targetDoc) {
showWarning: function(aOrigin) {
if (!document.mozFullScreen ||
!gPrefService.getBoolPref("full-screen-api.approval-required"))
return;
// Set the strings on the fullscreen approval UI.
this.fullscreenDoc = targetDoc;
let uri = this.fullscreenDoc.nodePrincipal.URI;
this.fullscreenOrigin = aOrigin;
let uri = BrowserUtils.makeURI(aOrigin);
let host = null;
try {
host = uri.host;

View File

@ -19,10 +19,6 @@ var FullZoom = {
// browser.zoom.updateBackgroundTabs preference cache
updateBackgroundTabs: undefined,
// One of the possible values for the mousewheel.* preferences.
// From EventStateManager.h.
ACTION_ZOOM: 3,
// This maps the browser to monotonically increasing integer
// tokens. _browserTokenMap[browser] is increased each time the zoom is
// changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
@ -49,8 +45,7 @@ var FullZoom = {
// Initialization & Destruction
init: function FullZoom_init() {
// Listen for scrollwheel events so we can save scrollwheel-based changes.
window.addEventListener("DOMMouseScroll", this, false);
gBrowser.addEventListener("ZoomChangeUsingMouseWheel", this);
// Register ourselves with the service so we know when our pref changes.
this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
@ -81,7 +76,7 @@ var FullZoom = {
destroy: function FullZoom_destroy() {
gPrefService.removeObserver("browser.zoom.", this);
this._cps2.removeObserverForName(this.name, this);
window.removeEventListener("DOMMouseScroll", this, false);
gBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this);
},
@ -92,58 +87,14 @@ var FullZoom = {
handleEvent: function FullZoom_handleEvent(event) {
switch (event.type) {
case "DOMMouseScroll":
this._handleMouseScrolled(event);
case "ZoomChangeUsingMouseWheel":
let browser = this._getTargetedBrowser(event);
this._ignorePendingZoomAccesses(browser);
this._applyZoomToPref(browser);
break;
}
},
_handleMouseScrolled: function FullZoom__handleMouseScrolled(event) {
// Construct the "mousewheel action" pref key corresponding to this event.
// Based on EventStateManager::WheelPrefs::GetBasePrefName().
var pref = "mousewheel.";
var pressedModifierCount = event.shiftKey + event.ctrlKey + event.altKey +
event.metaKey + event.getModifierState("OS");
if (pressedModifierCount != 1) {
pref += "default.";
} else if (event.shiftKey) {
pref += "with_shift.";
} else if (event.ctrlKey) {
pref += "with_control.";
} else if (event.altKey) {
pref += "with_alt.";
} else if (event.metaKey) {
pref += "with_meta.";
} else {
pref += "with_win.";
}
pref += "action";
// Don't do anything if this isn't a "zoom" scroll event.
var isZoomEvent = false;
try {
isZoomEvent = (gPrefService.getIntPref(pref) == this.ACTION_ZOOM);
} catch (e) {}
if (!isZoomEvent)
return;
// XXX Lazily cache all the possible action prefs so we don't have to get
// them anew from the pref service for every scroll event? We'd have to
// make sure to observe them so we can update the cache when they change.
// We have to call _applyZoomToPref in a timeout because we handle the
// event before the event state manager has a chance to apply the zoom
// during EventStateManager::PostHandleEvent.
let browser = gBrowser.selectedBrowser;
let token = this._getBrowserToken(browser);
window.setTimeout(function () {
if (token.isCurrent)
this._applyZoomToPref(browser);
}.bind(this), 0);
},
// nsIObserver
observe: function (aSubject, aTopic, aData) {
@ -469,6 +420,30 @@ var FullZoom = {
};
},
/**
* Returns the browser that the supplied zoom event is associated with.
* @param event The ZoomChangeUsingMouseWheel event.
* @return The associated browser element, if one exists, otherwise null.
*/
_getTargetedBrowser: function FullZoom__getTargetedBrowser(event) {
let target = event.originalTarget;
// With remote content browsers, the event's target is the browser
// we're looking for.
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if (target instanceof window.XULElement &&
target.localName == "browser" &&
target.namespaceURI == XUL_NS)
return target;
// With in-process content browsers, the event's target is the content
// document.
if (target.nodeType == Node.DOCUMENT_NODE)
return gBrowser.getBrowserForDocument(target);
throw new Error("Unexpected ZoomChangeUsingMouseWheel event source");
},
/**
* Increments the zoom change token for the given browser so that pending
* async operations know that it may be unsafe to access they zoom when they

View File

@ -23,6 +23,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
"resource://gre/modules/ShortcutUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
"resource://gre/modules/GMPInstallManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
"resource:///modules/ContentSearch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
@ -1294,17 +1296,7 @@ var gBrowserInit = {
if (Win7Features)
Win7Features.onOpenWindow();
// called when we go into full screen, even if initiated by a web page script
window.addEventListener("fullscreen", onFullScreen, true);
// Called when we enter DOM full-screen mode. Note we can already be in browser
// full-screen mode when we enter DOM full-screen mode.
window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
if (window.fullScreen)
onFullScreen();
if (document.mozFullScreen)
onMozEnteredDomFullscreen();
FullScreen.init();
#ifdef MOZ_SERVICES_SYNC
// initialize the sync UI
@ -1435,7 +1427,7 @@ var gBrowserInit = {
gHistorySwipeAnimation.uninit();
FullScreen.cleanup();
FullScreen.uninit();
#ifdef MOZ_SERVICES_SYNC
gFxAccounts.uninit();
@ -2762,14 +2754,6 @@ function SwitchToMetro() {
#endif
}
function onFullScreen(event) {
FullScreen.toggle(event);
}
function onMozEnteredDomFullscreen(event) {
FullScreen.enterDomFullscreen(event);
}
function getWebNavigation()
{
return gBrowser.webNavigation;
@ -3109,7 +3093,7 @@ const BrowserSearch = {
let mm = gBrowser.selectedBrowser.messageManager;
if (url === "about:home") {
AboutHome.focusInput(mm);
} else if (url === "about:newtab") {
} else if (url === "about:newtab" && NewTabUtils.allPages.enabled) {
ContentSearch.focusInput(mm);
} else {
openUILinkIn("about:home", "current");

View File

@ -578,3 +578,40 @@ if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
trHandler = new TranslationContentHandler(global, docShell);
}
let DOMFullscreenHandler = {
_fullscreenDoc: null,
init: function() {
addMessageListener("DOMFullscreen:Approved", this);
addMessageListener("DOMFullscreen:CleanUp", this);
addEventListener("MozEnteredDomFullscreen", this);
},
receiveMessage: function(aMessage) {
switch(aMessage.name) {
case "DOMFullscreen:Approved": {
if (this._fullscreenDoc) {
Services.obs.notifyObservers(this._fullscreenDoc,
"fullscreen-approved",
"");
}
break;
}
case "DOMFullscreen:CleanUp": {
this._fullscreenDoc = null;
break;
}
}
},
handleEvent: function(aEvent) {
if (aEvent.type == "MozEnteredDomFullscreen") {
this._fullscreenDoc = aEvent.target;
sendAsyncMessage("MozEnteredDomFullscreen", {
origin: this._fullscreenDoc.nodePrincipal.origin,
});
}
}
};
DOMFullscreenHandler.init();

View File

@ -1225,6 +1225,15 @@
// If we're using remote tabs, we have to wait until after we've finalized
// switching the tabs.
if (newTab._skipContentFocus) {
// It's possible the tab we're switching to is ready to focus asynchronously,
// when we've already focused something else. In that case, this
// _skipContentFocus property can be set so that we skip focusing the
// content after we switch tabs.
delete newTab._skipContentFocus;
return;
}
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
let focusFlags = fm.FLAG_NOSCROLL;

View File

@ -276,6 +276,8 @@ skip-if = e10s # Bug 921959 - reload with LOAD_FLAGS_ALLOW_MIXED_CONTENT fails i
skip-if = buildapp == "mulet" || e10s # Bug ?????? - test directly manipulates content (strange - gets an element from a child which it tries to treat as a string, but that fails)
[browser_bug970746.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (directly gets elements from the content)
[browser_bug1015721.js]
skip-if = os == 'win' || e10s # Bug 1056146 - FullZoomHelper uses promiseTabLoadEvent() which isn't e10s friendly
[browser_canonizeURL.js]
skip-if = e10s # Bug ?????? - [JavaScript Error: "Error in AboutHome.sendAboutHomeData TypeError: target.messageManager is undefined" {file: "resource:///modules/AboutHome.jsm" line: 208}]
[browser_contentAreaClick.js]

View File

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html";
var gTab1, gTab2, gLevel1;
function test() {
waitForExplicitFinish();
Task.spawn(function () {
gTab1 = gBrowser.addTab();
gTab2 = gBrowser.addTab();
yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
yield FullZoomHelper.load(gTab1, TEST_PAGE);
yield FullZoomHelper.load(gTab2, TEST_PAGE);
}).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
}
function dispatchZoomEventToBrowser(browser) {
EventUtils.synthesizeWheel(browser.contentDocument.documentElement, 10, 10, {
ctrlKey: true, deltaY: -1, deltaMode: WheelEvent.DOM_DELTA_LINE
}, browser.contentWindow);
}
function zoomTab1() {
Task.spawn(function () {
is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
let browser1 = gBrowser.getBrowserForTab(gTab1);
dispatchZoomEventToBrowser(browser1);
gLevel1 = ZoomManager.getZoomForBrowser(browser1);
ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1");
yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
FullZoomHelper.zoomTest(gTab2, gLevel1, "Tab 2 should have zoomed along with tab 1");
}).then(finishTest, FullZoomHelper.failAndContinue(finish));
}
function finishTest() {
Task.spawn(function () {
yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
FullZoom.reset();
yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
FullZoom.reset();
yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
}).then(finish, FullZoomHelper.failAndContinue(finish));
}

View File

@ -205,6 +205,24 @@ function runTests() {
EventUtils.synthesizeKey("k", { accelKey: true });
is(searchBar.textbox.inputField, gWindow.document.activeElement, "Toolbar's search bar should be focused");
// Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if
// the newtab is disabled from `NewTabUtils.allPages.enabled`.
yield addNewTabPageTab();
// Remove the search bar from toolbar
CustomizableUI.removeWidgetFromArea("search-container");
NewTabUtils.allPages.enabled = false;
EventUtils.synthesizeKey("k", { accelKey: true });
let waitEvent = "AboutHomeLoadSnippetsCompleted";
yield promiseTabLoadEvent(gWindow.gBrowser.selectedTab, "about:home", waitEvent).then(TestRunner.next);
is(getContentDocument().documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
let searchInput = getContentDocument().getElementById("searchText");
is(searchInput, getContentDocument().activeElement, "Search input must be the selected element");
NewTabUtils.allPages.enabled = true;
CustomizableUI.reset();
gBrowser.removeCurrentTab();
// Done. Revert the current engine and remove the new engines.
Services.search.currentEngine = oldCurrentEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
@ -414,3 +432,46 @@ function logoImg() {
function gSearch() {
return getContentWindow().gSearch;
}
/**
* Waits for a load (or custom) event to finish in a given tab. If provided
* load an uri into the tab.
*
* @param tab
* The tab to load into.
* @param [optional] url
* The url to load, or the current url.
* @param [optional] event
* The load event type to wait for. Defaults to "load".
* @return {Promise} resolved when the event is handled.
* @resolves to the received event
* @rejects if a valid load event is not received within a meaningful interval
*/
function promiseTabLoadEvent(tab, url, eventType="load") {
let deferred = Promise.defer();
info("Wait tab event: " + eventType);
function handle(event) {
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
event.target.location.href == "about:blank" ||
(url && event.target.location.href != url)) {
info("Skipping spurious '" + eventType + "'' event" +
" for " + event.target.location.href);
return;
}
clearTimeout(timeout);
tab.linkedBrowser.removeEventListener(eventType, handle, true);
info("Tab event received: " + eventType);
deferred.resolve(event);
}
let timeout = setTimeout(() => {
tab.linkedBrowser.removeEventListener(eventType, handle, true);
deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
}, 20000);
tab.linkedBrowser.addEventListener(eventType, handle, true, true);
if (url)
tab.linkedBrowser.loadURI(url);
return deferred.promise;
}

View File

@ -14,7 +14,7 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://browser/content/sanitize.js", tmp);
Cu.import("resource://gre/modules/Timer.jsm", tmp);
let {Promise, NewTabUtils, Sanitizer, clearTimeout, DirectoryLinksProvider} = tmp;
let {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider} = tmp;
let uri = Services.io.newURI("about:newtab", null, null);
let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);

View File

@ -309,6 +309,8 @@ function openLinkIn(url, where, params) {
// result in a new frontmost window (e.g. "javascript:window.open('');").
w.focus();
let newTab;
switch (where) {
case "current":
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
@ -331,23 +333,30 @@ function openLinkIn(url, where, params) {
loadInBackground = !loadInBackground;
// fall through
case "tab":
let browser = w.gBrowser;
browser.loadOneTab(url, {
referrerURI: aReferrerURI,
charset: aCharset,
postData: aPostData,
inBackground: loadInBackground,
allowThirdPartyFixup: aAllowThirdPartyFixup,
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipTabAnimation,
allowMixedContent: aAllowMixedContent });
newTab = w.gBrowser.loadOneTab(url, {
referrerURI: aReferrerURI,
charset: aCharset,
postData: aPostData,
inBackground: loadInBackground,
allowThirdPartyFixup: aAllowThirdPartyFixup,
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipTabAnimation,
allowMixedContent: aAllowMixedContent
});
break;
}
w.gBrowser.selectedBrowser.focus();
if (!loadInBackground && w.isBlankPageURL(url))
if (!loadInBackground && w.isBlankPageURL(url)) {
if (newTab && gMultiProcessBrowser) {
// Remote browsers are switched to asynchronously, and we need to
// ensure that the location bar remains focused in that case rather
// than the content area being focused.
newTab._skipContentFocus = true;
}
w.focusAndSelectUrlBar();
}
}
// Used as an onclick handler for UI elements with link-like behavior.

View File

@ -1320,7 +1320,7 @@ BrowserGlue.prototype = {
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 23;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion = 0;
try {
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
@ -1328,28 +1328,22 @@ BrowserGlue.prototype = {
if (currentUIVersion >= UI_VERSION)
return;
this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
this._dataSource = this._rdf.GetDataSource("rdf:local-store");
this._dirty = false;
let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
if (currentUIVersion < 2) {
// This code adds the customizable bookmarks button.
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Need to migrate only if toolbar is customized and the element is not found.
if (currentset &&
currentset.indexOf("bookmarks-menu-button-container") == -1) {
currentset += ",bookmarks-menu-button-container";
this._setPersist(toolbarResource, currentsetResource, currentset);
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
if (currentUIVersion < 3) {
// This code merges the reload/stop/go button into the url bar.
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Need to migrate only if toolbar is customized and all 3 elements are found.
if (currentset &&
currentset.indexOf("reload-button") != -1 &&
@ -1360,15 +1354,13 @@ BrowserGlue.prototype = {
.replace(/(^|,)stop-button($|,)/, "$1$2")
.replace(/(^|,)urlbar-container($|,)/,
"$1urlbar-container,reload-button,stop-button$2");
this._setPersist(toolbarResource, currentsetResource, currentset);
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
if (currentUIVersion < 4) {
// This code moves the home button to the immediate left of the bookmarks menu button.
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Need to migrate only if toolbar is customized and the elements are found.
if (currentset &&
currentset.indexOf("home-button") != -1 &&
@ -1376,24 +1368,21 @@ BrowserGlue.prototype = {
currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2")
.replace(/(^|,)bookmarks-menu-button-container($|,)/,
"$1home-button,bookmarks-menu-button-container$2");
this._setPersist(toolbarResource, currentsetResource, currentset);
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
if (currentUIVersion < 5) {
// This code uncollapses PersonalToolbar if its collapsed status is not
// persisted, and user customized it or changed default bookmarks.
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "PersonalToolbar");
let collapsedResource = this._rdf.GetResource("collapsed");
let collapsed = this._getPersist(toolbarResource, collapsedResource);
//
// If the user does not have a persisted value for the toolbar's
// "collapsed" attribute, try to determine whether it's customized.
if (collapsed === null) {
if (!xulStore.hasValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed")) {
// We consider the toolbar customized if it has more than
// 3 children, or if it has a persisted currentset value.
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarIsCustomized = !!this._getPersist(toolbarResource,
currentsetResource);
let toolbarIsCustomized = xulStore.hasValue(BROWSER_DOCURL,
"PersonalToolbar", "currentset");
let getToolbarFolderCount = function () {
let toolbarFolder =
PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
@ -1403,7 +1392,7 @@ BrowserGlue.prototype = {
};
if (toolbarIsCustomized || getToolbarFolderCount() > 3) {
this._setPersist(toolbarResource, collapsedResource, "false");
xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
}
}
}
@ -1419,9 +1408,7 @@ BrowserGlue.prototype = {
if (currentUIVersion < 9) {
// This code adds the customizable downloads buttons.
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Since the Downloads button is located in the navigation bar by default,
// migration needs to happen only if the toolbar was customized using a
@ -1442,7 +1429,7 @@ BrowserGlue.prototype = {
currentset = currentset.replace(/(^|,)window-controls($|,)/,
"$1downloads-button,window-controls$2")
}
this._setPersist(toolbarResource, currentsetResource, currentset);
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
@ -1472,15 +1459,13 @@ BrowserGlue.prototype = {
if (currentUIVersion < 12) {
// Remove bookmarks-menu-button-container, then place
// bookmarks-menu-button into its position.
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Need to migrate only if toolbar is customized.
if (currentset) {
if (currentset.contains("bookmarks-menu-button-container")) {
currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
"$1bookmarks-menu-button$2");
this._setPersist(toolbarResource, currentsetResource, currentset);
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
}
@ -1501,20 +1486,16 @@ BrowserGlue.prototype = {
}
if (currentUIVersion < 16) {
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let collapsedResource = this._rdf.GetResource("collapsed");
let isCollapsed = this._getPersist(toolbarResource, collapsedResource);
let isCollapsed = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "collapsed");
if (isCollapsed == "true") {
this._setPersist(toolbarResource, collapsedResource, "false");
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "collapsed", "false");
}
}
// Insert the bookmarks-menu-button into the nav-bar if it isn't already
// there.
if (currentUIVersion < 17) {
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
// Need to migrate only if toolbar is customized.
if (currentset) {
if (!currentset.contains("bookmarks-menu-button")) {
@ -1531,7 +1512,7 @@ BrowserGlue.prototype = {
currentset = currentset.replace(/(^|,)window-controls($|,)/,
"$1bookmarks-menu-button,window-controls$2")
}
this._setPersist(toolbarResource, currentsetResource, currentset);
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
}
@ -1541,12 +1522,8 @@ BrowserGlue.prototype = {
let toolbars = ["navigator-toolbox", "nav-bar", "PersonalToolbar",
"addon-bar", "TabsToolbar", "toolbar-menubar"];
for (let resourceName of ["mode", "iconsize"]) {
let resource = this._rdf.GetResource(resourceName);
for (let toolbarId of toolbars) {
let toolbar = this._rdf.GetResource(BROWSER_DOCURL + toolbarId);
if (this._getPersist(toolbar, resource)) {
this._setPersist(toolbar, resource);
}
xulStore.removeValue(BROWSER_DOCURL, toolbarId, resourceName);
}
}
}
@ -1569,21 +1546,13 @@ BrowserGlue.prototype = {
if (currentUIVersion < 20) {
// Remove persisted collapsed state from TabsToolbar.
let resource = this._rdf.GetResource("collapsed");
let toolbar = this._rdf.GetResource(BROWSER_DOCURL + "TabsToolbar");
if (this._getPersist(toolbar, resource)) {
this._setPersist(toolbar, resource);
}
xulStore.removeValue(BROWSER_DOCURL, "TabsToolbar", "collapsed");
}
if (currentUIVersion < 21) {
// Make sure the 'toolbarbutton-1' class will always be present from here
// on out.
let button = this._rdf.GetResource(BROWSER_DOCURL + "bookmarks-menu-button");
let classResource = this._rdf.GetResource("class");
if (this._getPersist(button, classResource)) {
this._setPersist(button, classResource);
}
xulStore.removeValue(BROWSER_DOCURL, "bookmarks-menu-button", "class");
}
if (currentUIVersion < 22) {
@ -1603,49 +1572,10 @@ BrowserGlue.prototype = {
}
}
if (this._dirty)
this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
delete this._rdf;
delete this._dataSource;
// Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
},
_getPersist: function BG__getPersist(aSource, aProperty) {
var target = this._dataSource.GetTarget(aSource, aProperty, true);
if (target instanceof Ci.nsIRDFLiteral)
return target.Value;
return null;
},
_setPersist: function BG__setPersist(aSource, aProperty, aTarget) {
this._dirty = true;
try {
var oldTarget = this._dataSource.GetTarget(aSource, aProperty, true);
if (oldTarget) {
if (aTarget)
this._dataSource.Change(aSource, aProperty, oldTarget, this._rdf.GetLiteral(aTarget));
else
this._dataSource.Unassert(aSource, aProperty, oldTarget);
}
else {
this._dataSource.Assert(aSource, aProperty, this._rdf.GetLiteral(aTarget), true);
}
// Add the entry to the persisted set for this document if it's not there.
// This code is mostly borrowed from XULDocument::Persist.
let docURL = aSource.ValueUTF8.split("#")[0];
let docResource = this._rdf.GetResource(docURL);
let persistResource = this._rdf.GetResource("http://home.netscape.com/NC-rdf#persist");
if (!this._dataSource.HasAssertion(docResource, persistResource, aSource, true)) {
this._dataSource.Assert(docResource, persistResource, aSource, true);
}
}
catch(ex) {}
},
// ------------------------------
// public nsIBrowserGlue members
// ------------------------------

View File

@ -1020,10 +1020,6 @@ XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
"@mozilla.org/rdf/rdf-service;1",
"nsIRDFService");
XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() {
return PlacesUIUtils.RDF.GetDataSource("rdf:local-store");
});
XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
return Services.prefs.getComplexValue("intl.ellipsis",
Ci.nsIPrefLocalizedString).data;

View File

@ -23,6 +23,14 @@ function PlacesTreeView(aFlatList, aOnOpenFlatContainer, aController) {
PlacesTreeView.prototype = {
get wrappedJSObject() this,
__xulStore: null,
get _xulStore() {
if (!this.__xulStore) {
this.__xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
}
return this.__xulStore;
},
__dateService: null,
get _dateService() {
if (!this.__dateService) {
@ -307,11 +315,15 @@ PlacesTreeView.prototype = {
if (!this._flatList &&
curChild instanceof Ci.nsINavHistoryContainerResultNode &&
!this._controller.hasCachedLivemarkInfo(curChild)) {
let resource = this._getResourceForNode(curChild);
let isopen = resource != null &&
PlacesUIUtils.localStore.HasAssertion(resource,
openLiteral,
trueLiteral, true);
let uri = curChild.uri;
let isopen = false;
if (uri) {
let docURI = this._getDocumentURI();
let val = this._xulStore.getValue(docURI, uri, "open");
isopen = (val == "true");
}
if (isopen != curChild.containerOpen)
aToOpen.push(curChild);
else if (curChild.containerOpen && curChild.childCount > 0)
@ -1109,11 +1121,16 @@ PlacesTreeView.prototype = {
return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
},
_getResourceForNode: function PTV_getResourceForNode(aNode)
// Retrieves an nsIURI for the document
_documentURI: null,
_getDocumentURI: function()
{
let uri = aNode.uri;
NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
if (!this._documentURI) {
let ioService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
this._documentURI = ioService.newURI(document.URL, null, null);
}
return this._documentURI;
},
// nsITreeView
@ -1497,15 +1514,16 @@ PlacesTreeView.prototype = {
// Persist containers open status, but never persist livemarks.
if (!this._controller.hasCachedLivemarkInfo(node)) {
let resource = this._getResourceForNode(node);
if (resource) {
const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
let uri = node.uri;
if (node.containerOpen)
PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
else
PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
if (uri) {
let docURI = this._getDocumentURI();
if (node.containerOpen) {
this._xulStore.removeValue(docURI, uri, "open");
} else {
this._xulStore.setValue(docURI, uri, "open", "true");
}
}
}

View File

@ -5,62 +5,25 @@
/**
* Tests PersonalToolbar migration path.
*/
let bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
let gOriginalMigrationVersion;
const BROWSER_URL = getBrowserURL();
let localStore = {
get RDF() Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService),
get store() this.RDF.GetDataSource("rdf:local-store"),
get xulStore() Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore),
get toolbar()
getValue: function getValue(aProperty)
{
delete this.toolbar;
let toolbar = this.RDF.GetResource(BROWSER_URL + "#PersonalToolbar");
// Add the entry to the persisted set for this document if it's not there.
// See XULDocument::Persist.
let doc = this.RDF.GetResource(BROWSER_URL);
let persist = this.RDF.GetResource("http://home.netscape.com/NC-rdf#persist");
if (!this.store.HasAssertion(doc, persist, toolbar, true)) {
this.store.Assert(doc, persist, toolbar, true);
}
return this.toolbar = toolbar;
return this.xulStore.getValue(BROWSER_URL, "PersonalToolbar", aProperty);
},
getPersist: function getPersist(aProperty)
setValue: function setValue(aProperty, aValue)
{
let property = this.RDF.GetResource(aProperty);
let target = this.store.GetTarget(this.toolbar, property, true);
if (target instanceof Ci.nsIRDFLiteral)
return target.Value;
return null;
},
setPersist: function setPersist(aProperty, aValue)
{
let property = this.RDF.GetResource(aProperty);
let value = aValue ? this.RDF.GetLiteral(aValue) : null;
try {
let oldTarget = this.store.GetTarget(this.toolbar, property, true);
if (oldTarget && value) {
this.store.Change(this.toolbar, property, oldTarget, value);
}
else if (value) {
this.store.Assert(this.toolbar, property, value, true);
}
else if (oldTarget) {
this.store.Unassert(this.toolbar, property, oldTarget);
}
else {
return;
}
if (aValue) {
this.xulStore.setValue(BROWSER_URL, "PersonalToolbar", aProperty, aValue);
} else {
this.xulStore.removeValue(BROWSER_URL, "PersonalToolbar", aProperty);
}
catch(ex) {
return;
}
this.store.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
}
};
@ -69,17 +32,17 @@ let gTests = [
function test_explicitly_collapsed_toolbar()
{
info("An explicitly collapsed toolbar should not be uncollapsed.");
localStore.setPersist("collapsed", "true");
localStore.setValue("collapsed", "true");
bg.observe(null, "browser-glue-test", "force-ui-migration");
is(localStore.getPersist("collapsed"), "true", "Toolbar is collapsed");
is(localStore.getValue("collapsed"), "true", "Toolbar is collapsed");
},
function test_customized_toolbar()
{
info("A customized toolbar should be uncollapsed.");
localStore.setPersist("currentset", "splitter");
localStore.setValue("currentset", "splitter");
bg.observe(null, "browser-glue-test", "force-ui-migration");
is(localStore.getPersist("collapsed"), "false", "Toolbar has been uncollapsed");
is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
},
function test_many_bookmarks_toolbar()
@ -98,8 +61,12 @@ function test_many_bookmarks_toolbar()
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX)
);
ids.push(
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX)
);
bg.observe(null, "browser-glue-test", "force-ui-migration");
is(localStore.getPersist("collapsed"), "false", "Toolbar has been uncollapsed");
is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
},
];
@ -109,14 +76,14 @@ function test()
gOriginalMigrationVersion = Services.prefs.getIntPref("browser.migration.version");
registerCleanupFunction(clean);
if (localStore.getPersist("currentset") !== null) {
if (localStore.getValue("currentset") !== null) {
info("Toolbar currentset was persisted by a previous test, fixing it.");
localStore.setPersist("currentset", null);
localStore.setValue("currentset", null);
}
if (localStore.getPersist("collapsed") !== null) {
if (localStore.getValue("collapsed") !== null) {
info("Toolbar collapsed status was persisted by a previous test, fixing it.");
localStore.setPersist("collapsed", null);
localStore.setValue("collapsed", null);
}
while (gTests.length) {
@ -129,7 +96,7 @@ function test()
function clean()
{
Services.prefs.setIntPref("browser.migration.version", gOriginalMigrationVersion);
localStore.setPersist("currentset", null);
localStore.setPersist("collapsed", null);
localStore.setValue("currentset", null);
localStore.setValue("collapsed", null);
}

View File

@ -7,24 +7,75 @@ this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/Chr
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
"resource://gre/modules/FormHistory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
function test() {
waitForExplicitFinish();
function expectedURL(aSearchTerms) {
const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html";
var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
getService(Ci.nsITextToSubURI);
var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
return ENGINE_HTML_BASE + "?test=" + searchArg;
}
var searchEntries = ["test", "More Text", "Some Text"];
var searchBar = BrowserSearch.searchBar;
var searchButton = document.getAnonymousElementByAttribute(searchBar,
"anonid", "search-go-button");
ok(searchButton, "got search-go-button");
function simulateClick(aEvent, aTarget) {
var event = document.createEvent("MouseEvent");
var ctrlKeyArg = aEvent.ctrlKey || false;
var altKeyArg = aEvent.altKey || false;
var shiftKeyArg = aEvent.shiftKey || false;
var metaKeyArg = aEvent.metaKey || false;
var buttonArg = aEvent.button || 0;
event.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0,
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
buttonArg, null);
aTarget.dispatchEvent(event);
}
searchBar.value = "test";
// modified from toolkit/components/satchel/test/test_form_autocomplete.html
function checkMenuEntries(expectedValues) {
var actualValues = getMenuEntries();
is(actualValues.length, expectedValues.length, "Checking length of expected menu");
for (var i = 0; i < expectedValues.length; i++)
is(actualValues[i], expectedValues[i], "Checking menu entry #" + i);
}
function getMenuEntries() {
var entries = [];
var autocompleteMenu = searchBar.textbox.popup;
// Could perhaps pull values directly from the controller, but it seems
// more reliable to test the values that are actually in the tree?
var column = autocompleteMenu.tree.columns[0];
var numRows = autocompleteMenu.tree.view.rowCount;
for (var i = 0; i < numRows; i++) {
entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
}
return entries;
}
function* countEntries(name, value) {
let deferred = Promise.defer();
let count = 0;
let obj = name && value ? {fieldname: name, value: value} : {};
FormHistory.count(obj,
{ handleResult: function(result) { count = result; },
handleError: function(error) { throw error; },
handleCompletion: function(reason) {
if (!reason) {
deferred.resolve(count);
}
}
});
return deferred.promise;
}
var searchBar;
var searchButton;
var searchEntries = ["test", "More Text", "Some Text"];
function* promiseSetEngine() {
let deferred = Promise.defer();
var ss = Services.search;
let testIterator;
function observer(aSub, aTopic, aData) {
switch (aData) {
case "engine-added":
@ -34,266 +85,221 @@ function test() {
break;
case "engine-current":
ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
testReturn();
break;
case "engine-removed":
searchBar = BrowserSearch.searchBar;
searchButton = document.getAnonymousElementByAttribute(searchBar,
"anonid", "search-go-button");
ok(searchButton, "got search-go-button");
searchBar.value = "test";
Services.obs.removeObserver(observer, "browser-search-engine-modified");
finish();
deferred.resolve();
break;
}
}
};
Services.obs.addObserver(observer, "browser-search-engine-modified", false);
ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00",
false);
var preSelectedBrowser, preTabNo;
function init() {
preSelectedBrowser = gBrowser.selectedBrowser;
preTabNo = gBrowser.tabs.length;
searchBar.focus();
}
function testReturn() {
init();
EventUtils.synthesizeKey("VK_RETURN", {});
doOnloadOnce(function(event) {
is(gBrowser.tabs.length, preTabNo, "Return key did not open new tab");
is(event.originalTarget, preSelectedBrowser.contentDocument,
"Return key loaded results in current tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testReturn opened correct search page");
testAltReturn();
});
}
function testAltReturn() {
init();
EventUtils.synthesizeKey("VK_RETURN", { altKey: true });
doOnloadOnce(function(event) {
is(gBrowser.tabs.length, preTabNo + 1, "Alt+Return key added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"Alt+Return key loaded results in new tab");
is(event.originalTarget, gBrowser.contentDocument,
"Alt+Return key loaded results in foreground tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testAltReturn opened correct search page");
//Shift key has no effect for now, so skip it
//testShiftAltReturn();
testLeftClick();
});
}
function testShiftAltReturn() {
init();
EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
doOnloadOnce(function(event) {
is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"Shift+Alt+Return key loaded results in new tab");
isnot(event.originalTarget, gBrowser.contentDocument,
"Shift+Alt+Return key loaded results in background tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftAltReturn opened correct search page");
testLeftClick();
});
}
function testLeftClick() {
init();
simulateClick({ button: 0 }, searchButton);
doOnloadOnce(function(event) {
is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
is(event.originalTarget, preSelectedBrowser.contentDocument,
"LeftClick loaded results in current tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testLeftClick opened correct search page");
testMiddleClick();
});
}
function testMiddleClick() {
init();
simulateClick({ button: 1 }, searchButton);
doOnloadOnce(function(event) {
is(gBrowser.tabs.length, preTabNo + 1, "MiddleClick added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"MiddleClick loaded results in new tab");
is(event.originalTarget, gBrowser.contentDocument,
"MiddleClick loaded results in foreground tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testMiddleClick opened correct search page");
testShiftMiddleClick();
});
}
function testShiftMiddleClick() {
init();
simulateClick({ button: 1, shiftKey: true }, searchButton);
doOnloadOnce(function(event) {
is(gBrowser.tabs.length, preTabNo + 1, "Shift+MiddleClick added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"Shift+MiddleClick loaded results in new tab");
isnot(event.originalTarget, gBrowser.contentDocument,
"Shift+MiddleClick loaded results in background tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftMiddleClick opened correct search page");
testDropText();
});
}
// prevent the search buttonmenu from opening during the drag tests
function stopPopup(event) { event.preventDefault(); }
function testDropText() {
init();
searchBar.addEventListener("popupshowing", stopPopup, true);
// drop on the search button so that we don't need to worry about the
// default handlers for textboxes.
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/plain", data: "Some Text" } ]], "copy", window);
doOnloadOnce(function(event) {
is(searchBar.value, "Some Text", "drop text/plain on searchbar");
testDropInternalText();
});
}
function testDropInternalText() {
init();
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/x-moz-text-internal", data: "More Text" } ]], "copy", window);
doOnloadOnce(function(event) {
is(searchBar.value, "More Text", "drop text/x-moz-text-internal on searchbar");
testDropLink();
});
}
function testDropLink() {
init();
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/uri-list", data: "http://www.mozilla.org" } ]], "copy", window);
is(searchBar.value, "More Text", "drop text/uri-list on searchbar");
SimpleTest.executeSoon(testRightClick);
}
function testRightClick() {
init();
searchBar.removeEventListener("popupshowing", stopPopup, true);
content.location.href = "about:blank";
simulateClick({ button: 2 }, searchButton);
setTimeout(function() {
is(gBrowser.tabs.length, preTabNo, "RightClick did not open new tab");
is(gBrowser.currentURI.spec, "about:blank", "RightClick did nothing");
testIterator = testSearchHistory();
testIterator.next();
}, 5000);
}
function countEntries(name, value, message) {
let count = 0;
FormHistory.count({ fieldname: name, value: value },
{ handleResult: function(result) { count = result; },
handleError: function(error) { throw error; },
handleCompletion: function(reason) {
if (!reason) {
ok(count > 0, message);
testIterator.next();
}
}
});
}
function testSearchHistory() {
var textbox = searchBar._textbox;
for (var i = 0; i < searchEntries.length; i++) {
yield countEntries(textbox.getAttribute("autocompletesearchparam"), searchEntries[i],
"form history entry '" + searchEntries[i] + "' should exist");
}
testAutocomplete();
}
function testAutocomplete() {
var popup = searchBar.textbox.popup;
popup.addEventListener("popupshown", function testACPopupShowing() {
popup.removeEventListener("popupshown", testACPopupShowing);
checkMenuEntries(searchEntries);
testClearHistory();
});
searchBar.textbox.showHistoryPopup();
}
function testClearHistory() {
let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
controller.doCommand("cmd_clearhistory");
let count = 0;
FormHistory.count({ },
{ handleResult: function(result) { count = result; },
handleError: function(error) { throw error; },
handleCompletion: function(reason) {
if (!reason) {
ok(count == 0, "History cleared");
finalize();
}
}
});
}
function finalize() {
searchBar.value = "";
while (gBrowser.tabs.length != 1) {
gBrowser.removeTab(gBrowser.tabs[0]);
}
content.location.href = "about:blank";
var engine = ss.getEngineByName("Bug 426329");
ss.removeEngine(engine);
}
function simulateClick(aEvent, aTarget) {
var event = document.createEvent("MouseEvent");
var ctrlKeyArg = aEvent.ctrlKey || false;
var altKeyArg = aEvent.altKey || false;
var shiftKeyArg = aEvent.shiftKey || false;
var metaKeyArg = aEvent.metaKey || false;
var buttonArg = aEvent.button || 0;
event.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0,
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
buttonArg, null);
aTarget.dispatchEvent(event);
}
function expectedURL(aSearchTerms) {
var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
getService(Ci.nsITextToSubURI);
var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
return ENGINE_HTML_BASE + "?test=" + searchArg;
}
// modified from toolkit/components/satchel/test/test_form_autocomplete.html
function checkMenuEntries(expectedValues) {
var actualValues = getMenuEntries();
is(actualValues.length, expectedValues.length, "Checking length of expected menu");
for (var i = 0; i < expectedValues.length; i++)
is(actualValues[i], expectedValues[i], "Checking menu entry #" + i);
}
function getMenuEntries() {
var entries = [];
var autocompleteMenu = searchBar.textbox.popup;
// Could perhaps pull values directly from the controller, but it seems
// more reliable to test the values that are actually in the tree?
var column = autocompleteMenu.tree.columns[0];
var numRows = autocompleteMenu.tree.view.rowCount;
for (var i = 0; i < numRows; i++) {
entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
}
return entries;
}
return deferred.promise;
}
function* promiseRemoveEngine() {
let deferred = Promise.defer();
var ss = Services.search;
function observer(aSub, aTopic, aData) {
if (aData == "engine-removed") {
Services.obs.removeObserver(observer, "browser-search-engine-modified");
deferred.resolve();
}
};
Services.obs.addObserver(observer, "browser-search-engine-modified", false);
var engine = ss.getEngineByName("Bug 426329");
ss.removeEngine(engine);
return deferred.promise;
}
var preSelectedBrowser;
var preTabNo;
function* prepareTest() {
preSelectedBrowser = gBrowser.selectedBrowser;
preTabNo = gBrowser.tabs.length;
searchBar = BrowserSearch.searchBar;
let windowFocused = Promise.defer();
SimpleTest.waitForFocus(windowFocused.resolve, window);
yield windowFocused.promise;
let deferred = Promise.defer();
if (document.activeElement != searchBar) {
searchBar.addEventListener("focus", function onFocus() {
searchBar.removeEventListener("focus", onFocus);
deferred.resolve();
});
searchBar.focus();
} else {
deferred.resolve();
}
return deferred.promise;
}
add_task(function testSetupEngine() {
yield promiseSetEngine();
});
add_task(function testReturn() {
yield prepareTest();
EventUtils.synthesizeKey("VK_RETURN", {});
let event = yield promiseOnLoad();
is(gBrowser.tabs.length, preTabNo, "Return key did not open new tab");
is(event.originalTarget, preSelectedBrowser.contentDocument,
"Return key loaded results in current tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testReturn opened correct search page");
});
add_task(function testAltReturn() {
yield prepareTest();
EventUtils.synthesizeKey("VK_RETURN", { altKey: true });
let event = yield promiseOnLoad();
is(gBrowser.tabs.length, preTabNo + 1, "Alt+Return key added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"Alt+Return key loaded results in new tab");
is(event.originalTarget, gBrowser.contentDocument,
"Alt+Return key loaded results in foreground tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testAltReturn opened correct search page");
});
//Shift key has no effect for now, so skip it
add_task(function testShiftAltReturn() {
return;
yield prepareTest();
EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
let event = yield promiseOnLoad();
is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"Shift+Alt+Return key loaded results in new tab");
isnot(event.originalTarget, gBrowser.contentDocument,
"Shift+Alt+Return key loaded results in background tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftAltReturn opened correct search page");
});
add_task(function testLeftClick() {
yield prepareTest();
simulateClick({ button: 0 }, searchButton);
let event = yield promiseOnLoad();
is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
is(event.originalTarget, preSelectedBrowser.contentDocument,
"LeftClick loaded results in current tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testLeftClick opened correct search page");
});
add_task(function testMiddleClick() {
yield prepareTest();
simulateClick({ button: 1 }, searchButton);
let event = yield promiseOnLoad();
is(gBrowser.tabs.length, preTabNo + 1, "MiddleClick added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"MiddleClick loaded results in new tab");
is(event.originalTarget, gBrowser.contentDocument,
"MiddleClick loaded results in foreground tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testMiddleClick opened correct search page");
});
add_task(function testShiftMiddleClick() {
yield prepareTest();
simulateClick({ button: 1, shiftKey: true }, searchButton);
let event = yield promiseOnLoad();
is(gBrowser.tabs.length, preTabNo + 1, "Shift+MiddleClick added new tab");
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
"Shift+MiddleClick loaded results in new tab");
isnot(event.originalTarget, gBrowser.contentDocument,
"Shift+MiddleClick loaded results in background tab");
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftMiddleClick opened correct search page");
});
add_task(function testDropText() {
yield prepareTest();
let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
// drop on the search button so that we don't need to worry about the
// default handlers for textboxes.
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/plain", data: "Some Text" } ]], "copy", window);
yield promisePreventPopup;
let event = yield promiseOnLoad();
is(event.originalTarget.URL, expectedURL(searchBar.value), "testDropText opened correct search page");
is(searchBar.value, "Some Text", "drop text/plain on searchbar");
});
add_task(function testDropInternalText() {
yield prepareTest();
let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/x-moz-text-internal", data: "More Text" } ]], "copy", window);
yield promisePreventPopup;
let event = yield promiseOnLoad();
is(event.originalTarget.URL, expectedURL(searchBar.value), "testDropInternalText opened correct search page");
is(searchBar.value, "More Text", "drop text/x-moz-text-internal on searchbar");
// testDropLink implicitly depended on testDropInternalText, so these two tests
// were merged so that if testDropInternalText failed it wouldn't cause testDropLink
// to fail unexplainably.
yield prepareTest();
let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/uri-list", data: "http://www.mozilla.org" } ]], "copy", window);
yield promisePreventPopup;
is(searchBar.value, "More Text", "drop text/uri-list on searchbar shouldn't change anything");
});
add_task(function testRightClick() {
preTabNo = gBrowser.tabs.length;
content.location.href = "about:blank";
simulateClick({ button: 2 }, searchButton);
let deferred = Promise.defer();
setTimeout(function() {
is(gBrowser.tabs.length, preTabNo, "RightClick did not open new tab");
is(gBrowser.currentURI.spec, "about:blank", "RightClick did nothing");
deferred.resolve();
}, 5000);
yield deferred.promise;
});
add_task(function testSearchHistory() {
var textbox = searchBar._textbox;
for (var i = 0; i < searchEntries.length; i++) {
let count = yield countEntries(textbox.getAttribute("autocompletesearchparam"), searchEntries[i]);
ok(count > 0, "form history entry '" + searchEntries[i] + "' should exist");
}
});
add_task(function testAutocomplete() {
var popup = searchBar.textbox.popup;
let popupShownPromise = promiseEvent(popup, "popupshown");
searchBar.textbox.showHistoryPopup();
yield popupShownPromise;
checkMenuEntries(searchEntries);
});
add_task(function testClearHistory() {
let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
controller.doCommand("cmd_clearhistory");
let count = yield countEntries();
ok(count == 0, "History cleared");
});
add_task(function asyncCleanup() {
searchBar.value = "";
while (gBrowser.tabs.length != 1) {
gBrowser.removeTab(gBrowser.tabs[0], {animate: false});
}
content.location.href = "about:blank";
yield promiseRemoveEngine();
});

View File

@ -1,6 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
function whenNewWindowLoaded(aOptions, aCallback) {
let win = OpenBrowserWindow(aOptions);
let gotLoad = false;
@ -88,6 +91,18 @@ function waitForPopupShown(aPopupId, aCallback) {
registerCleanupFunction(removePopupShownListener);
}
function* promiseEvent(aTarget, aEventName, aPreventDefault) {
let deferred = Promise.defer();
aTarget.addEventListener(aEventName, function onEvent(aEvent) {
aTarget.removeEventListener(aEventName, onEvent, true);
if (aPreventDefault) {
aEvent.preventDefault();
}
deferred.resolve();
}, true);
return deferred.promise;
}
function waitForBrowserContextMenu(aCallback) {
waitForPopupShown(gBrowser.selectedBrowser.contextMenu, aCallback);
}
@ -106,3 +121,16 @@ function doOnloadOnce(aCallback) {
gBrowser.addEventListener("load", doOnloadOnceListener, true);
registerCleanupFunction(removeDoOnloadOnceListener);
}
function* promiseOnLoad() {
let deferred = Promise.defer();
gBrowser.addEventListener("load", function onLoadListener(aEvent) {
info("onLoadListener: " + aEvent.originalTarget.location);
gBrowser.removeEventListener("load", onLoadListener, true);
deferred.resolve(aEvent);
}, true);
return deferred.promise;
}

View File

@ -22,13 +22,13 @@ const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
const MAX_ORDINAL = 99;
/**
* DevTools is a class that represents a set of developer tools, it holds a
* set of tools and keeps track of open toolboxes in the browser.
*/
this.DevTools = function DevTools() {
this._tools = new Map(); // Map<toolId, tool>
this._themes = new Map(); // Map<themeId, theme>
this._toolboxes = new Map(); // Map<target, toolbox>
// destroy() is an observer's handler so we need to preserve context.
@ -229,6 +229,136 @@ DevTools.prototype = {
return definitions.sort(this.ordinalSort);
},
/**
* Register a new theme for developer tools toolbox.
*
* A definition is a light object that holds various information about a
* theme.
*
* Each themeDefinition has the following properties:
* - id: Unique identifier for this theme (string|required)
* - label: Localized name for the theme to be displayed to the user
* (string|required)
* - stylesheets: Array of URLs pointing to a CSS document(s) containing
* the theme style rules (array|required)
* - classList: Array of class names identifying the theme within a document.
* These names are set to document element when applying
* the theme (array|required)
* - onApply: Function that is executed by the framework when the theme
* is applied. The function takes the current iframe window
* and the previous theme id as arguments (function)
* - onUnapply: Function that is executed by the framework when the theme
* is unapplied. The function takes the current iframe window
* and the new theme id as arguments (function)
*/
registerTheme: function DT_registerTheme(themeDefinition) {
let themeId = themeDefinition.id;
if (!themeId) {
throw new Error("Invalid theme id");
}
if (this._themes.get(themeId)) {
throw new Error("Theme with the same id is already registered");
}
this._themes.set(themeId, themeDefinition);
this.emit("theme-registered", themeId);
},
/**
* Removes an existing theme from the list of registered themes.
* Needed so that add-ons can remove themselves when they are deactivated
*
* @param {string|object} theme
* Definition or the id of the theme to unregister.
*/
unregisterTheme: function DT_unregisterTheme(theme) {
let themeId = null;
if (typeof theme == "string") {
themeId = theme;
theme = this._themes.get(theme);
}
else {
themeId = theme.id;
}
let currTheme = Services.prefs.getCharPref("devtools.theme");
// Change the current theme if it's being dynamically removed together
// with the owner (bootstrapped) extension.
// But, do not change it if the application is just shutting down.
if (!Services.startup.shuttingDown && theme.id == currTheme) {
Services.prefs.setCharPref("devtools.theme", "light");
let data = {
pref: "devtools.theme",
newValue: "light",
oldValue: currTheme
};
gDevTools.emit("pref-changed", data);
this.emit("theme-unregistered", theme);
}
this._themes.delete(themeId);
},
/**
* Get a theme definition if it exists.
*
* @param {string} themeId
* The id of the theme
*
* @return {ThemeDefinition|null} theme
* The ThemeDefinition for the id or null.
*/
getThemeDefinition: function DT_getThemeDefinition(themeId) {
let theme = this._themes.get(themeId);
if (!theme) {
return null;
}
return theme;
},
/**
* Get map of registered themes.
*
* @return {Map} themes
* A map of the the theme definitions registered in this instance
*/
getThemeDefinitionMap: function DT_getThemeDefinitionMap() {
let themes = new Map();
for (let [id, definition] of this._themes) {
if (this.getThemeDefinition(id)) {
themes.set(id, definition);
}
}
return themes;
},
/**
* Get registered themes definitions sorted by ordinal value.
*
* @return {Array} themes
* A sorted array of the theme definitions registered in this instance
*/
getThemeDefinitionArray: function DT_getThemeDefinitionArray() {
let definitions = [];
for (let [id, definition] of this._themes) {
if (this.getThemeDefinition(id)) {
definitions.push(definition);
}
}
return definitions.sort(this.ordinalSort);
},
/**
* Show a Toolbox for a target (either by creating a new one, or if a toolbox
* already exists for the target, by bring to the front the existing one)

View File

@ -5,6 +5,7 @@ support-files =
browser_toolbox_options_disable_js_iframe.html
browser_toolbox_options_disable_cache.sjs
head.js
doc_theme.css
[browser_devtools_api.js]
[browser_dynamic_tool_enabling.js]
@ -32,6 +33,7 @@ skip-if = e10s # Bug 1030318
[browser_toolbox_window_title_changes.js]
[browser_toolbox_zoom.js]
[browser_toolbox_custom_host.js]
[browser_toolbox_theme_registration.js]
# We want this test to run for mochitest-dt as well, so we include it here:
[../../../base/content/test/general/browser_parsable_css.js]

View File

@ -0,0 +1,113 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const CHROME_URL = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
let toolbox;
function test()
{
gBrowser.selectedTab = gBrowser.addTab();
let target = TargetFactory.forTab(gBrowser.selectedTab);
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
gDevTools.showToolbox(target).then(testRegister);
}, true);
content.location = "data:text/html,test for dynamically registering and unregistering themes";
}
function testRegister(aToolbox)
{
toolbox = aToolbox
gDevTools.once("theme-registered", themeRegistered);
gDevTools.registerTheme({
id: "test-theme",
label: "Test theme",
stylesheets: [CHROME_URL + "doc_theme.css"],
classList: ["theme-test"],
});
}
function themeRegistered(event, themeId)
{
is(themeId, "test-theme", "theme-registered event handler sent theme id");
ok(gDevTools.getThemeDefinitionMap().has(themeId), "theme added to map");
// Test that new theme appears in the Options panel
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "options").then(() => {
let panel = toolbox.getCurrentPanel();
let doc = panel.panelWin.frameElement.contentDocument;
let themeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
ok(themeOption, "new theme exists in the Options panel");
// Apply the new theme.
applyTheme();
});
}
function applyTheme()
{
let panelWin = toolbox.getCurrentPanel().panelWin;
let doc = panelWin.frameElement.contentDocument;
let testThemeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
let lightThemeOption = doc.querySelector("#devtools-theme-box > radio[value=light]");
let color = panelWin.getComputedStyle(testThemeOption).color;
isnot(color, "rgb(255, 0, 0)", "style unapplied");
// Select test theme.
testThemeOption.click();
let color = panelWin.getComputedStyle(testThemeOption).color;
is(color, "rgb(255, 0, 0)", "style applied");
// Select light theme
lightThemeOption.click();
let color = panelWin.getComputedStyle(testThemeOption).color;
isnot(color, "rgb(255, 0, 0)", "style unapplied");
// Select test theme again.
testThemeOption.click();
// Then unregister the test theme.
testUnregister();
}
function testUnregister()
{
gDevTools.unregisterTheme("test-theme");
ok(!gDevTools.getThemeDefinitionMap().has("test-theme"), "theme removed from map");
let panelWin = toolbox.getCurrentPanel().panelWin;
let doc = panelWin.frameElement.contentDocument;
let themeBox = doc.querySelector("#devtools-theme-box");
// The default light theme must be selected now.
is(themeBox.selectedItem, themeBox.querySelector("[value=light]"),
"theme light must be selected");
// Make sure the tab-attaching process is done before we destroy the toolbox.
let target = TargetFactory.forTab(gBrowser.selectedTab);
let actor = target.activeTab.actor;
target.client.attachTab(actor, (response) => {
cleanup();
});
}
function cleanup()
{
toolbox.destroy().then(function() {
toolbox = null;
gBrowser.removeCurrentTab();
finish();
});
}

View File

@ -0,0 +1,3 @@
.theme-test #devtools-theme-box radio {
color: red !important;
}

View File

@ -75,6 +75,8 @@ function OptionsPanel(iframeWindow, toolbox) {
this.isReady = false;
this._prefChanged = this._prefChanged.bind(this);
this._themeRegistered = this._themeRegistered.bind(this);
this._themeUnregistered = this._themeUnregistered.bind(this);
this._addListeners();
@ -101,7 +103,9 @@ OptionsPanel.prototype = {
return targetPromise.then(() => {
this.setupToolsList();
this.setupToolbarButtonsList();
this.setupThemeList();
this.populatePreferences();
this.updateDefaultTheme();
this._disableJSClicked = this._disableJSClicked.bind(this);
@ -119,10 +123,14 @@ OptionsPanel.prototype = {
_addListeners: function() {
gDevTools.on("pref-changed", this._prefChanged);
gDevTools.on("theme-registered", this._themeRegistered);
gDevTools.on("theme-unregistered", this._themeUnregistered);
},
_removeListeners: function() {
gDevTools.off("pref-changed", this._prefChanged);
gDevTools.off("theme-registered", this._themeRegistered);
gDevTools.off("theme-unregistered", this._themeUnregistered);
},
_prefChanged: function(event, data) {
@ -132,6 +140,22 @@ OptionsPanel.prototype = {
cbx.checked = cacheDisabled;
}
else if (data.pref === "devtools.theme") {
this.updateCurrentTheme();
}
},
_themeRegistered: function(event, themeId) {
this.setupThemeList();
},
_themeUnregistered: function(event, theme) {
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
let themeOption = themeBox.querySelector("[value=" + theme.id + "]");
if (themeOption) {
themeBox.removeChild(themeOption);
}
},
setupToolbarButtonsList: function() {
@ -229,6 +253,26 @@ OptionsPanel.prototype = {
this.panelWin.focus();
},
setupThemeList: function() {
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
themeBox.textContent = "";
let createThemeOption = theme => {
let radio = this.panelDoc.createElement("radio");
radio.setAttribute("value", theme.id);
radio.setAttribute("label", theme.label);
return radio;
};
// Populating the default theme list
let themes = gDevTools.getThemeDefinitionArray();
for (let theme of themes) {
themeBox.appendChild(createThemeOption(theme));
}
this.updateCurrentTheme();
},
populatePreferences: function() {
let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
for (let checkbox of prefCheckboxes) {
@ -258,9 +302,13 @@ OptionsPanel.prototype = {
pref: this.getAttribute("data-pref"),
newValue: this.selectedItem.getAttribute("value")
};
data.oldValue = GetPref(data.pref);
SetPref(data.pref, data.newValue);
gDevTools.emit("pref-changed", data);
if (data.newValue != data.oldValue) {
gDevTools.emit("pref-changed", data);
}
}.bind(radiogroup));
}
let prefMenulists = this.panelDoc.querySelectorAll("menulist[data-pref]");
@ -292,6 +340,25 @@ OptionsPanel.prototype = {
});
},
updateDefaultTheme: function() {
// Make sure a theme is set in case the previous one coming from
// an extension isn't available anymore.
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
if (themeBox.selectedIndex == -1) {
themeBox.selectedItem = themeBox.querySelector("[value=light]");
}
},
updateCurrentTheme: function() {
let currentTheme = GetPref("devtools.theme");
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
let themeOption = themeBox.querySelector("[value=" + currentTheme + "]");
if (themeOption) {
themeBox.selectedItem = themeOption;
}
},
_populateDisableJSCheckbox: function() {
let cbx = this.panelDoc.getElementById("devtools-disable-javascript");
cbx.checked = !this._origJavascriptEnabled;

View File

@ -33,8 +33,6 @@
class="options-groupbox"
data-pref="devtools.theme"
orient="horizontal">
<radio value="light" label="&options.lightTheme.label;"/>
<radio value="dark" label="&options.darkTheme.label;"/>
</radiogroup>
<label>&options.commonPrefs.label;</label>
<vbox id="commonprefs-options" class="options-groupbox">

View File

@ -357,6 +357,31 @@ for (let definition of defaultTools) {
gDevTools.registerTool(definition);
}
Tools.darkTheme = {
id: "dark",
label: l10n("options.darkTheme.label", toolboxStrings),
ordinal: 1,
stylesheets: ["chrome://browser/skin/devtools/dark-theme.css"],
classList: ["theme-dark"],
};
Tools.lightTheme = {
id: "light",
label: l10n("options.lightTheme.label", toolboxStrings),
ordinal: 2,
stylesheets: ["chrome://browser/skin/devtools/light-theme.css"],
classList: ["theme-light"],
};
let defaultThemes = [
Tools.darkTheme,
Tools.lightTheme,
];
for (let definition of defaultThemes) {
gDevTools.registerTheme(definition);
}
var unloadObserver = {
observe: function(subject, topic, data) {
if (subject.wrappedJSObject === require("@loader/unload")) {
@ -364,6 +389,9 @@ var unloadObserver = {
for (let definition of gDevTools.getToolDefinitionArray()) {
gDevTools.unregisterTool(definition.id);
}
for (let definition of gDevTools.getThemeDefinitionArray()) {
gDevTools.unregisterTheme(definition.id);
}
}
}
};

View File

@ -4,6 +4,8 @@
"use strict";
let { utils: Cu, interfaces: Ci } = Components;
addMessageListener("devtools:test:history", function ({ data }) {
content.history[data.direction]();
});
@ -16,3 +18,11 @@ addMessageListener("devtools:test:reload", function ({ data }) {
data = data || {};
content.location.reload(data.forceget);
});
addMessageListener("devtools:test:forceCC", function () {
let DOMWindowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
DOMWindowUtils.cycleCollect();
DOMWindowUtils.garbageCollect();
DOMWindowUtils.garbageCollect();
});

View File

@ -24,24 +24,35 @@
return;
}
if (oldTheme && newTheme != oldTheme) {
StylesheetUtils.removeSheet(
window,
DEVTOOLS_SKIN_URL + oldTheme + "-theme.css",
"author"
);
let oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
let newThemeDef = gDevTools.getThemeDefinition(newTheme);
// Unload all theme stylesheets related to the old theme.
if (oldThemeDef) {
for (let url of oldThemeDef.stylesheets) {
StylesheetUtils.removeSheet(window, url, "author");
}
}
StylesheetUtils.loadSheet(
window,
DEVTOOLS_SKIN_URL + newTheme + "-theme.css",
"author"
);
// Load all stylesheets associated with the new theme.
let newThemeDef = gDevTools.getThemeDefinition(newTheme);
// Floating scrollbars à la osx
// The theme might not be available anymore (e.g. uninstalled)
// Use the default one.
if (!newThemeDef) {
newThemeDef = gDevTools.getThemeDefinition("light");
}
for (let url of newThemeDef.stylesheets) {
StylesheetUtils.loadSheet(window, url, "author");
}
// Floating scroll-bars like in OSX
let hiddenDOMWindow = Cc["@mozilla.org/appshell/appShellService;1"]
.getService(Ci.nsIAppShellService)
.hiddenDOMWindow;
// TODO: extensions might want to customize scrollbar styles too.
if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
let scrollbarsUrl = Services.io.newURI(
DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null);
@ -62,8 +73,26 @@
forceStyle();
}
documentElement.classList.remove("theme-" + oldTheme);
documentElement.classList.add("theme-" + newTheme);
if (oldThemeDef) {
for (let name of oldThemeDef.classList) {
documentElement.classList.remove(name);
}
if (oldThemeDef.onUnapply) {
oldThemeDef.onUnapply(window, newTheme);
}
}
for (let name of newThemeDef.classList) {
documentElement.classList.add(name);
}
if (newThemeDef.onApply) {
newThemeDef.onApply(window, oldTheme);
}
// Final notification for further theme-switching related logic.
gDevTools.emit("theme-switched", window, newTheme, oldTheme);
}
function handlePrefChange(event, data) {

View File

@ -10,18 +10,23 @@ support-files =
doc_connect-toggle.html
doc_connect-param.html
doc_connect-multi-param.html
doc_change-param.html
440hz_sine.ogg
head.js
[browser_audionode-actor-get-set-param.js]
[browser_audionode-actor-get-type.js]
[browser_audionode-actor-get-param-flags.js]
[browser_audionode-actor-get-params-01.js]
[browser_audionode-actor-get-params-02.js]
[browser_audionode-actor-get-param-flags.js]
[browser_audionode-actor-get-set-param.js]
[browser_audionode-actor-get-type.js]
[browser_audionode-actor-is-source.js]
[browser_webaudio-actor-simple.js]
[browser_webaudio-actor-destroy-node.js]
[browser_webaudio-actor-change-params-01.js]
[browser_webaudio-actor-change-params-02.js]
[browser_webaudio-actor-change-params-03.js]
[browser_webaudio-actor-connect-param.js]
[browser_webaudio-actor-destroy-node.js]
[browser_webaudio-actor-simple.js]
[browser_wa_destroy-node-01.js]
@ -48,4 +53,5 @@ support-files =
# [browser_wa_properties-view-edit-02.js]
# Disabled for too many intermittents bug 1010423
[browser_wa_properties-view-params.js]
[browser_wa_properties-view-change-params.js]
[browser_wa_properties-view-params-objects.js]

View File

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that params view correctly updates changed parameters
* when source code updates them, as well as CHANGE_PARAM events.
*/
function spawnTest() {
let [target, debuggee, panel] = yield initWebAudioEditor(CHANGE_PARAM_URL);
let { panelWin } = panel;
let { gFront, $, $$, EVENTS, WebAudioInspectorView } = panelWin;
let gVars = WebAudioInspectorView._propsView;
// Set parameter polling to 20ms for tests
panelWin.PARAM_POLLING_FREQUENCY = 20;
let started = once(gFront, "start-context");
reload(target);
let [actors] = yield Promise.all([
getN(gFront, "create-node", 3),
waitForGraphRendered(panelWin, 3, 0)
]);
let oscId = actors[1].actorID;
click(panelWin, findGraphNode(panelWin, oscId));
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
// Yield twice so we get a diff
yield once(panelWin, EVENTS.CHANGE_PARAM);
let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
is(args.actorID, oscId, "EVENTS.CHANGE_PARAM has correct `actorID`");
ok(args.oldValue < args.newValue, "EVENTS.CHANGE_PARAM has correct `newValue` and `oldValue`");
is(args.param, "detune", "EVENTS.CHANGE_PARAM has correct `param`");
let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
checkVariableView(gVars, 0, { "detune": args.newValue }, "`detune` parameter updated.");
let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
checkVariableView(gVars, 0, { "detune": args.newValue }, "`detune` parameter updated.");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test WebAudioActor `change-param` events and front.[en|dis]ableChangeParamEvents
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(CHANGE_PARAM_URL);
let [_, nodes] = yield Promise.all([
front.setup({ reload: true }),
getN(front, "create-node", 3)
]);
let osc = nodes[1];
let eventCount = 0;
yield front.enableChangeParamEvents(osc, 20);
front.on("change-param", onChangeParam);
yield getN(front, "change-param", 3);
yield front.disableChangeParamEvents();
let currEventCount = eventCount;
// Be flexible here incase we get an extra counter before the listener is turned off
ok(eventCount >= 3, "Calling `enableChangeParamEvents` should allow front to emit `change-param`.");
yield wait(100);
ok((eventCount - currEventCount) <= 2, "Calling `disableChangeParamEvents` should turn off the listener.");
front.off("change-param", onChangeParam);
yield removeTab(target.tab);
finish();
function onChangeParam ({ newValue, oldValue, param, actorID }) {
is(actorID, osc.actorID, "correct `actorID` in `change-param`.");
is(param, "detune", "correct `param` property in `change-param`.");
ok(newValue > oldValue,
"correct `newValue` (" + newValue + ") and `oldValue` (" + oldValue + ") in `change-param`");
eventCount++;
}
}

View File

@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that listening to param change polling does not break when the AudioNode is collected.
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(DESTROY_NODES_URL);
let waitUntilDestroyed = getN(front, "destroy-node", 10);
let [_, nodes] = yield Promise.all([
front.setup({ reload: true }),
getN(front, "create-node", 13)
]);
let bufferNode = nodes[6];
yield front.enableChangeParamEvents(bufferNode, 20);
front.on("change-param", onChangeParam);
forceCC();
yield waitUntilDestroyed;
yield wait(50);
front.off("change-param", onChangeParam);
ok(true, "listening to `change-param` on a dead node doesn't throw.");
yield removeTab(target.tab);
finish();
function onChangeParam (args) {
ok(false, "`change-param` should not be emitted on a node that hasn't changed params or is dead.");
}
}

View File

@ -0,0 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test WebAudioActor `change-param` events on special types.
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(CHANGE_PARAM_URL);
let [_, nodes] = yield Promise.all([
front.setup({ reload: true }),
getN(front, "create-node", 3)
]);
let shaper = nodes[2];
let eventCount = 0;
yield front.enableChangeParamEvents(shaper, 20);
let onChange = once(front, "change-param");
shaper.setParam("curve", null);
let { newValue, oldValue } = yield onChange;
is(oldValue.type, "object", "`oldValue` should be an object.");
is(oldValue.class, "Float32Array", "`oldValue` should be of class Float32Array.");
is(newValue.type, "null", "`newValue` should be null.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,25 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Web Audio Editor test page</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
let ctx = new AudioContext();
let osc = ctx.createOscillator();
let shaperNode = ctx.createWaveShaper();
let detuneVal = 0;
shaperNode.curve = new Float32Array(65536);
setInterval(() => osc.detune.value = ++detuneVal, 10);
</script>
</body>
</html>

View File

@ -19,7 +19,9 @@ let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.j
let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
let TargetFactory = devtools.TargetFactory;
let mm = null;
const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js";
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
@ -30,6 +32,7 @@ const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
const CONNECT_TOGGLE_URL = EXAMPLE_URL + "doc_connect-toggle.html";
const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html";
const CONNECT_MULTI_PARAM_URL = EXAMPLE_URL + "doc_connect-multi-param.html";
const CHANGE_PARAM_URL = EXAMPLE_URL + "doc_change-param.html";
// All tests are asynchronous.
waitForExplicitFinish();
@ -133,6 +136,8 @@ function initBackend(aUrl) {
yield target.makeRemote();
let front = new WebAudioFront(target.client, target.form);
loadFrameScripts();
return [target, debuggee, front];
});
}
@ -150,6 +155,8 @@ function initWebAudioEditor(aUrl) {
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor");
let panel = toolbox.getCurrentPanel();
loadFrameScripts();
return [target, debuggee, panel];
});
}
@ -387,9 +394,12 @@ function countGraphObjects (win) {
* Forces cycle collection and GC, used in AudioNode destruction tests.
*/
function forceCC () {
SpecialPowers.DOMWindowUtils.cycleCollect();
SpecialPowers.DOMWindowUtils.garbageCollect();
SpecialPowers.DOMWindowUtils.garbageCollect();
mm.sendAsyncMessage("devtools:test:forceCC");
}
function loadFrameScripts () {
mm = gBrowser.selectedBrowser.messageManager;
mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
}
/**

View File

@ -20,9 +20,10 @@ const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const Telemetry = require("devtools/shared/telemetry");
const telemetry = new Telemetry();
let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
let PARAM_POLLING_FREQUENCY = 1000;
// The panel's window global is an EventEmitter firing the following events:
const EVENTS = {
// Fired when the first AudioNode has been created, signifying
@ -173,6 +174,8 @@ let WebAudioEditorController = {
telemetry.toolOpened("webaudioeditor");
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onThemeChange = this._onThemeChange.bind(this);
this._onSelectNode = this._onSelectNode.bind(this);
this._onChangeParam = this._onChangeParam.bind(this);
gTarget.on("will-navigate", this._onTabNavigated);
gTarget.on("navigate", this._onTabNavigated);
gFront.on("start-context", this._onStartContext);
@ -194,12 +197,15 @@ let WebAudioEditorController = {
window.on(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
window.on(EVENTS.DESTROY_NODE, this._onUpdatedContext);
window.on(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
// Set up a controller for managing parameter changes per audio node
window.on(EVENTS.UI_SELECT_NODE, this._onSelectNode);
},
/**
* Remove events emitted by the current tab target.
*/
destroy: function() {
destroy: Task.async(function* () {
telemetry.toolClosed("webaudioeditor");
gTarget.off("will-navigate", this._onTabNavigated);
gTarget.off("navigate", this._onTabNavigated);
@ -215,8 +221,11 @@ let WebAudioEditorController = {
window.off(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
window.off(EVENTS.DESTROY_NODE, this._onUpdatedContext);
window.off(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
window.off(EVENTS.UI_SELECT_NODE, this._onSelectNode);
gDevTools.off("pref-changed", this._onThemeChange);
},
yield gFront.disableChangeParamEvents();
}),
/**
* Called when page is reloaded to show the reload notice and waiting
@ -345,9 +354,21 @@ let WebAudioEditorController = {
/**
* Called when a node param is changed.
*/
_onChangeParam: function({ actor, param, value }) {
window.emit(EVENTS.CHANGE_PARAM, getViewNodeByActor(actor), param, value);
}
_onChangeParam: function (args) {
window.emit(EVENTS.CHANGE_PARAM, args);
},
/**
* Called on UI_SELECT_NODE, used to manage
* `change-param` events on that node.
*/
_onSelectNode: function (_, id) {
let node = getViewNodeById(id);
if (node && node.actor) {
gFront.enableChangeParamEvents(node.actor, PARAM_POLLING_FREQUENCY);
}
},
};
/**

View File

@ -377,6 +377,7 @@ let WebAudioInspectorView = {
this._onNodeSelect = this._onNodeSelect.bind(this);
this._onTogglePaneClick = this._onTogglePaneClick.bind(this);
this._onDestroyNode = this._onDestroyNode.bind(this);
this._onChangeParam = this._onChangeParam.bind(this);
this._inspectorPaneToggleButton.addEventListener("mousedown", this._onTogglePaneClick, false);
this._propsView = new VariablesView($("#properties-tabpanel-content"), GENERIC_VARIABLES_VIEW_SETTINGS);
@ -384,6 +385,7 @@ let WebAudioInspectorView = {
window.on(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
window.on(EVENTS.DESTROY_NODE, this._onDestroyNode);
window.on(EVENTS.CHANGE_PARAM, this._onChangeParam);
},
/**
@ -393,6 +395,7 @@ let WebAudioInspectorView = {
this._inspectorPaneToggleButton.removeEventListener("mousedown", this._onTogglePaneClick);
window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
window.off(EVENTS.DESTROY_NODE, this._onDestroyNode);
window.off(EVENTS.CHANGE_PARAM, this._onChangeParam);
this._inspectorPane = null;
this._inspectorPaneToggleButton = null;
@ -612,7 +615,22 @@ let WebAudioInspectorView = {
if (this._currentNode && this._currentNode.id === id) {
this.setCurrentAudioNode(null);
}
}
},
/**
* Called when `CHANGE_PARAM` is fired. We should ensure that this event is
* for the same node that is currently selected. We check the existence
* of each part of the scope to make sure that if this event was fired
* during a VariablesView rebuild, then we just ignore it.
*/
_onChangeParam: function (_, { param, newValue, oldValue, actorID }) {
if (!this._currentNode || this._currentNode.actor.actorID !== actorID) return;
let scope = this._getAudioPropertiesScope();
if (!scope) return;
let property = scope.get(param);
if (!property) return;
property.setGrip(newValue);
},
};
/**

View File

@ -113,7 +113,10 @@ support-files =
test-console-api-stackframe.html
test_bug_1010953_cspro.html^headers^
test_bug_1010953_cspro.html
test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
test_bug1045902_console_csp_ignore_reflected_xss_message.html
[browser_bug1045902_console_csp_ignore_reflected_xss_message.js]
[browser_bug664688_sandbox_update_after_navigation.js]
[browser_bug_638949_copy_link_location.js]
[browser_bug_862916_console_dir_and_filter_off.js]

View File

@ -0,0 +1,53 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* Description of the test:
* We are loading a file with the following CSP:
* 'reflected-xss filter'
* This directive is not supported, hence we confirm that
* the according message is displayed in the web console.
*/
const EXPECTED_RESULT = "Not supporting directive 'reflected-xss'. Directive and values will be ignored.";
const TEST_FILE = "http://example.com/browser/browser/devtools/webconsole/test/" +
"test_bug1045902_console_csp_ignore_reflected_xss_message.html";
let hud = undefined;
function test() {
addTab("data:text/html;charset=utf8,Web Console CSP ignoring reflected XSS (bug 1045902)");
browser.addEventListener("load", function _onLoad() {
browser.removeEventListener("load", _onLoad, true);
openConsole(null, loadDocument);
}, true);
}
function loadDocument(theHud) {
hud = theHud;
hud.jsterm.clearOutput()
browser.addEventListener("load", onLoad, true);
content.location = TEST_FILE;
}
function onLoad(aEvent) {
browser.removeEventListener("load", onLoad, true);
testViolationMessage();
}
function testViolationMessage() {
let aOutputNode = hud.outputNode;
waitForSuccess({
name: "Confirming that CSP logs messages to the console when 'reflected-xss' directive is used!",
validatorFn: function() {
console.log(hud.outputNode.textContent);
let success = false;
success = hud.outputNode.textContent.indexOf(EXPECTED_RESULT) > -1;
return success;
},
successFn: finishTest,
failureFn: finishTest,
});
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Bug 1045902 - CSP: Log console message for 'reflected-xss'</title>
</head>
<body>
Bug 1045902 - CSP: Log console message for 'reflected-xss'
</body>
</html>

View File

@ -0,0 +1 @@
Content-Security-Policy: reflected-xss filter;

View File

@ -59,7 +59,7 @@
<menuitem command="cmd_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
<menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
<menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_accesskey;"/>
<menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accessley;"/>
<menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
<menuseparator/>
<menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
<menuitem command="cmd_stop" accesskey="&projectMenu_stop_accesskey;"/>

View File

@ -359,7 +359,7 @@ Experiments.Experiments = function (policy=new Experiments.Policy()) {
// crashes. For forensics purposes, keep the last few log
// messages in memory and upload them in case of crash.
this._forensicsLogs = [];
this._forensicsLogs.length = 3;
this._forensicsLogs.length = 10;
this._log = Object.create(log);
this._log.log = (level, string, params) => {
this._forensicsLogs.shift();

View File

@ -303,6 +303,7 @@
@BINPATH@/components/toolkit_finalizationwitness.xpt
@BINPATH@/components/toolkit_formautofill.xpt
@BINPATH@/components/toolkit_osfile.xpt
@BINPATH@/components/toolkit_xulstore.xpt
@BINPATH@/components/toolkitprofile.xpt
#ifdef MOZ_ENABLE_XREMOTE
@BINPATH@/components/toolkitremote.xpt
@ -503,6 +504,8 @@
@BINPATH@/components/cryptoComponents.manifest
@BINPATH@/components/TelemetryStartup.js
@BINPATH@/components/TelemetryStartup.manifest
@BINPATH@/components/XULStore.js
@BINPATH@/components/XULStore.manifest
@BINPATH@/components/messageWakeupService.js
@BINPATH@/components/messageWakeupService.manifest
@BINPATH@/components/SettingsManager.js

View File

@ -114,8 +114,6 @@
- the heading of the radiobox corresponding to the theme of the developer
- tools. -->
<!ENTITY options.selectDevToolsTheme.label "Choose DevTools theme:">
<!ENTITY options.darkTheme.label "Dark theme">
<!ENTITY options.lightTheme.label "Light theme">
<!-- LOCALIZATION NOTE (options.webconsole.label): This is the label for the
- heading of the group of Web Console preferences in the options panel. -->

View File

@ -70,3 +70,11 @@ browserConsoleCmd.commandkey=j
# LOCALIZATION NOTE (pickButton.tooltip)
# This is the tooltip of the pick button in the toolbox toolbar
pickButton.tooltip=Pick an element from the page
# LOCALIZATION NOTE (options.darkTheme.label)
# Used as a label for dark theme
options.darkTheme.label=Dark theme
# LOCALIZATION NOTE (options.lightTheme.label)
# Used as a label for light theme
options.lightTheme.label=Light theme

View File

@ -13,7 +13,7 @@
<!ENTITY projectMenu_importHostedApp_label "Open Hosted App…">
<!ENTITY projectMenu_importHostedApp_accesskey "H">
<!ENTITY projectMenu_selectApp_label "Open App…">
<!ENTITY projectMenu_selectApp_accessley "S">
<!ENTITY projectMenu_selectApp_accesskey "O">
<!ENTITY projectMenu_play_label "Install and Run">
<!ENTITY projectMenu_play_accesskey "I">
<!ENTITY projectMenu_stop_label "Stop App">

View File

@ -40,6 +40,10 @@ error_cantFetchAddonsJSON=Can't fetch the add-on list: %S
addons_stable=stable
addons_unstable=unstable
# LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of
# a given simulator version in the "Manage Simulators" pane. %1$S: Firefox OS
# version in the simulator, ex. 1.3. %2$S: Simulator stability label, ex.
# "stable" or "unstable".
addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
addons_install_button=install
addons_uninstall_button=uninstall

View File

@ -77,6 +77,10 @@ menu[disabled="true"].subviewbutton > .menu-right {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
transform: scaleX(-1);
}
.subviewbutton > .toolbarbutton-icon {
-moz-margin-end: 5px !important;
}

View File

@ -126,6 +126,10 @@ menu[disabled="true"].subviewbutton > .menu-right {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
transform: scaleX(-1);
}
%ifdef WINDOWS_AERO
/* Win8 and beyond. */
@media not all and (-moz-os-version: windows-vista) {

View File

@ -61,6 +61,10 @@ leak:gsmsdp_add_default_audio_formats_to_local_sdp
leak:gsmsdp_add_default_video_formats_to_local_sdp
leak:CCAPI_CallInfo_getMediaStreams
# Intermittent Mochitest 3 WebRTC leaks, as seen in bug 1055910.
leak:sdp_build_attr_ice_attr
leak:VcmSIPCCBinding::CandidateReady
###
### Many leaks only affect some test suites. The suite annotations are not checked.

View File

@ -784,6 +784,17 @@ nsCSPParser::directiveName()
return nullptr;
}
// The directive 'reflected-xss' is part of CSP 1.1, see:
// http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
// Currently we are not supporting that directive, hence we log a
// warning to the console and ignore the directive including its values.
if (CSP_IsDirective(mCurToken, CSP_REFLECTED_XSS)) {
const char16_t* params[] = { mCurToken.get() };
logWarningErrorToConsole(nsIScriptError::warningFlag, "notSupportingDirective",
params, ArrayLength(params));
return nullptr;
}
// Make sure the directive does not already exist
// (see http://www.w3.org/TR/CSP11/#parsing)
if (mPolicy->directiveExists(CSP_DirectiveToEnum(mCurToken))) {

View File

@ -70,23 +70,25 @@ enum CSPDirective {
CSP_CONNECT_SRC,
CSP_REPORT_URI,
CSP_FRAME_ANCESTORS,
CSP_REFLECTED_XSS,
// CSP_LAST_DIRECTIVE_VALUE always needs to be the last element in the enum
// because we use it to calculate the size for the char* array.
CSP_LAST_DIRECTIVE_VALUE
};
static const char* CSPStrDirectives[] = {
"default-src", // CSP_DEFAULT_SRC = 0
"script-src", // CSP_SCRIPT_SRC
"object-src", // CSP_OBJECT_SRC
"style-src", // CSP_STYLE_SRC
"img-src", // CSP_IMG_SRC
"media-src", // CSP_MEDIA_SRC
"frame-src", // CSP_FRAME_SRC
"font-src", // CSP_FONT_SRC
"connect-src", // CSP_CONNECT_SRC
"report-uri", // CSP_REPORT_URI
"frame-ancestors" // CSP_FRAME_ANCESTORS
"default-src", // CSP_DEFAULT_SRC = 0
"script-src", // CSP_SCRIPT_SRC
"object-src", // CSP_OBJECT_SRC
"style-src", // CSP_STYLE_SRC
"img-src", // CSP_IMG_SRC
"media-src", // CSP_MEDIA_SRC
"frame-src", // CSP_FRAME_SRC
"font-src", // CSP_FONT_SRC
"connect-src", // CSP_CONNECT_SRC
"report-uri", // CSP_REPORT_URI
"frame-ancestors", // CSP_FRAME_ANCESTORS
"reflected-xss" // CSP_REFLECTED_XSS
};
inline const char* CSP_EnumToDirective(enum CSPDirective aDir)

View File

@ -198,6 +198,7 @@
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/NodeFilterBinding.h"
#include "mozilla/dom/OwningNonNull.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/UndoManager.h"
#include "mozilla/dom/WebComponentsBinding.h"
#include "nsFrame.h"
@ -10543,8 +10544,9 @@ nsIDocument::ExitFullscreen(nsIDocument* aDoc, bool aRunAsync)
}
// Returns true if the document is a direct child of a cross process parent
// mozbrowser iframe. This is the case when the document has a null parent,
// and its DocShell reports that it is a browser frame.
// mozbrowser iframe or TabParent. This is the case when the document has
// a null parent and its DocShell reports that it is a browser frame, or
// we can get a TabChild from it.
static bool
HasCrossProcessParent(nsIDocument* aDocument)
{
@ -10562,7 +10564,12 @@ HasCrossProcessParent(nsIDocument* aDocument)
if (!docShell) {
return false;
}
return docShell->GetIsBrowserOrApp();
TabChild* tabChild(TabChild::GetFrom(docShell));
if (!tabChild) {
return false;
}
return true;
}
static bool

View File

@ -35,6 +35,7 @@
#include "prenv.h"
#include "mozilla/Preferences.h"
#include "gfx2DGlue.h"
#include "nsPrintfCString.h"
#include <algorithm>
@ -50,26 +51,32 @@ using namespace mozilla::gfx;
#ifdef PR_LOGGING
extern PRLogModuleInfo* gMediaDecoderLog;
#define DECODER_LOG(type, msg, ...) \
PR_LOG(gMediaDecoderLog, type, ("Decoder=%p " msg, mDecoder.get(), ##__VA_ARGS__))
#define VERBOSE_LOG(msg, ...) \
#define DECODER_LOG(x, ...) \
PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, ("Decoder=%p " x, mDecoder.get(), ##__VA_ARGS__))
#define VERBOSE_LOG(x, ...) \
PR_BEGIN_MACRO \
if (!PR_GetEnv("MOZ_QUIET")) { \
DECODER_LOG(PR_LOG_DEBUG, msg, ##__VA_ARGS__); \
DECODER_LOG(x, ##__VA_ARGS__); \
} \
PR_END_MACRO
#define SAMPLE_LOG(msg, ...) \
#define SAMPLE_LOG(x, ...) \
PR_BEGIN_MACRO \
if (PR_GetEnv("MEDIA_LOG_SAMPLES")) { \
DECODER_LOG(PR_LOG_DEBUG, msg, ##__VA_ARGS__); \
DECODER_LOG(x, ##__VA_ARGS__); \
} \
PR_END_MACRO
#else
#define DECODER_LOG(type, msg, ...)
#define VERBOSE_LOG(msg, ...)
#define SAMPLE_LOG(msg, ...)
#define DECODER_LOG(x, ...)
#define VERBOSE_LOG(x, ...)
#define SAMPLE_LOG(x, ...)
#endif
// Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__
// when __VA_ARGS__ expands to nothing. This is a workaround for it.
#define DECODER_WARN_HELPER(a, b) NS_WARNING b
#define DECODER_WARN(x, ...) \
DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoder.get(), ##__VA_ARGS__).get()))
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
// implementation. With unified builds, putting this in headers is not enough.
@ -600,7 +607,7 @@ MediaDecoderStateMachine::DecodeVideo()
!HasLowUndecodedData())
{
skipToNextKeyFrame = true;
DECODER_LOG(PR_LOG_DEBUG, "Skipping video decode to the next keyframe");
DECODER_LOG("Skipping video decode to the next keyframe");
}
currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime();
@ -923,7 +930,7 @@ MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS);
mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
mAmpleAudioThresholdUsecs);
DECODER_LOG(PR_LOG_DEBUG, "Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
DECODER_LOG("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs);
}
return;
@ -983,7 +990,7 @@ MediaDecoderStateMachine::CheckIfSeekComplete()
if (HasVideo() && !videoSeekComplete) {
// We haven't reached the target. Ensure we have requested another sample.
if (NS_FAILED(EnsureVideoDecodeTaskQueued())) {
NS_WARNING("Failed to request video during seek");
DECODER_WARN("Failed to request video during seek");
DecodeError();
}
}
@ -992,7 +999,7 @@ MediaDecoderStateMachine::CheckIfSeekComplete()
if (HasAudio() && !audioSeekComplete) {
// We haven't reached the target. Ensure we have requested another sample.
if (NS_FAILED(EnsureAudioDecodeTaskQueued())) {
NS_WARNING("Failed to request audio during seek");
DECODER_WARN("Failed to request audio during seek");
DecodeError();
}
}
@ -1043,7 +1050,7 @@ MediaDecoderStateMachine::CheckIfDecodeComplete()
DispatchDecodeTasksIfNeeded();
ScheduleStateMachine();
}
DECODER_LOG(PR_LOG_DEBUG, "CheckIfDecodeComplete %scompleted",
DECODER_LOG("CheckIfDecodeComplete %scompleted",
((mState == DECODER_STATE_COMPLETED) ? "" : "NOT "));
}
@ -1087,7 +1094,7 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor)
void MediaDecoderStateMachine::StopPlayback()
{
DECODER_LOG(PR_LOG_DEBUG, "StopPlayback()");
DECODER_LOG("StopPlayback()");
AssertCurrentThreadInMonitor();
@ -1130,13 +1137,13 @@ int64_t MediaDecoderStateMachine::GetCurrentTimeViaMediaStreamSync()
void MediaDecoderStateMachine::StartPlayback()
{
DECODER_LOG(PR_LOG_DEBUG, "StartPlayback()");
DECODER_LOG("StartPlayback()");
NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
AssertCurrentThreadInMonitor();
if (mDecoder->CheckDecoderCanOffloadAudio()) {
DECODER_LOG(PR_LOG_DEBUG, "Offloading playback");
DECODER_LOG("Offloading playback");
return;
}
@ -1145,7 +1152,7 @@ void MediaDecoderStateMachine::StartPlayback()
NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
if (NS_FAILED(StartAudioThread())) {
NS_WARNING("Failed to create audio thread");
DECODER_WARN("Failed to create audio thread");
}
mDecoder->GetReentrantMonitor().NotifyAll();
mDecoder->UpdateStreamBlockingForStateMachinePlaying();
@ -1313,6 +1320,8 @@ void MediaDecoderStateMachine::SetDormant(bool aDormant)
return;
}
DECODER_LOG("SetDormant=%d", aDormant);
if (aDormant) {
ScheduleStateMachine();
mState = DECODER_STATE_DORMANT;
@ -1335,7 +1344,7 @@ void MediaDecoderStateMachine::Shutdown()
// Change state before issuing shutdown request to threads so those
// threads can start exiting cleanly during the Shutdown call.
DECODER_LOG(PR_LOG_DEBUG, "Changed state to SHUTDOWN");
DECODER_LOG("Changed state to SHUTDOWN");
mState = DECODER_STATE_SHUTDOWN;
mScheduler->ScheduleAndShutdown();
if (mAudioSink) {
@ -1377,6 +1386,7 @@ void MediaDecoderStateMachine::StartWaitForResources()
"Should be on state machine or decode thread.");
AssertCurrentThreadInMonitor();
mState = DECODER_STATE_WAIT_FOR_RESOURCES;
DECODER_LOG("StartWaitForResources");
}
void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
@ -1386,6 +1396,7 @@ void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
mReader->IsWaitingMediaResources()) {
return;
}
DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
// The reader is no longer waiting for resources (say a hardware decoder),
// we can now proceed to decode metadata.
mState = DECODER_STATE_DECODING_METADATA;
@ -1400,7 +1411,7 @@ void MediaDecoderStateMachine::Play()
// when the state machine notices the decoder's state change to PLAYING.
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (mState == DECODER_STATE_BUFFERING) {
DECODER_LOG(PR_LOG_DEBUG, "Changed state from BUFFERING to DECODING");
DECODER_LOG("Changed state from BUFFERING to DECODING");
mState = DECODER_STATE_DECODING;
mDecodeStartTime = TimeStamp::Now();
}
@ -1461,7 +1472,7 @@ void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
// We need to be able to seek both at a transport level and at a media level
// to seek.
if (!mDecoder->IsMediaSeekable()) {
NS_WARNING("Seek() function should not be called on a non-seekable state machine");
DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
return;
}
// MediaDecoder::mPlayState should be SEEKING while we seek, and
@ -1481,7 +1492,7 @@ void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
"Can only seek in range [0,duration]");
mSeekTarget = SeekTarget(seekTime, aTarget.mType);
DECODER_LOG(PR_LOG_DEBUG, "Changed state to SEEKING (to %lld)", mSeekTarget.mTime);
DECODER_LOG("Changed state to SEEKING (to %lld)", mSeekTarget.mTime);
mState = DECODER_STATE_SEEKING;
if (mDecoder->GetDecodedStream()) {
mDecoder->RecreateDecodedStream(seekTime - mStartTime);
@ -1503,7 +1514,7 @@ void MediaDecoderStateMachine::StopAudioThread()
mStopAudioThread = true;
mDecoder->GetReentrantMonitor().NotifyAll();
if (mAudioSink) {
DECODER_LOG(PR_LOG_DEBUG, "Shutdown audio thread");
DECODER_LOG("Shutdown audio thread");
mAudioSink->PrepareToShutdown();
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@ -1531,7 +1542,7 @@ MediaDecoderStateMachine::EnqueueDecodeMetadataTask()
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata));
nsresult rv = mDecodeTaskQueue->Dispatch(task);
if (NS_FAILED(rv)) {
NS_WARNING("Dispatch ReadMetadata task failed.");
DECODER_WARN("Dispatch ReadMetadata task failed.");
mDispatchedDecodeMetadataTask = false;
}
return rv;
@ -1543,7 +1554,7 @@ MediaDecoderStateMachine::SetReaderIdle()
#ifdef PR_LOGGING
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
DECODER_LOG(PR_LOG_DEBUG, "SetReaderIdle() audioQueue=%lld videoQueue=%lld",
DECODER_LOG("SetReaderIdle() audioQueue=%lld videoQueue=%lld",
GetDecodedAudioDuration(),
VideoQueue().Duration());
}
@ -1606,7 +1617,7 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
this, &MediaDecoderStateMachine::SetReaderIdle);
nsresult rv = mDecodeTaskQueue->Dispatch(event.forget());
if (NS_FAILED(rv) && mState != DECODER_STATE_SHUTDOWN) {
NS_WARNING("Failed to dispatch event to set decoder idle state");
DECODER_WARN("Failed to dispatch event to set decoder idle state");
}
}
}
@ -1632,7 +1643,7 @@ MediaDecoderStateMachine::EnqueueDecodeSeekTask()
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeSeek));
nsresult rv = mDecodeTaskQueue->Dispatch(task);
if (NS_FAILED(rv)) {
NS_WARNING("Dispatch DecodeSeek task failed.");
DECODER_WARN("Dispatch DecodeSeek task failed.");
mCurrentSeekTarget.Reset();
DecodeError();
}
@ -1676,7 +1687,7 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
if (NS_SUCCEEDED(rv)) {
mAudioRequestPending = true;
} else {
NS_WARNING("Failed to dispatch task to decode audio");
DECODER_WARN("Failed to dispatch task to decode audio");
}
}
@ -1721,7 +1732,7 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
if (NS_SUCCEEDED(rv)) {
mVideoRequestPending = true;
} else {
NS_WARNING("Failed to dispatch task to decode video");
DECODER_WARN("Failed to dispatch task to decode video");
}
}
@ -1746,7 +1757,7 @@ MediaDecoderStateMachine::StartAudioThread()
mAudioStartTime, mInfo.mAudio, mDecoder->GetAudioChannel());
nsresult rv = mAudioSink->Init();
if (NS_FAILED(rv)) {
DECODER_LOG(PR_LOG_WARNING, "Changed state to SHUTDOWN because audio sink initialization failed");
DECODER_WARN("Changed state to SHUTDOWN because audio sink initialization failed");
mState = DECODER_STATE_SHUTDOWN;
mScheduler->ScheduleAndShutdown();
return rv;
@ -1825,7 +1836,7 @@ MediaDecoderStateMachine::DecodeError()
// Change state to shutdown before sending error report to MediaDecoder
// and the HTMLMediaElement, so that our pipeline can start exiting
// cleanly during the sync dispatch below.
DECODER_LOG(PR_LOG_WARNING, "Decode error, changed state to SHUTDOWN due to error");
DECODER_WARN("Decode error, changed state to SHUTDOWN due to error");
mState = DECODER_STATE_SHUTDOWN;
mScheduler->ScheduleAndShutdown();
mDecoder->GetReentrantMonitor().NotifyAll();
@ -1852,7 +1863,7 @@ MediaDecoderStateMachine::CallDecodeMetadata()
return;
}
if (NS_FAILED(DecodeMetadata())) {
DECODER_LOG(PR_LOG_WARNING, "Decode metadata failed, shutting down decoder");
DECODER_WARN("Decode metadata failed, shutting down decoder");
DecodeError();
}
}
@ -1861,7 +1872,7 @@ nsresult MediaDecoderStateMachine::DecodeMetadata()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
DECODER_LOG(PR_LOG_DEBUG, "Decoding Media Headers");
DECODER_LOG("Decoding Media Headers");
if (mState != DECODER_STATE_DECODING_METADATA) {
return NS_ERROR_FAILURE;
}
@ -1891,6 +1902,7 @@ nsresult MediaDecoderStateMachine::DecodeMetadata()
mInfo = info;
if (NS_FAILED(res) || (!info.HasValidMedia())) {
DECODER_WARN("ReadMetadata failed, res=%x HasValidMedia=%d", res, info.HasValidMedia());
return NS_ERROR_FAILURE;
}
mDecoder->StartProgressUpdates();
@ -1930,7 +1942,7 @@ MediaDecoderStateMachine::FinishDecodeMetadata()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
DECODER_LOG(PR_LOG_DEBUG, "Decoding Media Headers");
DECODER_LOG("FinishDecodeMetadata");
if (mState == DECODER_STATE_SHUTDOWN) {
return NS_ERROR_FAILURE;
@ -1946,10 +1958,8 @@ MediaDecoderStateMachine::FinishDecodeMetadata()
if (startTime == INT64_MAX) {
startTime = 0;
}
DECODER_LOG(PR_LOG_DEBUG, "DecodeMetadata first video frame start %lld",
v ? v->mTime : -1);
DECODER_LOG(PR_LOG_DEBUG, "DecodeMetadata first audio frame start %lld",
a ? a->mTime : -1);
DECODER_LOG("DecodeMetadata first video frame start %lld", v ? v->mTime : -1);
DECODER_LOG("DecodeMetadata first audio frame start %lld", a ? a->mTime : -1);
SetStartTime(startTime);
if (VideoQueue().GetSize()) {
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@ -1965,8 +1975,8 @@ MediaDecoderStateMachine::FinishDecodeMetadata()
MOZ_ASSERT(!(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) ||
GetDuration() != -1,
"Seekable media should have duration");
DECODER_LOG(PR_LOG_DEBUG, "Media goes from %lld to %lld (duration %lld) "
"transportSeekable=%d, mediaSeekable=%d",
DECODER_LOG("Media goes from %lld to %lld (duration %lld) "
"transportSeekable=%d, mediaSeekable=%d",
mStartTime, mEndTime, GetDuration(),
mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable());
@ -1986,7 +1996,7 @@ MediaDecoderStateMachine::FinishDecodeMetadata()
NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
if (mState == DECODER_STATE_DECODING_METADATA) {
DECODER_LOG(PR_LOG_DEBUG, "Changed state from DECODING_METADATA to DECODING");
DECODER_LOG("Changed state from DECODING_METADATA to DECODING");
StartDecoding();
}
@ -2051,7 +2061,7 @@ void MediaDecoderStateMachine::DecodeSeek()
}
if (!currentTimeChanged) {
DECODER_LOG(PR_LOG_DEBUG, "Seek !currentTimeChanged...");
DECODER_LOG("Seek !currentTimeChanged...");
mDecodeToSeekTarget = false;
nsresult rv = mDecodeTaskQueue->Dispatch(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
@ -2150,14 +2160,14 @@ MediaDecoderStateMachine::SeekCompleted()
// Seeked to end of media, move to COMPLETED state. Note we don't do
// this if we're playing a live stream, since the end of media will advance
// once we download more data!
DECODER_LOG(PR_LOG_DEBUG, "Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
DECODER_LOG("Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd);
// Explicitly set our state so we don't decode further, and so
// we report playback ended to the media element.
mState = DECODER_STATE_COMPLETED;
DispatchDecodeTasksIfNeeded();
} else {
DECODER_LOG(PR_LOG_DEBUG, "Changed state from SEEKING (to %lld) to DECODING", seekTime);
DECODER_LOG("Changed state from SEEKING (to %lld) to DECODING", seekTime);
stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStopped);
StartDecoding();
}
@ -2169,7 +2179,7 @@ MediaDecoderStateMachine::SeekCompleted()
}
// Try to decode another frame to detect if we're at the end...
DECODER_LOG(PR_LOG_DEBUG, "Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
DECODER_LOG("Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
// Prevent changes in playback position before 'seeked' is fired for we
// expect currentTime equals seek target in 'seeked' callback.
@ -2293,6 +2303,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
GetStateMachineThread()->Dispatch(
new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
DECODER_LOG("SHUTDOWN OK");
return NS_OK;
}
@ -2362,14 +2373,14 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
!mDecoder->IsDataCachedToEndOfResource() &&
!resource->IsSuspended())
{
DECODER_LOG(PR_LOG_DEBUG, "Buffering: wait %ds, timeout in %.3lfs %s",
DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
(mQuickBuffering ? "(quick exit)" : ""));
ScheduleStateMachine(USECS_PER_S);
return NS_OK;
} else {
DECODER_LOG(PR_LOG_DEBUG, "Changed state from BUFFERING to DECODING");
DECODER_LOG(PR_LOG_DEBUG, "Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
DECODER_LOG("Changed state from BUFFERING to DECODING");
DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
StartDecoding();
}
@ -2698,8 +2709,7 @@ MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample)
{
nsAutoPtr<VideoData> video(aSample);
MOZ_ASSERT(video);
DECODER_LOG(PR_LOG_DEBUG,
"DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d",
DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d",
video->mTime, video->GetEndTime(), video->mDuplicate);
const int64_t target = mCurrentSeekTarget.mTime;
@ -2722,8 +2732,7 @@ MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample)
// If the frame end time is less than the seek target, we won't want
// to display this frame after the seek, so discard it.
if (target >= video->GetEndTime()) {
DECODER_LOG(PR_LOG_DEBUG,
"DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld",
DECODER_LOG("DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld",
video->mTime, video->GetEndTime(), target);
mFirstVideoFrameAfterSeek = video;
} else {
@ -2736,8 +2745,7 @@ MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample)
}
mFirstVideoFrameAfterSeek = nullptr;
DECODER_LOG(PR_LOG_DEBUG,
"DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
DECODER_LOG("DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
video->mTime, video->GetEndTime(), target);
VideoQueue().PushFront(video.forget());
@ -2774,7 +2782,7 @@ MediaDecoderStateMachine::DropAudioUpToSeekTarget(AudioData* aSample)
// abort the audio decode-to-target, the state machine will play
// silence to cover the gap. Typically this happens in poorly muxed
// files.
NS_WARNING("Audio not synced after seek, maybe a poorly muxed file?");
DECODER_WARN("Audio not synced after seek, maybe a poorly muxed file?");
AudioQueue().Push(audio.forget());
return NS_OK;
}
@ -2791,7 +2799,7 @@ MediaDecoderStateMachine::DropAudioUpToSeekTarget(AudioData* aSample)
if (framesToPrune > audio->mFrames) {
// We've messed up somehow. Don't try to trim frames, the |frames|
// variable below will overflow.
NS_WARNING("Can't prune more frames that we have!");
DECODER_WARN("Can't prune more frames that we have!");
return NS_ERROR_FAILURE;
}
uint32_t frames = audio->mFrames - static_cast<uint32_t>(framesToPrune);
@ -2819,7 +2827,7 @@ MediaDecoderStateMachine::DropAudioUpToSeekTarget(AudioData* aSample)
void MediaDecoderStateMachine::SetStartTime(int64_t aStartTimeUsecs)
{
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
DECODER_LOG(PR_LOG_DEBUG, "SetStartTime(%lld)", aStartTimeUsecs);
DECODER_LOG("SetStartTime(%lld)", aStartTimeUsecs);
mStartTime = 0;
if (aStartTimeUsecs != 0) {
mStartTime = aStartTimeUsecs;
@ -2836,7 +2844,7 @@ void MediaDecoderStateMachine::SetStartTime(int64_t aStartTimeUsecs)
// first actual audio frame we have, we'll inject silence during playback
// to ensure the audio starts at the correct time.
mAudioStartTime = mStartTime;
DECODER_LOG(PR_LOG_DEBUG, "Set media start time to %lld", mStartTime);
DECODER_LOG("Set media start time to %lld", mStartTime);
}
void MediaDecoderStateMachine::UpdateReadyState() {
@ -2905,11 +2913,11 @@ void MediaDecoderStateMachine::StartBuffering()
// the element we're buffering or not.
UpdateReadyState();
mState = DECODER_STATE_BUFFERING;
DECODER_LOG(PR_LOG_DEBUG, "Changed state from DECODING to BUFFERING, decoded for %.3lfs",
DECODER_LOG("Changed state from DECODING to BUFFERING, decoded for %.3lfs",
decodeDuration.ToSeconds());
#ifdef PR_LOGGING
MediaDecoder::Statistics stats = mDecoder->GetStatistics();
DECODER_LOG(PR_LOG_DEBUG, "Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
DECODER_LOG("Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)");
#endif
@ -3080,3 +3088,5 @@ void MediaDecoderStateMachine::OnAudioSinkComplete()
// avoid redefined macro in unified build
#undef DECODER_LOG
#undef VERBOSE_LOG
#undef DECODER_WARN
#undef DECODER_WARN_HELPER

View File

@ -570,7 +570,7 @@ MP4Reader::Decode(TrackType aTrack)
}
}
data.mMonitor.AssertCurrentThreadOwns();
bool rv = !(data.mEOS || data.mError);
bool rv = !(data.mDrainComplete || data.mError);
data.mMonitor.Unlock();
return rv;
}

View File

@ -49,19 +49,19 @@ GMPChild::~GMPChild()
}
static bool
GetPluginBinaryPath(const std::string& aPluginPath,
nsCString &aPluginBinaryPath)
GetPluginBinaryFile(const std::string& aPluginPath,
nsCOMPtr<nsIFile>& aLibFile)
{
nsDependentCString pluginPath(aPluginPath.c_str());
nsCOMPtr<nsIFile> libFile;
nsresult rv = NS_NewNativeLocalFile(pluginPath, true, getter_AddRefs(libFile));
nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(pluginPath),
true, getter_AddRefs(aLibFile));
if (NS_FAILED(rv)) {
return false;
}
nsAutoString leafName;
if (NS_FAILED(libFile->GetLeafName(leafName))) {
if (NS_FAILED(aLibFile->GetLeafName(leafName))) {
return false;
}
nsAutoString baseName(Substring(leafName, 4, leafName.Length() - 1));
@ -75,13 +75,24 @@ GetPluginBinaryPath(const std::string& aPluginPath,
#else
#error not defined
#endif
libFile->AppendRelativePath(binaryName);
aLibFile->AppendRelativePath(binaryName);
return true;
}
#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
static bool
GetPluginBinaryPath(const std::string& aPluginPath,
nsCString &aPluginBinaryPath)
{
nsCOMPtr<nsIFile> libFile;
if (!GetPluginBinaryFile(aPluginPath, libFile)) {
return false;
}
libFile->GetNativePath(aPluginBinaryPath);
return true;
}
#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
void
GMPChild::OnChannelConnected(int32_t aPid)
{
@ -147,23 +158,29 @@ GMPChild::Init(const std::string& aPluginPath,
bool
GMPChild::LoadPluginLibrary(const std::string& aPluginPath)
{
nsAutoCString nativePath;
#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
nsAutoCString nativePath;
nativePath.Assign(mPluginBinaryPath);
mLib = PR_LoadLibrary(nativePath.get());
#else
if (!GetPluginBinaryPath(aPluginPath, nativePath)) {
nsCOMPtr<nsIFile> libFile;
if (!GetPluginBinaryFile(aPluginPath, libFile)) {
return false;
}
#endif
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
nsAutoCString nativePath;
libFile->GetNativePath(nativePath);
// Enable sandboxing here -- we know the plugin file's path, but
// this process's execution hasn't been affected by its content yet.
MOZ_ASSERT(mozilla::CanSandboxMediaPlugin());
mozilla::SetMediaPluginSandbox(nativePath.get());
#endif
#endif // XP_LINUX && MOZ_GMP_SANDBOX
libFile->Load(&mLib);
#endif // XP_MACOSX && MOZ_GMP_SANDBOX
mLib = PR_LoadLibrary(nativePath.get());
if (!mLib) {
return false;
}

View File

@ -119,14 +119,14 @@ GMPParent::LoadProcess()
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
MOZ_ASSERT(mState == GMPStateNotLoaded);
nsAutoCString path;
if (NS_FAILED(mDirectory->GetNativePath(path))) {
nsAutoString path;
if (NS_FAILED(mDirectory->GetPath(path))) {
return NS_ERROR_FAILURE;
}
LOGD(("%s::%s: %p for %s", __CLASS__, __FUNCTION__, this, path.get()));
if (!mProcess) {
mProcess = new GMPProcessParent(path.get());
mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
if (!mProcess->Launch(30 * 1000)) {
mProcess->Delete();
mProcess = nullptr;

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,8 @@ struct MediaCodec;
namespace mozilla {
class MediaTaskQueue;
class MediaCodecReader : public MediaOmxCommonReader
{
public:
@ -53,17 +55,15 @@ public:
// irreversible, whereas ReleaseMediaResources() is reversible.
virtual void Shutdown();
// Decodes an unspecified amount of audio data, enqueuing the audio data
// in mAudioQueue. Returns true when there's more audio to decode,
// false if the audio is finished, end of file has been reached,
// or an un-recoverable read error has occured.
virtual bool DecodeAudioData();
// Flush the MediaTaskQueue, flush MediaCodec and raise the mDiscontinuity.
virtual nsresult ResetDecode() MOZ_OVERRIDE;
// Reads and decodes one video frame. Packets with a timestamp less
// than aTimeThreshold will be decoded (unless they're not keyframes
// and aKeyframeSkip is true), but will not be added to the queue.
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold);
// Disptach a DecodeVideoFrameTask to decode video data.
virtual void RequestVideoData(bool aSkipToNextKeyframe,
int64_t aTimeThreshold) MOZ_OVERRIDE;
// Disptach a DecodeAduioDataTask to decode video data.
virtual void RequestAudioData() MOZ_OVERRIDE;
virtual bool HasAudio();
virtual bool HasVideo();
@ -90,7 +90,8 @@ public:
protected:
struct TrackInputCopier
{
virtual bool Copy(android::MediaBuffer* aSourceBuffer, android::sp<android::ABuffer> aCodecBuffer);
virtual bool Copy(android::MediaBuffer* aSourceBuffer,
android::sp<android::ABuffer> aCodecBuffer);
};
struct Track
@ -111,14 +112,20 @@ protected:
// playback parameters
CheckedUint32 mInputIndex;
// mDiscontinuity, mFlushed, mInputEndOfStream, mInputEndOfStream,
// mSeekTimeUs don't be protected by a lock because the
// mTaskQueue->Flush() will flush all tasks.
bool mInputEndOfStream;
bool mOutputEndOfStream;
int64_t mSeekTimeUs;
bool mFlushed; // meaningless when mSeekTimeUs is invalid.
bool mDiscontinuity;
nsRefPtr<MediaTaskQueue> mTaskQueue;
};
// Receive a message from MessageHandler.
// Called on MediaCodecReader::mLooper thread.
void onMessageReceived(const android::sp<android::AMessage> &aMessage);
void onMessageReceived(const android::sp<android::AMessage>& aMessage);
// Receive a notify from ResourceListener.
// Called on Binder thread.
@ -131,16 +138,16 @@ private:
class MessageHandler : public android::AHandler
{
public:
MessageHandler(MediaCodecReader *aReader);
MessageHandler(MediaCodecReader* aReader);
~MessageHandler();
virtual void onMessageReceived(const android::sp<android::AMessage> &aMessage);
virtual void onMessageReceived(const android::sp<android::AMessage>& aMessage);
private:
// Forbidden
MessageHandler() MOZ_DELETE;
MessageHandler(const MessageHandler &rhs) MOZ_DELETE;
const MessageHandler &operator=(const MessageHandler &rhs) MOZ_DELETE;
MessageHandler(const MessageHandler& rhs) MOZ_DELETE;
const MessageHandler& operator=(const MessageHandler& rhs) MOZ_DELETE;
MediaCodecReader *mReader;
};
@ -151,7 +158,7 @@ private:
class VideoResourceListener : public android::MediaCodecProxy::CodecResourceListener
{
public:
VideoResourceListener(MediaCodecReader *aReader);
VideoResourceListener(MediaCodecReader* aReader);
~VideoResourceListener();
virtual void codecReserved();
@ -160,16 +167,17 @@ private:
private:
// Forbidden
VideoResourceListener() MOZ_DELETE;
VideoResourceListener(const VideoResourceListener &rhs) MOZ_DELETE;
const VideoResourceListener &operator=(const VideoResourceListener &rhs) MOZ_DELETE;
VideoResourceListener(const VideoResourceListener& rhs) MOZ_DELETE;
const VideoResourceListener& operator=(const VideoResourceListener& rhs) MOZ_DELETE;
MediaCodecReader *mReader;
MediaCodecReader* mReader;
};
friend class VideoResourceListener;
class VorbisInputCopier : public TrackInputCopier
{
virtual bool Copy(android::MediaBuffer* aSourceBuffer, android::sp<android::ABuffer> aCodecBuffer);
virtual bool Copy(android::MediaBuffer* aSourceBuffer,
android::sp<android::ABuffer> aCodecBuffer);
};
struct AudioTrack : public Track
@ -206,7 +214,7 @@ private:
// Forbidden
MediaCodecReader() MOZ_DELETE;
const MediaCodecReader &operator=(const MediaCodecReader &rhs) MOZ_DELETE;
const MediaCodecReader& operator=(const MediaCodecReader& rhs) MOZ_DELETE;
bool ReallocateResources();
void ReleaseCriticalResources();
@ -222,27 +230,45 @@ private:
void DestroyMediaSources();
bool CreateMediaCodecs();
static bool CreateMediaCodec(android::sp<android::ALooper> &aLooper,
Track &aTrack,
static bool CreateMediaCodec(android::sp<android::ALooper>& aLooper,
Track& aTrack,
bool aAsync,
android::wp<android::MediaCodecProxy::CodecResourceListener> aListener);
static bool ConfigureMediaCodec(Track &aTrack);
static bool ConfigureMediaCodec(Track& aTrack);
void DestroyMediaCodecs();
static void DestroyMediaCodecs(Track &aTrack);
static void DestroyMediaCodecs(Track& aTrack);
bool CreateTaskQueues();
void ShutdownTaskQueues();
bool DecodeVideoFrameTask(int64_t aTimeThreshold);
bool DecodeVideoFrameSync(int64_t aTimeThreshold);
bool DecodeAudioDataTask();
bool DecodeAudioDataSync();
void DispatchVideoTask(int64_t aTimeThreshold);
void DispatchAudioTask();
inline bool CheckVideoResources() {
return (HasVideo() && mVideoTrack.mSource != nullptr &&
mVideoTrack.mTaskQueue);
}
inline bool CheckAudioResources() {
return (HasAudio() && mAudioTrack.mSource != nullptr &&
mAudioTrack.mTaskQueue);
}
bool UpdateDuration();
bool UpdateAudioInfo();
bool UpdateVideoInfo();
static android::status_t FlushCodecData(Track &aTrack);
static android::status_t FillCodecInputData(Track &aTrack);
static android::status_t GetCodecOutputData(Track &aTrack,
CodecBufferInfo &aBuffer,
static android::status_t FlushCodecData(Track& aTrack);
static android::status_t FillCodecInputData(Track& aTrack);
static android::status_t GetCodecOutputData(Track& aTrack,
CodecBufferInfo& aBuffer,
int64_t aThreshold,
const TimeStamp &aTimeout);
static bool EnsureCodecFormatParsed(Track &aTrack);
const TimeStamp& aTimeout);
static bool EnsureCodecFormatParsed(Track& aTrack);
uint8_t *GetColorConverterBuffer(int32_t aWidth, int32_t aHeight);
uint8_t* GetColorConverterBuffer(int32_t aWidth, int32_t aHeight);
void ClearColorConverterBuffer();
android::sp<MessageHandler> mHandler;

View File

@ -33,9 +33,6 @@
#include "nsViewManager.h"
#include "nsIContentViewer.h"
#include "nsIDOMXULElement.h"
#include "nsIRDFNode.h"
#include "nsIRDFRemoteDataSource.h"
#include "nsIRDFService.h"
#include "nsIStreamListener.h"
#include "nsITimer.h"
#include "nsDocShell.h"
@ -44,11 +41,10 @@
#include "nsXULContentSink.h"
#include "nsXULContentUtils.h"
#include "nsIXULOverlayProvider.h"
#include "nsIStringEnumerator.h"
#include "nsNetUtil.h"
#include "nsParserCIID.h"
#include "nsPIBoxObject.h"
#include "nsRDFCID.h"
#include "nsILocalStore.h"
#include "nsXPIDLString.h"
#include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h"
@ -137,11 +133,6 @@ const uint32_t kMaxAttributeLength = 4096;
int32_t XULDocument::gRefCnt = 0;
nsIRDFService* XULDocument::gRDFService;
nsIRDFResource* XULDocument::kNC_persist;
nsIRDFResource* XULDocument::kNC_attribute;
nsIRDFResource* XULDocument::kNC_value;
PRLogModuleInfo* XULDocument::gXULLog;
//----------------------------------------------------------------------
@ -230,26 +221,11 @@ XULDocument::~XULDocument()
PL_DHashTableDestroy(mBroadcasterMap);
}
if (mLocalStore) {
nsCOMPtr<nsIRDFRemoteDataSource> remote =
do_QueryInterface(mLocalStore);
if (remote)
remote->Flush();
}
delete mTemplateBuilderTable;
Preferences::UnregisterCallback(XULDocument::DirectionChanged,
"intl.uidirection.", this);
if (--gRefCnt == 0) {
NS_IF_RELEASE(gRDFService);
NS_IF_RELEASE(kNC_persist);
NS_IF_RELEASE(kNC_attribute);
NS_IF_RELEASE(kNC_value);
}
if (mOffThreadCompileStringBuf) {
js_free(mOffThreadCompileStringBuf);
}
@ -349,6 +325,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
tmp->mTemplateBuilderTable = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
//XXX We should probably unlink all the objects we traverse.
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -1331,10 +1308,7 @@ XULDocument::Persist(const nsAString& aID,
nameSpaceID = kNameSpaceID_None;
}
rv = Persist(element, nameSpaceID, tag);
if (NS_FAILED(rv)) return rv;
return NS_OK;
return Persist(element, nameSpaceID, tag);
}
nsresult
@ -1345,102 +1319,39 @@ XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
return NS_ERROR_NOT_AVAILABLE;
// First make sure we _have_ a local store to stuff the persisted
// information into. (We might not have one if profile information
// hasn't been loaded yet...)
if (!mLocalStore)
return NS_OK;
nsresult rv;
nsCOMPtr<nsIRDFResource> element;
rv = nsXULContentUtils::GetElementResource(aElement, getter_AddRefs(element));
if (NS_FAILED(rv)) return rv;
// No ID, so nothing to persist.
if (! element)
return NS_OK;
// Ick. Construct a property from the attribute. Punt on
// namespaces for now.
// Don't bother with unreasonable attributes. We clamp long values,
// but truncating attribute names turns it into a different attribute
// so there's no point in persisting anything at all
nsAtomCString attrstr(aAttribute);
if (attrstr.Length() > kMaxAttrNameLength) {
NS_WARNING("Can't persist, Attribute name too long");
return NS_ERROR_ILLEGAL_VALUE;
if (!mLocalStore) {
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if (NS_WARN_IF(!mLocalStore)) {
return NS_ERROR_NOT_INITIALIZED;
}
}
nsCOMPtr<nsIRDFResource> attr;
rv = gRDFService->GetResource(attrstr,
getter_AddRefs(attr));
if (NS_FAILED(rv)) return rv;
nsAutoString id;
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
nsAtomString attrstr(aAttribute);
// Turn the value into a literal
nsAutoString valuestr;
aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
// prevent over-long attributes that choke the parser (bug 319846)
// (can't simply Truncate without testing, it's implemented
// using SetLength and will grow a short string)
if (valuestr.Length() > kMaxAttributeLength) {
NS_WARNING("Truncating persisted attribute value");
valuestr.Truncate(kMaxAttributeLength);
nsAutoCString utf8uri;
nsresult rv = mDocumentURI->GetSpec(utf8uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
NS_ConvertUTF8toUTF16 uri(utf8uri);
bool hasAttr;
rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// See if there was an old value...
nsCOMPtr<nsIRDFNode> oldvalue;
rv = mLocalStore->GetTarget(element, attr, true, getter_AddRefs(oldvalue));
if (NS_FAILED(rv)) return rv;
if (oldvalue && valuestr.IsEmpty()) {
// ...there was an oldvalue, and they've removed it. XXXThis
// handling isn't quite right...
rv = mLocalStore->Unassert(element, attr, oldvalue);
if (hasAttr && valuestr.IsEmpty()) {
return mLocalStore->RemoveValue(uri, id, attrstr);
} else {
return mLocalStore->SetValue(uri, id, attrstr, valuestr);
}
else {
// Now either 'change' or 'assert' based on whether there was
// an old value.
nsCOMPtr<nsIRDFLiteral> newvalue;
rv = gRDFService->GetLiteral(valuestr.get(), getter_AddRefs(newvalue));
if (NS_FAILED(rv)) return rv;
if (oldvalue) {
if (oldvalue != newvalue)
rv = mLocalStore->Change(element, attr, oldvalue, newvalue);
else
rv = NS_OK;
}
else {
rv = mLocalStore->Assert(element, attr, newvalue, true);
}
}
if (NS_FAILED(rv)) return rv;
// Add it to the persisted set for this document (if it's not
// there already).
{
nsAutoCString docurl;
rv = mDocumentURI->GetSpec(docurl);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIRDFResource> doc;
rv = gRDFService->GetResource(docurl, getter_AddRefs(doc));
if (NS_FAILED(rv)) return rv;
bool hasAssertion;
rv = mLocalStore->HasAssertion(doc, kNC_persist, element, true, &hasAssertion);
if (NS_FAILED(rv)) return rv;
if (! hasAssertion) {
rv = mLocalStore->Assert(doc, kNC_persist, element, true);
if (NS_FAILED(rv)) return rv;
}
}
return NS_OK;
}
@ -1993,25 +1904,7 @@ XULDocument::Init()
mCommandDispatcher = new nsXULCommandDispatcher(this);
NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY);
// this _could_ fail; e.g., if we've tried to grab the local store
// before profiles have initialized. If so, no big deal; nothing
// will persist.
mLocalStore = do_GetService(NS_LOCALSTORE_CONTRACTID);
if (gRefCnt++ == 0) {
// Keep the RDF service cached in a member variable to make using
// it a bit less painful
rv = CallGetService("@mozilla.org/rdf/rdf-service;1", &gRDFService);
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF Service");
if (NS_FAILED(rv)) return rv;
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "persist"),
&kNC_persist);
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "attribute"),
&kNC_attribute);
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "value"),
&kNC_value);
// ensure that the XUL prototype cache is instantiated successfully,
// so that we can use nsXULPrototypeCache::GetInstance() without
// null-checks in the rest of the class.
@ -2175,8 +2068,12 @@ XULDocument::ApplyPersistentAttributes()
// Add all of the 'persisted' attributes into the content
// model.
if (!mLocalStore)
return NS_OK;
if (!mLocalStore) {
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if (NS_WARN_IF(!mLocalStore)) {
return NS_ERROR_NOT_INITIALIZED;
}
}
mApplyingPersistedAttrs = true;
ApplyPersistentAttributesInternal();
@ -2191,56 +2088,49 @@ XULDocument::ApplyPersistentAttributes()
}
nsresult
nsresult
XULDocument::ApplyPersistentAttributesInternal()
{
nsCOMArray<nsIContent> elements;
nsAutoCString docurl;
mDocumentURI->GetSpec(docurl);
nsAutoCString utf8uri;
nsresult rv = mDocumentURI->GetSpec(utf8uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
NS_ConvertUTF8toUTF16 uri(utf8uri);
nsCOMPtr<nsIRDFResource> doc;
gRDFService->GetResource(docurl, getter_AddRefs(doc));
nsCOMPtr<nsISimpleEnumerator> persisted;
mLocalStore->GetTargets(doc, kNC_persist, true, getter_AddRefs(persisted));
// Get a list of element IDs for which persisted values are available
nsCOMPtr<nsIStringEnumerator> ids;
rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
while (1) {
bool hasmore = false;
persisted->HasMoreElements(&hasmore);
if (! hasmore)
ids->HasMore(&hasmore);
if (!hasmore) {
break;
}
nsCOMPtr<nsISupports> isupports;
persisted->GetNext(getter_AddRefs(isupports));
nsAutoString id;
ids->GetNext(id);
nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports);
if (! resource) {
NS_WARNING("expected element to be a resource");
if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
continue;
}
const char *uri;
resource->GetValueConst(&uri);
if (! uri)
continue;
nsAutoString id;
nsXULContentUtils::MakeElementID(this, nsDependentCString(uri), id);
if (id.IsEmpty())
continue;
if (mRestrictPersistence && !mPersistenceIds.Contains(id))
continue;
// This will clear the array if there are no elements.
GetElementsForID(id, elements);
if (!elements.Count())
if (!elements.Count()) {
continue;
}
ApplyPersistentAttributesToElements(resource, elements);
rv = ApplyPersistentAttributesToElements(id, elements);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
return NS_OK;
@ -2248,71 +2138,53 @@ XULDocument::ApplyPersistentAttributesInternal()
nsresult
XULDocument::ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
nsCOMArray<nsIContent>& aElements)
{
nsresult rv;
nsAutoCString utf8uri;
nsresult rv = mDocumentURI->GetSpec(utf8uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
NS_ConvertUTF8toUTF16 uri(utf8uri);
nsCOMPtr<nsISimpleEnumerator> attrs;
rv = mLocalStore->ArcLabelsOut(aResource, getter_AddRefs(attrs));
if (NS_FAILED(rv)) return rv;
// Get a list of attributes for which persisted values are available
nsCOMPtr<nsIStringEnumerator> attrs;
rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
while (1) {
bool hasmore;
rv = attrs->HasMoreElements(&hasmore);
if (NS_FAILED(rv)) return rv;
if (! hasmore)
bool hasmore = PR_FALSE;
attrs->HasMore(&hasmore);
if (!hasmore) {
break;
nsCOMPtr<nsISupports> isupports;
rv = attrs->GetNext(getter_AddRefs(isupports));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
if (! property) {
NS_WARNING("expected a resource");
continue;
}
const char* attrname;
rv = property->GetValueConst(&attrname);
if (NS_FAILED(rv)) return rv;
nsAutoString attrstr;
attrs->GetNext(attrstr);
nsCOMPtr<nsIAtom> attr = do_GetAtom(attrname);
if (! attr)
nsAutoString value;
rv = mLocalStore->GetValue(uri, aID, attrstr, value);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIAtom> attr = do_GetAtom(attrstr);
if (NS_WARN_IF(!attr)) {
return NS_ERROR_OUT_OF_MEMORY;
// XXX could hang namespace off here, as well...
nsCOMPtr<nsIRDFNode> node;
rv = mLocalStore->GetTarget(aResource, property, true,
getter_AddRefs(node));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(node);
if (! literal) {
NS_WARNING("expected a literal");
continue;
}
const char16_t* value;
rv = literal->GetValueConst(&value);
if (NS_FAILED(rv)) return rv;
nsDependentString wrapper(value);
uint32_t cnt = aElements.Count();
for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
if (!element)
continue;
if (!element) {
continue;
}
rv = element->SetAttr(/* XXX */ kNameSpaceID_None,
attr,
wrapper,
true);
rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
}
}

View File

@ -22,6 +22,7 @@
#include "nsScriptLoader.h"
#include "nsIStreamListener.h"
#include "nsICSSLoaderObserver.h"
#include "nsIXULStore.h"
#include "mozilla/Attributes.h"
@ -259,7 +260,7 @@ protected:
nsresult ApplyPersistentAttributes();
nsresult ApplyPersistentAttributesInternal();
nsresult ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
nsresult ApplyPersistentAttributesToElements(const nsAString &aID,
nsCOMArray<nsIContent>& aElements);
nsresult
@ -314,10 +315,10 @@ protected:
// Tracks elements with a 'ref' attribute, or an 'id' attribute where
// the element's namespace has no registered ID attribute name.
nsTHashtable<nsRefMapEntry> mRefMap;
nsCOMPtr<nsIRDFDataSource> mLocalStore;
bool mApplyingPersistedAttrs;
bool mIsWritingFastLoad;
bool mDocumentLoaded;
nsCOMPtr<nsIXULStore> mLocalStore;
bool mApplyingPersistedAttrs;
bool mIsWritingFastLoad;
bool mDocumentLoaded;
/**
* Since ResumeWalk is interruptible, it's possible that last
* stylesheet finishes loading while the PD walk is still in

View File

@ -186,36 +186,6 @@ nsXULContentUtils::FindChildByTag(nsIContent* aElement,
}
nsresult
nsXULContentUtils::GetElementResource(nsIContent* aElement, nsIRDFResource** aResult)
{
// Perform a reverse mapping from an element in the content model
// to an RDF resource.
nsresult rv;
char16_t buf[128];
nsFixedString id(buf, ArrayLength(buf), 0);
// Whoa. Why the "id" attribute? What if it's not even a XUL
// element? This is totally bogus!
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
if (id.IsEmpty())
return NS_ERROR_FAILURE;
// Since the element will store its ID attribute as a document-relative value,
// we may need to qualify it first...
nsCOMPtr<nsIDocument> doc = aElement->GetDocument();
NS_ASSERTION(doc, "element is not in any document");
if (! doc)
return NS_ERROR_FAILURE;
rv = nsXULContentUtils::MakeElementResource(doc, id, aResult);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
/*
Note: this routine is similar, yet distinctly different from, nsBookmarksService::GetTextForNode
*/
@ -287,78 +257,6 @@ nsXULContentUtils::GetTextForNode(nsIRDFNode* aNode, nsAString& aResult)
return NS_ERROR_UNEXPECTED;
}
nsresult
nsXULContentUtils::MakeElementURI(nsIDocument* aDocument,
const nsAString& aElementID,
nsCString& aURI)
{
// Convert an element's ID to a URI that can be used to refer to
// the element in the XUL graph.
nsIURI *docURI = aDocument->GetDocumentURI();
NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
nsRefPtr<nsIURI> docURIClone;
nsresult rv = docURI->Clone(getter_AddRefs(docURIClone));
NS_ENSURE_SUCCESS(rv, rv);
rv = docURIClone->SetRef(NS_ConvertUTF16toUTF8(aElementID));
if (NS_SUCCEEDED(rv)) {
return docURIClone->GetSpec(aURI);
}
// docURIClone is apparently immutable. Fine - we can append ref manually.
rv = docURI->GetSpec(aURI);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString ref;
NS_EscapeURL(NS_ConvertUTF16toUTF8(aElementID), esc_FilePath | esc_AlwaysCopy, ref);
aURI.Append('#');
aURI.Append(ref);
return NS_OK;
}
nsresult
nsXULContentUtils::MakeElementResource(nsIDocument* aDocument, const nsAString& aID, nsIRDFResource** aResult)
{
nsresult rv;
char buf[256];
nsFixedCString uri(buf, sizeof(buf), 0);
rv = MakeElementURI(aDocument, aID, uri);
if (NS_FAILED(rv)) return rv;
rv = gRDF->GetResource(uri, aResult);
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create resource");
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
nsresult
nsXULContentUtils::MakeElementID(nsIDocument* aDocument,
const nsACString& aURI,
nsAString& aElementID)
{
// Convert a URI into an element ID that can be accessed from the
// DOM APIs.
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI,
aDocument->GetDocumentCharacterSet().get());
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString ref;
uri->GetRef(ref);
CopyUTF8toUTF16(ref, aElementID);
return NS_OK;
}
nsresult
nsXULContentUtils::GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult)
{

View File

@ -115,31 +115,9 @@ public:
nsIRDFResource* aResource,
nsIContent** aResult);
static nsresult
GetElementResource(nsIContent* aElement, nsIRDFResource** aResult);
static nsresult
GetTextForNode(nsIRDFNode* aNode, nsAString& aResult);
/**
* Construct a URI from the element ID given. This uses aElement as the
* ref and aDocument's document URI as the base. If aDocument's document
* URI does not support refs, this will throw NS_ERROR_NOT_AVAILABLE.
*/
static nsresult
MakeElementURI(nsIDocument* aDocument, const nsAString& aElementID, nsCString& aURI);
static nsresult
MakeElementResource(nsIDocument* aDocument, const nsAString& aElementID, nsIRDFResource** aResult);
/**
* Extract the element ID from aURI. Note that aURI must be an absolute
* URI string in UTF8; the element ID is the ref from the URI. If the
* scheme does not support refs, then the ID will be empty.
*/
static nsresult
MakeElementID(nsIDocument* aDocument, const nsACString& aURI, nsAString& aElementID);
static nsresult
GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult);

View File

@ -8,7 +8,6 @@
#include "nsIContent.h"
#include "mozilla/dom/NodeInfo.h"
#include "nsIDOMElement.h"
#include "nsILocalStore.h"
#include "nsIBoxObject.h"
#include "nsITreeBoxObject.h"
#include "nsITreeSelection.h"
@ -31,6 +30,7 @@
#include "nsDOMClassInfoID.h"
#include "nsWhitespaceTokenizer.h"
#include "nsTreeContentView.h"
#include "nsIXULStore.h"
// For security check
#include "nsIDocument.h"
@ -139,13 +139,10 @@ protected:
RemoveMatchesFor(nsTreeRows::Subtree& subtree);
/**
* Helper methods that determine if the specified container is open.
* Helper method that determines if the specified container is open.
*/
nsresult
IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen);
nsresult
IsContainerOpen(nsIRDFResource* aResource, bool* aOpen);
bool
IsContainerOpen(nsIXULTemplateResult* aResource);
/**
* A sorting callback for NS_QuickSort().
@ -242,6 +239,11 @@ protected:
* The builder observers.
*/
nsCOMArray<nsIXULTreeBuilderObserver> mObservers;
/*
* XUL store for holding open container state
*/
nsCOMPtr<nsIXULStore> mLocalStore;
};
//----------------------------------------------------------------------
@ -278,6 +280,7 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder,
mBoxObject,
mSelection,
mPersistStateStore,
mLocalStore,
mObservers)
DOMCI_DATA(XULTreeBuilder, nsXULTreeBuilder)
@ -528,8 +531,7 @@ nsXULTreeBuilder::IsContainerOpen(int32_t aIndex, bool* aOpen)
nsTreeRows::iterator iter = mRows[aIndex];
if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) {
bool isOpen;
IsContainerOpen(iter->mMatch->mResult, &isOpen);
bool isOpen = IsContainerOpen(iter->mMatch->mResult);
iter->mContainerState = isOpen
? nsTreeRows::eContainerState_Open
@ -757,42 +759,16 @@ nsXULTreeBuilder::SetTree(nsITreeBoxObject* aTree)
}
NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
// Is our root's principal trusted?
// Only use the XUL store if the root's principal is trusted.
bool isTrusted = false;
nsresult rv = IsSystemPrincipal(mRoot->NodePrincipal(), &isTrusted);
if (NS_SUCCEEDED(rv) && isTrusted) {
// Get the datasource we intend to use to remember open state.
nsAutoString datasourceStr;
mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::statedatasource, datasourceStr);
// since we are trusted, use the user specified datasource
// if non specified, use localstore, which gives us
// persistence across sessions
if (! datasourceStr.IsEmpty()) {
gRDFService->GetDataSource(NS_ConvertUTF16toUTF8(datasourceStr).get(),
getter_AddRefs(mPersistStateStore));
}
else {
gRDFService->GetDataSource("rdf:local-store",
getter_AddRefs(mPersistStateStore));
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if(NS_WARN_IF(!mLocalStore)){
return NS_ERROR_NOT_INITIALIZED;
}
}
// Either no specific datasource was specified, or we failed
// to get one because we are not trusted.
//
// XXX if it were possible to ``write an arbitrary datasource
// back'', then we could also allow an untrusted document to
// use a statedatasource from the same codebase.
if (! mPersistStateStore) {
mPersistStateStore =
do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource");
}
NS_ASSERTION(mPersistStateStore, "failed to get a persistent state store");
if (! mPersistStateStore)
return NS_ERROR_FAILURE;
Rebuild();
EnsureSortVariables();
@ -830,34 +806,36 @@ nsXULTreeBuilder::ToggleOpenState(int32_t aIndex)
observer->OnToggleOpenState(aIndex);
}
if (mPersistStateStore) {
if (mLocalStore && mRoot) {
bool isOpen;
IsContainerOpen(aIndex, &isOpen);
nsCOMPtr<nsIRDFResource> container;
GetResourceFor(aIndex, getter_AddRefs(container));
if (! container)
nsIDocument* doc = mRoot->GetDocument();
if (!doc) {
return NS_ERROR_FAILURE;
}
bool hasProperty;
IsContainerOpen(container, &hasProperty);
nsIURI* docURI = doc->GetDocumentURI();
nsTreeRows::Row& row = *(mRows[aIndex]);
nsAutoString nodeid;
nsresult rv = row.mMatch->mResult->GetId(nodeid);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString utf8uri;
rv = docURI->GetSpec(utf8uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
NS_ConvertUTF8toUTF16 uri(utf8uri);
if (isOpen) {
if (hasProperty) {
mPersistStateStore->Unassert(container,
nsXULContentUtils::NC_open,
nsXULContentUtils::true_);
}
mLocalStore->RemoveValue(uri, nodeid, NS_LITERAL_STRING("open"));
CloseContainer(aIndex);
}
else {
if (! hasProperty) {
mPersistStateStore->Assert(container,
nsXULContentUtils::NC_open,
nsXULContentUtils::true_,
true);
}
} else {
mLocalStore->SetValue(uri, nodeid, NS_LITERAL_STRING("open"),
NS_LITERAL_STRING("true"));
OpenContainer(aIndex, result);
}
@ -1225,10 +1203,9 @@ nsXULTreeBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
if (NS_FAILED(rv) || ! mayProcessChildren) return NS_OK;
}
bool open;
IsContainerOpen(result, &open);
if (open)
if (IsContainerOpen(result)) {
OpenContainer(iter.GetRowIndex(), result);
}
}
}
@ -1636,9 +1613,7 @@ nsXULTreeBuilder::OpenSubtreeForQuerySet(nsTreeRows::Subtree* aSubtree,
// If this is open, then remember it so we can recursively add
// *its* rows to the tree.
bool isOpen = false;
IsContainerOpen(nextresult, &isOpen);
if (isOpen) {
if (IsContainerOpen(nextresult)) {
if (open.AppendElement(count) == nullptr)
return NS_ERROR_OUT_OF_MEMORY;
}
@ -1722,36 +1697,42 @@ nsXULTreeBuilder::RemoveMatchesFor(nsTreeRows::Subtree& subtree)
return NS_OK;
}
nsresult
nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen)
bool
nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult)
{
// items are never open if recursion is disabled
if ((mFlags & eDontRecurse) && aResult != mRootResult) {
*aOpen = false;
return NS_OK;
}
// items are never open if recursion is disabled
if ((mFlags & eDontRecurse) && aResult != mRootResult) {
return false;
}
nsCOMPtr<nsIRDFResource> id;
nsresult rv = GetResultResource(aResult, getter_AddRefs(id));
if (NS_FAILED(rv))
return rv;
if (!mLocalStore) {
return false;
}
return IsContainerOpen(id, aOpen);
}
nsIDocument* doc = mRoot->GetDocument();
if (!doc) {
return false;
}
nsresult
nsXULTreeBuilder::IsContainerOpen(nsIRDFResource* aResource, bool* aOpen)
{
if (mPersistStateStore)
mPersistStateStore->HasAssertion(aResource,
nsXULContentUtils::NC_open,
nsXULContentUtils::true_,
true,
aOpen);
else
*aOpen = false;
nsIURI* docURI = doc->GetDocumentURI();
return NS_OK;
nsAutoString nodeid;
nsresult rv = aResult->GetId(nodeid);
if (NS_FAILED(rv)) {
return false;
}
nsAutoCString utf8uri;
rv = docURI->GetSpec(utf8uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
NS_ConvertUTF8toUTF16 uri(utf8uri);
nsAutoString val;
mLocalStore->GetValue(uri, nodeid, NS_LITERAL_STRING("open"), val);
return val.EqualsLiteral("true");
}
int

View File

@ -10752,12 +10752,6 @@ class CGDescriptor(CGThing):
if props.isCrossOriginGetter:
crossOriginGetters.add(m.identifier.name)
if not m.readonly:
for extAttr in ["PutForwards", "Replaceable"]:
if m.getExtendedAttribute(extAttr):
raise TypeError("Writable attributes should not "
"have %s specified.\n"
"%s" %
(extAttr, m.location))
if m.isStatic():
assert descriptor.interface.hasInterfaceObject()
cgThings.append(CGStaticSetter(descriptor, m))

View File

@ -957,10 +957,15 @@ class IDLInterface(IDLObjectWithScope):
list(i.location for i in
self.interfacesBasedOnSelf if i.parent == self))
for member in self.members:
member.validate()
if self.isCallback() and member.getExtendedAttribute("Replaceable"):
raise WebIDLError("[Replaceable] used on an attribute on "
"interface %s which is a callback interface" %
self.identifier.name,
[self.location, member.location])
# Check that PutForwards refers to another attribute and that no
# cycles exist in forwarded assignments.
if member.isAttr():
@ -3236,6 +3241,15 @@ class IDLAttribute(IDLInterfaceMember):
raise WebIDLError("[PutForwards] takes an identifier",
[attr.location, self.location])
elif identifier == "Replaceable":
if not attr.noArguments():
raise WebIDLError("[Replaceable] must take no arguments",
[attr.location])
if not self.readonly:
raise WebIDLError("[Replaceable] is only allowed on readonly "
"attributes", [attr.location, self.location])
if self.isStatic():
raise WebIDLError("[Replaceable] is only allowed on non-static "
"attributes", [attr.location, self.location])
if self.getExtendedAttribute("PutForwards") is not None:
raise WebIDLError("[PutForwards] and [Replaceable] can't both "
"appear on the same attribute",

View File

@ -0,0 +1,58 @@
# 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/.
def should_throw(parser, harness, message, code):
parser = parser.reset();
threw = False
try:
parser.parse(code)
parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown: %s" % message)
def WebIDLTest(parser, harness):
# The [Replaceable] extended attribute MUST take no arguments.
should_throw(parser, harness, "no arguments", """
interface I {
[Replaceable=X] readonly attribute long A;
};
""")
# An attribute with the [Replaceable] extended attribute MUST NOT also be
# declared with the [PutForwards] extended attribute.
should_throw(parser, harness, "PutForwards", """
interface I {
[PutForwards=B, Replaceable] readonly attribute J A;
};
interface J {
attribute long B;
};
""")
# The [Replaceable] extended attribute MUST NOT be used on an attribute
# that is not read only.
should_throw(parser, harness, "writable attribute", """
interface I {
[Replaceable] attribute long A;
};
""")
# The [Replaceable] extended attribute MUST NOT be used on a static
# attribute.
should_throw(parser, harness, "static attribute", """
interface I {
[Replaceable] static readonly attribute long A;
};
""")
# The [Replaceable] extended attribute MUST NOT be used on an attribute
# declared on a callback interface.
should_throw(parser, harness, "callback interface", """
callback interface I {
[Replaceable] readonly attribute long A;
};
""")

View File

@ -68,6 +68,8 @@ this.BrowserElementParentBuilder = {
function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
debug("Creating new BrowserElementParent object for " + frameLoader);
this._domRequestCounter = 0;
this._domRequestReady = false;
this._pendingAPICalls = [];
this._pendingDOMRequests = {};
this._pendingSetInputMethodActive = [];
this._hasRemoteFrame = hasRemoteFrame;
@ -94,7 +96,7 @@ function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
let defineNoReturnMethod = function(name, fn) {
XPCNativeWrapper.unwrap(self._frameElement)[name] = function method() {
if (!self._mm) {
if (!self._domRequestReady) {
// Remote browser haven't been created, we just queue the API call.
let args = Array.slice(arguments);
args.unshift(self);
@ -181,7 +183,6 @@ function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
} else {
// if we are a pending frame, we setup message manager after
// observing remote-browser-frame-shown
this._pendingAPICalls = [];
Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
}
}
@ -370,6 +371,13 @@ BrowserElementParent.prototype = {
this._ownerVisibilityChange();
}
if (!this._domRequestReady) {
// At least, one message listener such as for hello is registered.
// So we can use sendAsyncMessage now.
this._domRequestReady = true;
this._runPendingAPICall();
}
return {
name: this._frameElement.getAttribute('name'),
fullscreenAllowed:
@ -531,7 +539,7 @@ BrowserElementParent.prototype = {
Services.DOMRequest.fireErrorAsync(req, "fail");
}
};
if (this._mm) {
if (this._domRequestReady) {
send();
} else {
// Child haven't been loaded.
@ -797,7 +805,7 @@ BrowserElementParent.prototype = {
if (self._nextPaintListeners.push(listener) == 1)
self._sendAsyncMsg('activate-next-paint-listener');
};
if (!this._mm) {
if (!this._domRequestReady) {
this._pendingAPICalls.push(run);
} else {
run();
@ -820,7 +828,7 @@ BrowserElementParent.prototype = {
if (self._nextPaintListeners.length == 0)
self._sendAsyncMsg('deactivate-next-paint-listener');
};
if (!this._mm) {
if (!this._domRequestReady) {
this._pendingAPICalls.push(run);
} else {
run();
@ -908,7 +916,6 @@ BrowserElementParent.prototype = {
if (!this._mm) {
this._setupMessageListener();
this._registerAppManifest();
this._runPendingAPICall();
}
Services.obs.removeObserver(this, 'remote-browser-frame-shown');
}

View File

@ -51,11 +51,16 @@ function runTest() {
// should have 'backgroundLRU' equals 1
var p = expectPriorityWithBackgroundLRUSet(childID, '1');
iframe2.setVisible(false);
document.body.removeChild(iframe2);
return p;
}).then(SimpleTest.finish);
}).then(function() {
// Don't call removeChild immediately after calling setVisible.
// setVisible on remote browser is async method, so we should wait
// until it sends to the child process.
document.body.removeChild(iframe2);
SimpleTest.finish();
});
document.body.appendChild(iframe1);
}

View File

@ -2021,6 +2021,9 @@ EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
} else {
ChangeTextSize(change);
}
nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument),
NS_LITERAL_STRING("ZoomChangeUsingMouseWheel"),
true, true);
}
}

View File

@ -65,6 +65,9 @@ scriptFromStringBlocked = An attempt to call JavaScript from a string (by callin
# LOCALIZATION NOTE (hostNameMightBeKeyword):
# %1$S is the hostname in question and %2$S is the keyword
hostNameMightBeKeyword = Interpreting %1$S as a hostname, not a keyword. If you intended this to be a keyword, use '%2$S' (wrapped in single quotes).
# LOCALIZATION NOTE (notSupportingDirective):
# directive is not supported (e.g. 'reflected-xss')
notSupportingDirective = Not supporting directive '%1$S'. Directive and values will be ignored.
# CSP Errors:
policyURINotAlone = policy-uri directive can only appear alone

View File

@ -59,7 +59,4 @@ RESOURCE_FILES += [
'res/text_caret_tilt_right@1.5x.png',
'res/text_caret_tilt_right@2.25x.png',
'res/text_caret_tilt_right@2x.png',
'res/text_selection_handle.png',
'res/text_selection_handle@1.5.png',
'res/text_selection_handle@2.png',
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -3,11 +3,17 @@
* 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/. */
#include <algorithm>
#include <stdio.h>
#include "CreateElementTxn.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/Casting.h"
#include "CreateElementTxn.h"
#include "nsAlgorithm.h"
#include "nsAString.h"
#include "nsDebug.h"
#include "nsEditor.h"
#include "nsError.h"
@ -21,14 +27,19 @@
#include "nsReadableUtils.h"
#include "nsStringFwd.h"
#include "nsString.h"
#include "nsAString.h"
#include <algorithm>
using namespace mozilla;
using namespace mozilla::dom;
CreateElementTxn::CreateElementTxn()
CreateElementTxn::CreateElementTxn(nsEditor& aEditor,
nsIAtom& aTag,
nsINode& aParent,
int32_t aOffsetInParent)
: EditTxn()
, mEditor(&aEditor)
, mTag(&aTag)
, mParent(&aParent)
, mOffsetInParent(aOffsetInParent)
{
}
@ -45,114 +56,86 @@ NS_IMPL_ADDREF_INHERITED(CreateElementTxn, EditTxn)
NS_IMPL_RELEASE_INHERITED(CreateElementTxn, EditTxn)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTxn)
NS_INTERFACE_MAP_END_INHERITING(EditTxn)
NS_IMETHODIMP CreateElementTxn::Init(nsEditor *aEditor,
const nsAString &aTag,
nsIDOMNode *aParent,
uint32_t aOffsetInParent)
NS_IMETHODIMP
CreateElementTxn::DoTransaction()
{
NS_ASSERTION(aEditor&&aParent, "null args");
if (!aEditor || !aParent) { return NS_ERROR_NULL_POINTER; }
MOZ_ASSERT(mEditor && mTag && mParent);
mEditor = aEditor;
mTag = aTag;
mParent = do_QueryInterface(aParent);
mOffsetInParent = aOffsetInParent;
return NS_OK;
}
mNewNode = mEditor->CreateHTMLContent(mTag);
NS_ENSURE_STATE(mNewNode);
NS_IMETHODIMP CreateElementTxn::DoTransaction(void)
{
NS_ASSERTION(mEditor && mParent, "bad state");
NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<Element> newContent =
mEditor->CreateHTMLContent(nsCOMPtr<nsIAtom>(do_GetAtom(mTag)));
NS_ENSURE_STATE(newContent);
mNewNode = newContent->AsDOMNode();
// Try to insert formatting whitespace for the new node:
mEditor->MarkNodeDirty(mNewNode);
mEditor->MarkNodeDirty(GetAsDOMNode(mNewNode));
// insert the new node
if (CreateElementTxn::eAppend == int32_t(mOffsetInParent)) {
nsCOMPtr<nsIDOMNode> resultNode;
return mParent->AppendChild(mNewNode, getter_AddRefs(resultNode));
// Insert the new node
ErrorResult rv;
if (mOffsetInParent == -1) {
mParent->AppendChild(*mNewNode, rv);
return rv.ErrorCode();
}
nsCOMPtr<nsINode> parent = do_QueryInterface(mParent);
NS_ENSURE_STATE(parent);
mOffsetInParent = std::min(mOffsetInParent,
static_cast<int32_t>(mParent->GetChildCount()));
mOffsetInParent = std::min(mOffsetInParent, parent->GetChildCount());
// Note, it's ok for mRefNode to be null. That means append
mRefNode = mParent->GetChildAt(mOffsetInParent);
// note, it's ok for mRefNode to be null. that means append
nsIContent* refNode = parent->GetChildAt(mOffsetInParent);
mRefNode = refNode ? refNode->AsDOMNode() : nullptr;
mParent->InsertBefore(*mNewNode, mRefNode, rv);
NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());
nsCOMPtr<nsIDOMNode> resultNode;
nsresult result = mParent->InsertBefore(mNewNode, mRefNode, getter_AddRefs(resultNode));
NS_ENSURE_SUCCESS(result, result);
// only set selection to insertion point if editor gives permission
bool bAdjustSelection;
mEditor->ShouldTxnSetSelection(&bAdjustSelection);
if (!bAdjustSelection) {
// do nothing - dom range gravity will adjust selection
// Only set selection to insertion point if editor gives permission
if (!mEditor->GetShouldTxnSetSelection()) {
// Do nothing - DOM range gravity will adjust selection
return NS_OK;
}
nsCOMPtr<nsISelection> selection;
result = mEditor->GetSelection(getter_AddRefs(selection));
NS_ENSURE_SUCCESS(result, result);
nsRefPtr<Selection> selection = mEditor->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIContent> parentContent = do_QueryInterface(mParent);
NS_ENSURE_STATE(parentContent);
result = selection->CollapseNative(parentContent,
parentContent->IndexOf(newContent) + 1);
NS_ASSERTION((NS_SUCCEEDED(result)), "selection could not be collapsed after insert.");
return result;
rv = selection->CollapseNative(mParent, mParent->IndexOf(mNewNode) + 1);
NS_ASSERTION(!rv.Failed(),
"selection could not be collapsed after insert");
return NS_OK;
}
NS_IMETHODIMP CreateElementTxn::UndoTransaction(void)
NS_IMETHODIMP
CreateElementTxn::UndoTransaction()
{
NS_ASSERTION(mEditor && mParent, "bad state");
NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
MOZ_ASSERT(mEditor && mParent);
nsCOMPtr<nsIDOMNode> resultNode;
return mParent->RemoveChild(mNewNode, getter_AddRefs(resultNode));
ErrorResult rv;
mParent->RemoveChild(*mNewNode, rv);
return rv.ErrorCode();
}
NS_IMETHODIMP CreateElementTxn::RedoTransaction(void)
NS_IMETHODIMP
CreateElementTxn::RedoTransaction()
{
NS_ASSERTION(mEditor && mParent, "bad state");
NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
MOZ_ASSERT(mEditor && mParent);
// first, reset mNewNode so it has no attributes or content
nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(mNewNode);
if (nodeAsText)
{
nodeAsText->SetData(EmptyString());
}
// now, reinsert mNewNode
nsCOMPtr<nsIDOMNode> resultNode;
return mParent->InsertBefore(mNewNode, mRefNode, getter_AddRefs(resultNode));
// First, reset mNewNode so it has no attributes or content
// XXX We never actually did this, we only cleared mNewNode's contents if it
// was a CharacterData node (which it's not, it's an Element)
// Now, reinsert mNewNode
ErrorResult rv;
mParent->InsertBefore(*mNewNode, mRefNode, rv);
return rv.ErrorCode();
}
NS_IMETHODIMP CreateElementTxn::GetTxnDescription(nsAString& aString)
NS_IMETHODIMP
CreateElementTxn::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("CreateElementTxn: ");
aString += mTag;
aString += nsDependentAtomString(mTag);
return NS_OK;
}
NS_IMETHODIMP CreateElementTxn::GetNewNode(nsIDOMNode **aNewNode)
already_AddRefed<Element>
CreateElementTxn::GetNewNode()
{
NS_ENSURE_TRUE(aNewNode, NS_ERROR_NULL_POINTER);
NS_ENSURE_TRUE(mNewNode, NS_ERROR_NOT_INITIALIZED);
*aNewNode = mNewNode;
NS_ADDREF(*aNewNode);
return NS_OK;
return nsCOMPtr<Element>(mNewNode).forget();
}

View File

@ -9,21 +9,24 @@
#include "EditTxn.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMNode.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "nscore.h"
class nsEditor;
class nsIAtom;
class nsIContent;
class nsINode;
/**
* A transaction that creates a new node in the content tree.
*/
namespace mozilla {
namespace dom {
class Element;
class CreateElementTxn : public EditTxn
{
public:
enum { eAppend=-1 };
/** Initialize the transaction.
* @param aEditor the provider of basic editing functionality
* @param aTag the tag (P, HR, TABLE, etc.) for the new element
@ -31,12 +34,10 @@ public:
* @param aOffsetInParent the location in aParent to insert the new element
* if eAppend, the new element is appended as the last child
*/
NS_IMETHOD Init(nsEditor *aEditor,
const nsAString& aTag,
nsIDOMNode *aParent,
uint32_t aOffsetInParent);
CreateElementTxn();
CreateElementTxn(nsEditor& aEditor,
nsIAtom& aTag,
nsINode& aParent,
int32_t aOffsetInParent);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CreateElementTxn, EditTxn)
@ -45,28 +46,31 @@ public:
NS_IMETHOD RedoTransaction();
NS_IMETHOD GetNewNode(nsIDOMNode **aNewNode);
already_AddRefed<Element> GetNewNode();
protected:
virtual ~CreateElementTxn();
/** the document into which the new node will be inserted */
nsEditor* mEditor;
/** the tag (mapping to object type) for the new element */
nsString mTag;
nsCOMPtr<nsIAtom> mTag;
/** the node into which the new node will be inserted */
nsCOMPtr<nsIDOMNode> mParent;
nsCOMPtr<nsINode> mParent;
/** the index in mParent for the new node */
uint32_t mOffsetInParent;
int32_t mOffsetInParent;
/** the new node to insert */
nsCOMPtr<nsIDOMNode> mNewNode;
nsCOMPtr<Element> mNewNode;
/** the node we will insert mNewNode before. We compute this ourselves. */
nsCOMPtr<nsIDOMNode> mRefNode;
nsCOMPtr<nsIContent> mRefNode;
};
}
}
#endif

View File

@ -139,8 +139,6 @@ DeleteRangeTxn::CreateTxnsToDeleteBetween(nsINode* aNode,
// see what kind of node we have
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
// if the node is a chardata node, then delete chardata content
nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn();
int32_t numToDel;
if (aStartOffset == aEndOffset) {
numToDel = 1;
@ -148,9 +146,14 @@ DeleteRangeTxn::CreateTxnsToDeleteBetween(nsINode* aNode,
numToDel = aEndOffset - aStartOffset;
}
nsCOMPtr<nsIDOMCharacterData> charDataNode = do_QueryInterface(aNode);
nsresult res = txn->Init(mEditor, charDataNode, aStartOffset, numToDel,
mRangeUpdater);
nsRefPtr<nsGenericDOMDataNode> charDataNode =
static_cast<nsGenericDOMDataNode*>(aNode);
nsRefPtr<DeleteTextTxn> txn =
new DeleteTextTxn(*mEditor, *charDataNode, aStartOffset, numToDel,
mRangeUpdater);
nsresult res = txn->Init();
NS_ENSURE_SUCCESS(res, res);
AppendChild(txn);
@ -193,11 +196,12 @@ DeleteRangeTxn::CreateTxnsToDeleteContent(nsINode* aNode,
}
if (numToDelete) {
nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn();
nsRefPtr<nsGenericDOMDataNode> dataNode =
static_cast<nsGenericDOMDataNode*>(aNode);
nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn(*mEditor, *dataNode,
start, numToDelete, mRangeUpdater);
nsCOMPtr<nsIDOMCharacterData> charDataNode = do_QueryInterface(aNode);
nsresult res = txn->Init(mEditor, charDataNode, start, numToDelete,
mRangeUpdater);
nsresult res = txn->Init();
NS_ENSURE_SUCCESS(res, res);
AppendChild(txn);

View File

@ -19,14 +19,19 @@
using namespace mozilla;
using namespace mozilla::dom;
DeleteTextTxn::DeleteTextTxn() :
EditTxn(),
mEditor(nullptr),
mCharData(),
mOffset(0),
mNumCharsToDelete(0),
mRangeUpdater(nullptr)
DeleteTextTxn::DeleteTextTxn(nsEditor& aEditor,
nsGenericDOMDataNode& aCharData, uint32_t aOffset,
uint32_t aNumCharsToDelete,
nsRangeUpdater* aRangeUpdater)
: EditTxn()
, mEditor(aEditor)
, mCharData(&aCharData)
, mOffset(aOffset)
, mNumCharsToDelete(aNumCharsToDelete)
, mRangeUpdater(aRangeUpdater)
{
NS_ASSERTION(mCharData->Length() >= aOffset + aNumCharsToDelete,
"Trying to delete more characters than in node");
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTxn, EditTxn,
@ -35,42 +40,23 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTxn, EditTxn,
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteTextTxn)
NS_INTERFACE_MAP_END_INHERITING(EditTxn)
NS_IMETHODIMP
DeleteTextTxn::Init(nsEditor* aEditor,
nsIDOMCharacterData* aCharData,
uint32_t aOffset,
uint32_t aNumCharsToDelete,
nsRangeUpdater* aRangeUpdater)
nsresult
DeleteTextTxn::Init()
{
MOZ_ASSERT(aEditor && aCharData);
mEditor = aEditor;
mCharData = aCharData;
// do nothing if the node is read-only
if (!mEditor->IsModifiableNode(mCharData)) {
// Do nothing if the node is read-only
if (!mEditor.IsModifiableNode(mCharData)) {
return NS_ERROR_FAILURE;
}
mOffset = aOffset;
mNumCharsToDelete = aNumCharsToDelete;
#ifdef DEBUG
uint32_t length;
mCharData->GetLength(&length);
NS_ASSERTION(length >= aOffset + aNumCharsToDelete,
"Trying to delete more characters than in node");
#endif
mDeletedText.Truncate();
mRangeUpdater = aRangeUpdater;
return NS_OK;
}
NS_IMETHODIMP
DeleteTextTxn::DoTransaction()
{
MOZ_ASSERT(mEditor && mCharData);
MOZ_ASSERT(mCharData);
// get the text that we're about to delete
// Get the text that we're about to delete
nsresult res = mCharData->SubstringData(mOffset, mNumCharsToDelete,
mDeletedText);
MOZ_ASSERT(NS_SUCCEEDED(res));
@ -81,27 +67,25 @@ DeleteTextTxn::DoTransaction()
mRangeUpdater->SelAdjDeleteText(mCharData, mOffset, mNumCharsToDelete);
}
// only set selection to deletion point if editor gives permission
bool bAdjustSelection;
mEditor->ShouldTxnSetSelection(&bAdjustSelection);
if (bAdjustSelection) {
nsRefPtr<Selection> selection = mEditor->GetSelection();
// Only set selection to deletion point if editor gives permission
if (mEditor.GetShouldTxnSetSelection()) {
nsRefPtr<Selection> selection = mEditor.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
res = selection->Collapse(mCharData, mOffset);
NS_ASSERTION(NS_SUCCEEDED(res),
"selection could not be collapsed after undo of deletetext.");
"Selection could not be collapsed after undo of deletetext");
NS_ENSURE_SUCCESS(res, res);
}
// else do nothing - dom range gravity will adjust selection
// Else do nothing - DOM Range gravity will adjust selection
return NS_OK;
}
//XXX: we may want to store the selection state and restore it properly
// was it an insertion point or an extended selection?
//XXX: We may want to store the selection state and restore it properly. Was
// it an insertion point or an extended selection?
NS_IMETHODIMP
DeleteTextTxn::UndoTransaction()
{
MOZ_ASSERT(mEditor && mCharData);
MOZ_ASSERT(mCharData);
return mCharData->InsertData(mOffset, mDeletedText);
}

View File

@ -9,14 +9,17 @@
#include "EditTxn.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsGenericDOMDataNode.h"
#include "nsID.h"
#include "nsIDOMCharacterData.h"
#include "nsString.h"
#include "nscore.h"
class nsEditor;
class nsRangeUpdater;
namespace mozilla {
namespace dom {
/**
* A transaction that removes text from a content node.
*/
@ -29,13 +32,13 @@ public:
* @param aOffset the location in aElement to begin the deletion
* @param aNumCharsToDelete the number of characters to delete. Not the number of bytes!
*/
NS_IMETHOD Init(nsEditor* aEditor,
nsIDOMCharacterData* aCharData,
uint32_t aOffset,
uint32_t aNumCharsToDelete,
nsRangeUpdater* aRangeUpdater);
DeleteTextTxn(nsEditor& aEditor,
nsGenericDOMDataNode& aCharData,
uint32_t aOffset,
uint32_t aNumCharsToDelete,
nsRangeUpdater* aRangeUpdater);
DeleteTextTxn();
nsresult Init();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteTextTxn, EditTxn)
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
@ -49,10 +52,10 @@ public:
protected:
/** the provider of basic editing operations */
nsEditor* mEditor;
nsEditor& mEditor;
/** the CharacterData node to operate upon */
nsCOMPtr<nsIDOMCharacterData> mCharData;
nsRefPtr<nsGenericDOMDataNode> mCharData;
/** the offset into mCharData where the deletion is to take place */
uint32_t mOffset;
@ -67,4 +70,7 @@ protected:
nsRangeUpdater* mRangeUpdater;
};
}
}
#endif

View File

@ -1,112 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdio.h> // for printf
#include "InsertElementTxn.h"
#include "nsAString.h"
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
#include "nsEditor.h" // for nsEditor
#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc
#include "nsIContent.h" // for nsIContent
#include "nsINode.h" // for nsINode
#include "nsISelection.h" // for nsISelection
#include "nsMemory.h" // for nsMemory
#include "nsReadableUtils.h" // for ToNewCString
#include "nsString.h" // for nsString
using namespace mozilla;
InsertElementTxn::InsertElementTxn()
: EditTxn()
{
}
InsertElementTxn::~InsertElementTxn()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertElementTxn, EditTxn,
mNode,
mParent)
NS_IMPL_ADDREF_INHERITED(InsertElementTxn, EditTxn)
NS_IMPL_RELEASE_INHERITED(InsertElementTxn, EditTxn)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertElementTxn)
NS_INTERFACE_MAP_END_INHERITING(EditTxn)
NS_IMETHODIMP InsertElementTxn::Init(nsIDOMNode *aNode,
nsIDOMNode *aParent,
int32_t aOffset,
nsIEditor *aEditor)
{
NS_ASSERTION(aNode && aParent && aEditor, "bad arg");
NS_ENSURE_TRUE(aNode && aParent && aEditor, NS_ERROR_NULL_POINTER);
mNode = do_QueryInterface(aNode);
mParent = do_QueryInterface(aParent);
mOffset = aOffset;
mEditor = aEditor;
NS_ENSURE_TRUE(mNode && mParent && mEditor, NS_ERROR_INVALID_ARG);
return NS_OK;
}
NS_IMETHODIMP InsertElementTxn::DoTransaction(void)
{
NS_ENSURE_TRUE(mNode && mParent, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsINode> parent = do_QueryInterface(mParent);
NS_ENSURE_STATE(parent);
uint32_t count = parent->GetChildCount();
if (mOffset > int32_t(count) || mOffset == -1) {
// -1 is sentinel value meaning "append at end"
mOffset = count;
}
// note, it's ok for refContent to be null. that means append
nsCOMPtr<nsIContent> refContent = parent->GetChildAt(mOffset);
nsCOMPtr<nsIDOMNode> refNode = refContent ? refContent->AsDOMNode() : nullptr;
mEditor->MarkNodeDirty(mNode);
nsCOMPtr<nsIDOMNode> resultNode;
nsresult result = mParent->InsertBefore(mNode, refNode, getter_AddRefs(resultNode));
NS_ENSURE_SUCCESS(result, result);
NS_ENSURE_TRUE(resultNode, NS_ERROR_NULL_POINTER);
// only set selection to insertion point if editor gives permission
bool bAdjustSelection;
mEditor->ShouldTxnSetSelection(&bAdjustSelection);
if (bAdjustSelection)
{
nsCOMPtr<nsISelection> selection;
result = mEditor->GetSelection(getter_AddRefs(selection));
NS_ENSURE_SUCCESS(result, result);
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
// place the selection just after the inserted element
selection->Collapse(mParent, mOffset+1);
}
else
{
// do nothing - dom range gravity will adjust selection
}
return result;
}
NS_IMETHODIMP InsertElementTxn::UndoTransaction(void)
{
NS_ENSURE_TRUE(mNode && mParent, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsIDOMNode> resultNode;
return mParent->RemoveChild(mNode, getter_AddRefs(resultNode));
}
NS_IMETHODIMP InsertElementTxn::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("InsertElementTxn");
return NS_OK;
}

View File

@ -0,0 +1,93 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdio.h> // for printf
#include "mozilla/dom/Selection.h" // for Selection
#include "InsertNodeTxn.h"
#include "nsAString.h"
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
#include "nsEditor.h" // for nsEditor
#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc
#include "nsIContent.h" // for nsIContent
#include "nsMemory.h" // for nsMemory
#include "nsReadableUtils.h" // for ToNewCString
#include "nsString.h" // for nsString
using namespace mozilla;
using namespace mozilla::dom;
InsertNodeTxn::InsertNodeTxn(nsIContent& aNode, nsINode& aParent,
int32_t aOffset, nsEditor& aEditor)
: EditTxn()
, mNode(&aNode)
, mParent(&aParent)
, mOffset(aOffset)
, mEditor(aEditor)
{
}
InsertNodeTxn::~InsertNodeTxn()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTxn, EditTxn,
mNode,
mParent)
NS_IMPL_ADDREF_INHERITED(InsertNodeTxn, EditTxn)
NS_IMPL_RELEASE_INHERITED(InsertNodeTxn, EditTxn)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertNodeTxn)
NS_INTERFACE_MAP_END_INHERITING(EditTxn)
NS_IMETHODIMP
InsertNodeTxn::DoTransaction()
{
MOZ_ASSERT(mNode && mParent);
uint32_t count = mParent->GetChildCount();
if (mOffset > static_cast<int32_t>(count) || mOffset == -1) {
// -1 is sentinel value meaning "append at end"
mOffset = count;
}
// Note, it's ok for ref to be null. That means append.
nsCOMPtr<nsIContent> ref = mParent->GetChildAt(mOffset);
mEditor.MarkNodeDirty(GetAsDOMNode(mNode));
ErrorResult rv;
mParent->InsertBefore(*mNode, ref, rv);
NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());
// Only set selection to insertion point if editor gives permission
if (mEditor.GetShouldTxnSetSelection()) {
nsRefPtr<Selection> selection = mEditor.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
// Place the selection just after the inserted element
selection->Collapse(mParent, mOffset + 1);
} else {
// Do nothing - DOM Range gravity will adjust selection
}
return NS_OK;
}
NS_IMETHODIMP
InsertNodeTxn::UndoTransaction()
{
MOZ_ASSERT(mNode && mParent);
ErrorResult rv;
mParent->RemoveChild(*mNode, rv);
return rv.ErrorCode();
}
NS_IMETHODIMP
InsertNodeTxn::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("InsertNodeTxn");
return NS_OK;
}

View File

@ -3,22 +3,24 @@
* 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/. */
#ifndef InsertElementTxn_h__
#define InsertElementTxn_h__
#ifndef InsertNodeTxn_h__
#define InsertNodeTxn_h__
#include "EditTxn.h" // for EditTxn, NS_DECL_EDITTXN
#include "nsCOMPtr.h" // for nsCOMPtr
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMNode.h" // for nsIDOMNode
#include "nsIContent.h" // for nsIContent
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS_INHERITED
#include "nscore.h" // for NS_IMETHOD
class nsIEditor;
class nsEditor;
namespace mozilla {
namespace dom {
/**
* A transaction that inserts a single element
*/
class InsertElementTxn : public EditTxn
class InsertNodeTxn : public EditTxn
{
public:
/** initialize the transaction.
@ -26,32 +28,31 @@ public:
* @param aParent the node to insert into
* @param aOffset the offset in aParent to insert aNode
*/
NS_IMETHOD Init(nsIDOMNode *aNode,
nsIDOMNode *aParent,
int32_t aOffset,
nsIEditor *aEditor);
InsertElementTxn();
InsertNodeTxn(nsIContent& aNode, nsINode& aParent, int32_t aOffset,
nsEditor& aEditor);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertElementTxn, EditTxn)
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertNodeTxn, EditTxn)
NS_DECL_EDITTXN
protected:
virtual ~InsertElementTxn();
virtual ~InsertNodeTxn();
/** the element to insert */
nsCOMPtr<nsIDOMNode> mNode;
nsCOMPtr<nsIContent> mNode;
/** the node into which the new node will be inserted */
nsCOMPtr<nsIDOMNode> mParent;
/** the editor for this transaction */
nsIEditor* mEditor;
nsCOMPtr<nsINode> mParent;
/** the index in mParent for the new node */
int32_t mOffset;
/** the editor for this transaction */
nsEditor& mEditor;
};
}
}
#endif

View File

@ -16,7 +16,7 @@ UNIFIED_SOURCES += [
'EditAggregateTxn.cpp',
'EditTxn.cpp',
'IMETextTxn.cpp',
'InsertElementTxn.cpp',
'InsertNodeTxn.cpp',
'InsertTextTxn.cpp',
'JoinElementTxn.cpp',
'nsEditor.cpp',

View File

@ -16,7 +16,7 @@
#include "EditAggregateTxn.h" // for EditAggregateTxn
#include "EditTxn.h" // for EditTxn
#include "IMETextTxn.h" // for IMETextTxn
#include "InsertElementTxn.h" // for InsertElementTxn
#include "InsertNodeTxn.h" // for InsertNodeTxn
#include "InsertTextTxn.h" // for InsertTextTxn
#include "JoinElementTxn.h" // for JoinElementTxn
#include "PlaceholderTxn.h" // for PlaceholderTxn
@ -1335,72 +1335,89 @@ NS_IMETHODIMP nsEditor::SetSpellcheckUserOverride(bool enable)
return SyncRealTimeSpell();
}
NS_IMETHODIMP nsEditor::CreateNode(const nsAString& aTag,
nsIDOMNode * aParent,
int32_t aPosition,
nsIDOMNode ** aNewNode)
NS_IMETHODIMP
nsEditor::CreateNode(const nsAString& aTag,
nsIDOMNode* aParent,
int32_t aPosition,
nsIDOMNode** aNewNode)
{
int32_t i;
nsCOMPtr<nsIAtom> tag = do_GetAtom(aTag);
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
NS_ENSURE_STATE(parent);
*aNewNode = GetAsDOMNode(CreateNode(tag, parent, aPosition).take());
NS_ENSURE_STATE(*aNewNode);
return NS_OK;
}
already_AddRefed<Element>
nsEditor::CreateNode(nsIAtom* aTag,
nsINode* aParent,
int32_t aPosition)
{
MOZ_ASSERT(aTag && aParent);
nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext);
for (i = 0; i < mActionListeners.Count(); i++)
mActionListeners[i]->WillCreateNode(aTag, aParent, aPosition);
for (int32_t i = 0; i < mActionListeners.Count(); i++) {
mActionListeners[i]->WillCreateNode(nsDependentAtomString(aTag),
GetAsDOMNode(aParent), aPosition);
}
nsRefPtr<CreateElementTxn> txn;
nsresult result = CreateTxnForCreateElement(aTag, aParent, aPosition,
getter_AddRefs(txn));
if (NS_SUCCEEDED(result))
{
result = DoTransaction(txn);
if (NS_SUCCEEDED(result))
{
result = txn->GetNewNode(aNewNode);
NS_ASSERTION((NS_SUCCEEDED(result)), "GetNewNode can't fail if txn::DoTransaction succeeded.");
}
nsCOMPtr<Element> ret;
nsRefPtr<CreateElementTxn> txn =
CreateTxnForCreateElement(*aTag, *aParent, aPosition);
nsresult res = DoTransaction(txn);
if (NS_SUCCEEDED(res)) {
ret = txn->GetNewNode();
MOZ_ASSERT(ret);
}
mRangeUpdater.SelAdjCreateNode(aParent, aPosition);
for (i = 0; i < mActionListeners.Count(); i++)
mActionListeners[i]->DidCreateNode(aTag, *aNewNode, aParent, aPosition, result);
return result;
}
nsresult
nsEditor::InsertNode(nsIContent* aContent, nsINode* aParent, int32_t aPosition)
{
MOZ_ASSERT(aContent && aParent);
return InsertNode(GetAsDOMNode(aContent), GetAsDOMNode(aParent), aPosition);
}
NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode,
nsIDOMNode * aParent,
int32_t aPosition)
{
int32_t i;
nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
for (i = 0; i < mActionListeners.Count(); i++)
mActionListeners[i]->WillInsertNode(aNode, aParent, aPosition);
nsRefPtr<InsertElementTxn> txn;
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
nsresult result = CreateTxnForInsertElement(node->AsDOMNode(), parent->AsDOMNode(),
aPosition, getter_AddRefs(txn));
if (NS_SUCCEEDED(result)) {
result = DoTransaction(txn);
for (int32_t i = 0; i < mActionListeners.Count(); i++) {
mActionListeners[i]->DidCreateNode(nsDependentAtomString(aTag),
GetAsDOMNode(ret),
GetAsDOMNode(aParent), aPosition,
res);
}
mRangeUpdater.SelAdjInsertNode(aParent, aPosition);
return ret.forget();
}
for (i = 0; i < mActionListeners.Count(); i++)
mActionListeners[i]->DidInsertNode(aNode, aParent, aPosition, result);
return result;
NS_IMETHODIMP
nsEditor::InsertNode(nsIDOMNode* aNode, nsIDOMNode* aParent, int32_t aPosition)
{
nsCOMPtr<nsIContent> node = do_QueryInterface(aNode);
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
NS_ENSURE_TRUE(node && parent, NS_ERROR_NULL_POINTER);
return InsertNode(*node, *parent, aPosition);
}
nsresult
nsEditor::InsertNode(nsIContent& aNode, nsINode& aParent, int32_t aPosition)
{
nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
for (int32_t i = 0; i < mActionListeners.Count(); i++) {
mActionListeners[i]->WillInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(),
aPosition);
}
nsRefPtr<InsertNodeTxn> txn = CreateTxnForInsertNode(aNode, aParent,
aPosition);
nsresult res = DoTransaction(txn);
mRangeUpdater.SelAdjInsertNode(aParent.AsDOMNode(), aPosition);
for (int32_t i = 0; i < mActionListeners.Count(); i++) {
mActionListeners[i]->DidInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(),
aPosition, res);
}
return res;
}
@ -1555,8 +1572,8 @@ nsEditor::ReplaceContainer(Element* aOldContainer,
// notify our internal selection state listener
// (Note: A nsAutoSelectionReset object must be created
// before calling this to initialize mRangeUpdater)
nsAutoReplaceContainerSelNotify selStateNotify(mRangeUpdater,
aOldContainer->AsDOMNode(), ret->AsDOMNode());
AutoReplaceContainerSelNotify selStateNotify(mRangeUpdater, aOldContainer,
ret);
{
nsAutoTxnsConserveSelection conserveSelection(this);
while (aOldContainer->HasChildren()) {
@ -1565,13 +1582,13 @@ nsEditor::ReplaceContainer(Element* aOldContainer,
res = DeleteNode(child);
NS_ENSURE_SUCCESS(res, nullptr);
res = InsertNode(child, ret, -1);
res = InsertNode(*child, *ret, -1);
NS_ENSURE_SUCCESS(res, nullptr);
}
}
// insert new container into tree
res = InsertNode(ret, parent, offset);
res = InsertNode(*ret, *parent, offset);
NS_ENSURE_SUCCESS(res, nullptr);
// delete old container
@ -1581,171 +1598,129 @@ nsEditor::ReplaceContainer(Element* aOldContainer,
return ret.forget();
}
///////////////////////////////////////////////////////////////////////////
// RemoveContainer: remove inNode, reparenting its children into their
// the parent of inNode
///////////////////////////////////////////////////////////////////////////////
// RemoveContainer: remove inNode, reparenting its children (if any) into the
// parent of inNode
//
nsresult
nsEditor::RemoveContainer(nsIDOMNode* aNode)
nsEditor::RemoveContainer(nsIContent* aNode)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return RemoveContainer(node);
}
nsresult
nsEditor::RemoveContainer(nsINode* aNode)
{
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> parent = aNode->GetParentNode();
NS_ENSURE_STATE(parent);
int32_t offset = parent->IndexOf(aNode);
// loop through the child nodes of inNode and promote them
// into inNode's parent.
// Loop through the children of inNode and promote them into inNode's parent
uint32_t nodeOrigLen = aNode->GetChildCount();
// notify our internal selection state listener
nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, aNode, parent, offset, nodeOrigLen);
nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, aNode, parent,
offset, nodeOrigLen);
while (aNode->HasChildren()) {
nsCOMPtr<nsIContent> child = aNode->GetLastChild();
nsresult rv = DeleteNode(child->AsDOMNode());
nsresult rv = DeleteNode(child);
NS_ENSURE_SUCCESS(rv, rv);
rv = InsertNode(child->AsDOMNode(), parent->AsDOMNode(), offset);
rv = InsertNode(*child, *parent, offset);
NS_ENSURE_SUCCESS(rv, rv);
}
return DeleteNode(aNode->AsDOMNode());
return DeleteNode(aNode);
}
///////////////////////////////////////////////////////////////////////////
// InsertContainerAbove: insert a new parent for inNode, returned in outNode,
// which is contructed to be of type aNodeType. outNode becomes
// a child of inNode's earlier parent.
// Callers responsibility to make sure inNode's can be child
// of outNode, and outNode can be child of old parent.
nsresult
nsEditor::InsertContainerAbove( nsIDOMNode *inNode,
nsCOMPtr<nsIDOMNode> *outNode,
const nsAString &aNodeType,
const nsAString *aAttribute,
const nsAString *aValue)
{
NS_ENSURE_TRUE(inNode && outNode, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIContent> node = do_QueryInterface(inNode);
NS_ENSURE_STATE(node);
nsCOMPtr<dom::Element> element;
nsresult rv = InsertContainerAbove(node, getter_AddRefs(element), aNodeType,
aAttribute, aValue);
*outNode = element ? element->AsDOMNode() : nullptr;
return rv;
}
nsresult
///////////////////////////////////////////////////////////////////////////////
// InsertContainerAbove: Insert a new parent for inNode, which is contructed to
// be of type aNodeType. outNode becomes a child of
// inNode's earlier parent. Caller's responsibility to
// make sure inNode's can be child of outNode, and
// outNode can be child of old parent.
already_AddRefed<Element>
nsEditor::InsertContainerAbove(nsIContent* aNode,
dom::Element** aOutNode,
const nsAString& aNodeType,
const nsAString* aAttribute,
nsIAtom* aNodeType,
nsIAtom* aAttribute,
const nsAString* aValue)
{
MOZ_ASSERT(aNode);
MOZ_ASSERT(aNode && aNodeType);
nsCOMPtr<nsIContent> parent = aNode->GetParent();
NS_ENSURE_STATE(parent);
NS_ENSURE_TRUE(parent, nullptr);
int32_t offset = parent->IndexOf(aNode);
// create new container
nsCOMPtr<Element> newContent =
CreateHTMLContent(nsCOMPtr<nsIAtom>(do_GetAtom(aNodeType)));
NS_ENSURE_STATE(newContent);
// Create new container
nsCOMPtr<Element> newContent = CreateHTMLContent(aNodeType);
NS_ENSURE_TRUE(newContent, nullptr);
// set attribute if needed
// Set attribute if needed
nsresult res;
if (aAttribute && aValue && !aAttribute->IsEmpty()) {
nsIDOMNode* elem = newContent->AsDOMNode();
res = static_cast<nsIDOMElement*>(elem)->SetAttribute(*aAttribute, *aValue);
NS_ENSURE_SUCCESS(res, res);
if (aAttribute && aValue && aAttribute != nsGkAtoms::_empty) {
res = newContent->SetAttr(kNameSpaceID_None, aAttribute, *aValue, true);
NS_ENSURE_SUCCESS(res, nullptr);
}
// notify our internal selection state listener
// Notify our internal selection state listener
nsAutoInsertContainerSelNotify selNotify(mRangeUpdater);
// put inNode in new parent, outNode
res = DeleteNode(aNode->AsDOMNode());
NS_ENSURE_SUCCESS(res, res);
// Put inNode in new parent, outNode
res = DeleteNode(aNode);
NS_ENSURE_SUCCESS(res, nullptr);
{
nsAutoTxnsConserveSelection conserveSelection(this);
res = InsertNode(aNode->AsDOMNode(), newContent->AsDOMNode(), 0);
NS_ENSURE_SUCCESS(res, res);
res = InsertNode(*aNode, *newContent, 0);
NS_ENSURE_SUCCESS(res, nullptr);
}
// put new parent in doc
res = InsertNode(newContent->AsDOMNode(), parent->AsDOMNode(), offset);
newContent.forget(aOutNode);
return res;
// Put new parent in doc
res = InsertNode(*newContent, *parent, offset);
NS_ENSURE_SUCCESS(res, nullptr);
return newContent.forget();
}
///////////////////////////////////////////////////////////////////////////
// MoveNode: move aNode to {aParent,aOffset}
nsresult
nsEditor::MoveNode(nsIDOMNode* aNode, nsIDOMNode* aParent, int32_t aOffset)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
NS_ENSURE_STATE(node);
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
NS_ENSURE_STATE(parent);
return MoveNode(node, parent, aOffset);
}
nsresult
nsEditor::MoveNode(nsINode* aNode, nsINode* aParent, int32_t aOffset)
nsEditor::MoveNode(nsIContent* aNode, nsINode* aParent, int32_t aOffset)
{
MOZ_ASSERT(aNode);
MOZ_ASSERT(aParent);
MOZ_ASSERT(aOffset == -1 ||
(0 <= aOffset && SafeCast<uint32_t>(aOffset) <= aParent->Length()));
int32_t oldOffset;
nsCOMPtr<nsINode> oldParent = GetNodeLocation(aNode, &oldOffset);
nsCOMPtr<nsINode> oldParent = aNode->GetParentNode();
int32_t oldOffset = oldParent ? oldParent->IndexOf(aNode) : -1;
if (aOffset == -1) {
// Magic value meaning "move to end of aParent".
// Magic value meaning "move to end of aParent"
aOffset = SafeCast<int32_t>(aParent->Length());
}
// Don't do anything if it's already in right place.
// Don't do anything if it's already in right place
if (aParent == oldParent && aOffset == oldOffset) {
return NS_OK;
}
// Notify our internal selection state listener.
// Notify our internal selection state listener
nsAutoMoveNodeSelNotify selNotify(mRangeUpdater, oldParent, oldOffset,
aParent, aOffset);
// Need to adjust aOffset if we are moving aNode further along in its current
// parent.
// Need to adjust aOffset if we're moving aNode later in its current parent
if (aParent == oldParent && oldOffset < aOffset) {
// This is because when we delete aNode, it will make the offsets after it
// off by one.
// When we delete aNode, it will make the offsets after it off by one
aOffset--;
}
// Hold a reference so aNode doesn't go away when we remove it (bug 772282).
// Hold a reference so aNode doesn't go away when we remove it (bug 772282)
nsCOMPtr<nsINode> kungFuDeathGrip = aNode;
nsresult rv = DeleteNode(aNode);
NS_ENSURE_SUCCESS(rv, rv);
return InsertNode(aNode->AsDOMNode(), aParent->AsDOMNode(), aOffset);
return InsertNode(*aNode, *aParent, aOffset);
}
@ -2592,46 +2567,47 @@ NS_IMETHODIMP nsEditor::CreateTxnForInsertText(const nsAString & aStringToInsert
}
NS_IMETHODIMP nsEditor::DeleteText(nsIDOMCharacterData *aElement,
uint32_t aOffset,
uint32_t aLength)
nsresult
nsEditor::DeleteText(nsGenericDOMDataNode& aCharData, uint32_t aOffset,
uint32_t aLength)
{
nsRefPtr<DeleteTextTxn> txn;
nsresult result = CreateTxnForDeleteText(aElement, aOffset, aLength,
getter_AddRefs(txn));
nsRefPtr<DeleteTextTxn> txn =
CreateTxnForDeleteText(aCharData, aOffset, aLength);
NS_ENSURE_STATE(txn);
nsAutoRules beginRulesSniffing(this, EditAction::deleteText, nsIEditor::ePrevious);
if (NS_SUCCEEDED(result))
{
// let listeners know what's up
int32_t i;
for (i = 0; i < mActionListeners.Count(); i++)
mActionListeners[i]->WillDeleteText(aElement, aOffset, aLength);
result = DoTransaction(txn);
// let listeners know what happened
for (i = 0; i < mActionListeners.Count(); i++)
mActionListeners[i]->DidDeleteText(aElement, aOffset, aLength, result);
// Let listeners know what's up
for (int32_t i = 0; i < mActionListeners.Count(); i++) {
mActionListeners[i]->WillDeleteText(
static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
aLength);
}
return result;
nsresult res = DoTransaction(txn);
// Let listeners know what happened
for (int32_t i = 0; i < mActionListeners.Count(); i++) {
mActionListeners[i]->DidDeleteText(
static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
aLength, res);
}
return res;
}
nsresult
nsEditor::CreateTxnForDeleteText(nsIDOMCharacterData* aElement,
uint32_t aOffset,
uint32_t aLength,
DeleteTextTxn** aTxn)
already_AddRefed<DeleteTextTxn>
nsEditor::CreateTxnForDeleteText(nsGenericDOMDataNode& aCharData,
uint32_t aOffset, uint32_t aLength)
{
NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
nsRefPtr<DeleteTextTxn> txn =
new DeleteTextTxn(*this, aCharData, aOffset, aLength, &mRangeUpdater);
nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn();
nsresult res = txn->Init();
NS_ENSURE_SUCCESS(res, nullptr);
nsresult res = txn->Init(this, aElement, aOffset, aLength, &mRangeUpdater);
NS_ENSURE_SUCCESS(res, res);
txn.forget(aTxn);
return NS_OK;
return txn.forget();
}
@ -4146,6 +4122,8 @@ NS_IMETHODIMP
nsEditor::DeleteSelectionAndCreateNode(const nsAString& aTag,
nsIDOMNode ** aNewNode)
{
nsCOMPtr<nsIAtom> tag = do_GetAtom(aTag);
nsresult result = DeleteSelectionAndPrepareToCreateNode();
NS_ENSURE_SUCCESS(result, result);
@ -4156,12 +4134,9 @@ nsEditor::DeleteSelectionAndCreateNode(const nsAString& aTag,
uint32_t offset = selection->AnchorOffset();
nsCOMPtr<nsIDOMNode> newNode;
result = CreateNode(aTag, node->AsDOMNode(), offset,
getter_AddRefs(newNode));
*aNewNode = GetAsDOMNode(CreateNode(tag, node, offset).take());
// XXX: ERROR_HANDLING check result, and make sure aNewNode is set correctly
// in success/failure cases
*aNewNode = newNode;
NS_IF_ADDREF(*aNewNode);
// we want the selection to be just after the new node
return selection->Collapse(node, offset + 1);
@ -4317,41 +4292,26 @@ nsEditor::CreateTxnForRemoveAttribute(nsIDOMElement *aElement,
}
NS_IMETHODIMP nsEditor::CreateTxnForCreateElement(const nsAString& aTag,
nsIDOMNode *aParent,
int32_t aPosition,
CreateElementTxn ** aTxn)
already_AddRefed<CreateElementTxn>
nsEditor::CreateTxnForCreateElement(nsIAtom& aTag,
nsINode& aParent,
int32_t aPosition)
{
NS_ENSURE_TRUE(aParent, NS_ERROR_NULL_POINTER);
nsRefPtr<CreateElementTxn> txn =
new CreateElementTxn(*this, aTag, aParent, aPosition);
nsRefPtr<CreateElementTxn> txn = new CreateElementTxn();
nsresult rv = txn->Init(this, aTag, aParent, aPosition);
if (NS_SUCCEEDED(rv))
{
txn.forget(aTxn);
}
return rv;
return txn.forget();
}
NS_IMETHODIMP nsEditor::CreateTxnForInsertElement(nsIDOMNode * aNode,
nsIDOMNode * aParent,
int32_t aPosition,
InsertElementTxn ** aTxn)
already_AddRefed<InsertNodeTxn>
nsEditor::CreateTxnForInsertNode(nsIContent& aNode,
nsINode& aParent,
int32_t aPosition)
{
NS_ENSURE_TRUE(aNode && aParent, NS_ERROR_NULL_POINTER);
nsRefPtr<InsertElementTxn> txn = new InsertElementTxn();
nsresult rv = txn->Init(aNode, aParent, aPosition, this);
if (NS_SUCCEEDED(rv))
{
txn.forget(aTxn);
}
return rv;
nsRefPtr<InsertNodeTxn> txn = new InsertNodeTxn(aNode, aParent, aPosition,
*this);
return txn.forget();
}
nsresult
@ -4466,25 +4426,23 @@ nsEditor::CreateTxnForDeleteSelection(EDirection aAction,
return NS_OK;
}
nsresult
nsEditor::CreateTxnForDeleteCharacter(nsIDOMCharacterData* aData,
uint32_t aOffset,
EDirection aDirection,
DeleteTextTxn** aTxn)
already_AddRefed<DeleteTextTxn>
nsEditor::CreateTxnForDeleteCharacter(nsGenericDOMDataNode& aData,
uint32_t aOffset, EDirection aDirection)
{
NS_ASSERTION(aDirection == eNext || aDirection == ePrevious,
"invalid direction");
"Invalid direction");
nsAutoString data;
aData->GetData(data);
aData.GetData(data);
NS_ASSERTION(data.Length(), "Trying to delete from a zero-length node");
NS_ENSURE_STATE(data.Length());
NS_ENSURE_TRUE(data.Length(), nullptr);
uint32_t segOffset = aOffset, segLength = 1;
if (aDirection == eNext) {
if (segOffset + 1 < data.Length() &&
NS_IS_HIGH_SURROGATE(data[segOffset]) &&
NS_IS_LOW_SURROGATE(data[segOffset+1])) {
// delete both halves of the surrogate pair
// Delete both halves of the surrogate pair
++segLength;
}
} else if (aOffset > 0) {
@ -4496,9 +4454,9 @@ nsEditor::CreateTxnForDeleteCharacter(nsIDOMCharacterData* aData,
--segOffset;
}
} else {
return NS_ERROR_FAILURE;
return nullptr;
}
return CreateTxnForDeleteText(aData, segOffset, segLength, aTxn);
return CreateTxnForDeleteText(aData, segOffset, segLength);
}
//XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior
@ -4523,7 +4481,6 @@ nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange,
// determine if the insertion point is at the beginning, middle, or end of
// the node
nsCOMPtr<nsIDOMCharacterData> nodeAsCharData = do_QueryInterface(node);
uint32_t count = node->Length();
@ -4543,16 +4500,15 @@ nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange,
// there is a priorNode, so delete its last child (if chardata, delete the
// last char). if it has no children, delete it
nsCOMPtr<nsIDOMCharacterData> priorNodeAsCharData =
do_QueryInterface(priorNode);
if (priorNodeAsCharData) {
if (priorNode->IsNodeOfType(nsINode::eDATA_NODE)) {
nsRefPtr<nsGenericDOMDataNode> priorNodeAsCharData =
static_cast<nsGenericDOMDataNode*>(priorNode.get());
uint32_t length = priorNode->Length();
// Bail out for empty chardata XXX: Do we want to do something else?
NS_ENSURE_STATE(length);
nsRefPtr<DeleteTextTxn> txn;
res = CreateTxnForDeleteCharacter(priorNodeAsCharData, length,
ePrevious, getter_AddRefs(txn));
NS_ENSURE_SUCCESS(res, res);
nsRefPtr<DeleteTextTxn> txn =
CreateTxnForDeleteCharacter(*priorNodeAsCharData, length, ePrevious);
NS_ENSURE_STATE(txn);
*aOffset = txn->GetOffset();
*aLength = txn->GetNumCharsToDelete();
@ -4579,16 +4535,15 @@ nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange,
// there is a nextNode, so delete its first child (if chardata, delete the
// first char). if it has no children, delete it
nsCOMPtr<nsIDOMCharacterData> nextNodeAsCharData =
do_QueryInterface(nextNode);
if (nextNodeAsCharData) {
if (nextNode->IsNodeOfType(nsINode::eDATA_NODE)) {
nsRefPtr<nsGenericDOMDataNode> nextNodeAsCharData =
static_cast<nsGenericDOMDataNode*>(nextNode.get());
uint32_t length = nextNode->Length();
// Bail out for empty chardata XXX: Do we want to do something else?
NS_ENSURE_STATE(length);
nsRefPtr<DeleteTextTxn> txn;
res = CreateTxnForDeleteCharacter(nextNodeAsCharData, 0, eNext,
getter_AddRefs(txn));
NS_ENSURE_SUCCESS(res, res);
nsRefPtr<DeleteTextTxn> txn =
CreateTxnForDeleteCharacter(*nextNodeAsCharData, 0, eNext);
NS_ENSURE_STATE(txn);
*aOffset = txn->GetOffset();
*aLength = txn->GetNumCharsToDelete();
@ -4606,12 +4561,13 @@ nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange,
return NS_OK;
}
if (nodeAsCharData) {
if (node->IsNodeOfType(nsINode::eDATA_NODE)) {
nsRefPtr<nsGenericDOMDataNode> nodeAsCharData =
static_cast<nsGenericDOMDataNode*>(node.get());
// we have chardata, so delete a char at the proper offset
nsRefPtr<DeleteTextTxn> txn;
res = CreateTxnForDeleteCharacter(nodeAsCharData, offset, aAction,
getter_AddRefs(txn));
NS_ENSURE_SUCCESS(res, res);
nsRefPtr<DeleteTextTxn> txn = CreateTxnForDeleteCharacter(*nodeAsCharData,
offset, aAction);
NS_ENSURE_STATE(txn);
aTxn->AppendChild(txn);
NS_ADDREF(*aNode = node);
@ -4639,18 +4595,17 @@ nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange,
}
NS_ENSURE_STATE(selectedNode);
nsCOMPtr<nsIDOMCharacterData> selectedNodeAsCharData =
do_QueryInterface(selectedNode);
if (selectedNodeAsCharData) {
if (selectedNode->IsNodeOfType(nsINode::eDATA_NODE)) {
nsRefPtr<nsGenericDOMDataNode> selectedNodeAsCharData =
static_cast<nsGenericDOMDataNode*>(selectedNode.get());
// we are deleting from a chardata node, so do a character deletion
uint32_t position = 0;
if (aAction == ePrevious) {
position = selectedNode->Length();
}
nsRefPtr<DeleteTextTxn> delTextTxn;
res = CreateTxnForDeleteCharacter(selectedNodeAsCharData, position,
aAction, getter_AddRefs(delTextTxn));
NS_ENSURE_SUCCESS(res, res);
nsRefPtr<DeleteTextTxn> delTextTxn =
CreateTxnForDeleteCharacter(*selectedNodeAsCharData, position,
aAction);
NS_ENSURE_TRUE(delTextTxn, NS_ERROR_NULL_POINTER);
aTxn->AppendChild(delTextTxn);

View File

@ -29,12 +29,9 @@
class AddStyleSheetTxn;
class ChangeAttributeTxn;
class CreateElementTxn;
class DeleteNodeTxn;
class DeleteTextTxn;
class EditAggregateTxn;
class IMETextTxn;
class InsertElementTxn;
class InsertTextTxn;
class JoinElementTxn;
class RemoveStyleSheetTxn;
@ -72,9 +69,12 @@ class ErrorResult;
class TextComposition;
namespace dom {
class CreateElementTxn;
class DataTransfer;
class DeleteTextTxn;
class Element;
class EventTarget;
class InsertNodeTxn;
class Selection;
class Text;
} // namespace dom
@ -200,7 +200,6 @@ public:
public:
nsresult MarkNodeDirty(nsINode* aNode);
virtual bool IsModifiableNode(nsINode *aNode);
NS_IMETHOD InsertTextImpl(const nsAString& aStringToInsert,
@ -222,8 +221,7 @@ public:
/* helper routines for node/parent manipulations */
nsresult DeleteNode(nsINode* aNode);
nsresult InsertNode(nsIContent* aContent, nsINode* aParent,
int32_t aPosition);
nsresult InsertNode(nsIContent& aNode, nsINode& aParent, int32_t aPosition);
enum ECloneAttributes { eDontCloneAttributes, eCloneAttributes };
already_AddRefed<mozilla::dom::Element> ReplaceContainer(
mozilla::dom::Element* aOldContainer,
@ -234,21 +232,14 @@ public:
void CloneAttributes(mozilla::dom::Element* aDest,
mozilla::dom::Element* aSource);
nsresult RemoveContainer(nsINode* aNode);
nsresult RemoveContainer(nsIDOMNode *inNode);
nsresult InsertContainerAbove(nsIContent* aNode,
mozilla::dom::Element** aOutNode,
const nsAString& aNodeType,
const nsAString* aAttribute = nullptr,
nsresult RemoveContainer(nsIContent* aNode);
already_AddRefed<mozilla::dom::Element> InsertContainerAbove(
nsIContent* aNode,
nsIAtom* aNodeType,
nsIAtom* aAttribute = nullptr,
const nsAString* aValue = nullptr);
nsresult InsertContainerAbove(nsIDOMNode *inNode,
nsCOMPtr<nsIDOMNode> *outNode,
const nsAString &aNodeType,
const nsAString *aAttribute = nullptr,
const nsAString *aValue = nullptr);
nsresult JoinNodes(nsINode* aNodeToKeep, nsIContent* aNodeToMove);
nsresult MoveNode(nsINode* aNode, nsINode* aParent, int32_t aOffset);
nsresult MoveNode(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aOffset);
nsresult MoveNode(nsIContent* aNode, nsINode* aParent, int32_t aOffset);
/* Method to replace certain CreateElementNS() calls.
Arguments:
@ -282,17 +273,19 @@ protected:
/** create a transaction for creating a new child node of aParent of type aTag.
*/
NS_IMETHOD CreateTxnForCreateElement(const nsAString & aTag,
nsIDOMNode *aParent,
int32_t aPosition,
CreateElementTxn ** aTxn);
already_AddRefed<mozilla::dom::CreateElementTxn>
CreateTxnForCreateElement(nsIAtom& aTag,
nsINode& aParent,
int32_t aPosition);
already_AddRefed<mozilla::dom::Element> CreateNode(nsIAtom* aTag,
nsINode* aParent,
int32_t aPosition);
/** create a transaction for inserting aNode as a child of aParent.
*/
NS_IMETHOD CreateTxnForInsertElement(nsIDOMNode * aNode,
nsIDOMNode * aParent,
int32_t aOffset,
InsertElementTxn ** aTxn);
already_AddRefed<mozilla::dom::InsertNodeTxn>
CreateTxnForInsertNode(nsIContent& aNode, nsINode& aParent, int32_t aOffset);
/** create a transaction for removing aNode from its parent.
*/
@ -334,28 +327,18 @@ protected:
NS_IMETHOD CreateTxnForRemoveStyleSheet(mozilla::CSSStyleSheet* aSheet,
RemoveStyleSheetTxn* *aTxn);
NS_IMETHOD DeleteText(nsIDOMCharacterData *aElement,
uint32_t aOffset,
uint32_t aLength);
inline nsresult DeleteText(mozilla::dom::Text* aText, uint32_t aOffset,
uint32_t aLength)
{
return DeleteText(static_cast<nsIDOMCharacterData*>(GetAsDOMNode(aText)),
aOffset, aLength);
}
nsresult DeleteText(nsGenericDOMDataNode& aElement,
uint32_t aOffset, uint32_t aLength);
// NS_IMETHOD DeleteRange(nsIDOMRange *aRange);
nsresult CreateTxnForDeleteText(nsIDOMCharacterData* aElement,
uint32_t aOffset,
uint32_t aLength,
DeleteTextTxn** aTxn);
already_AddRefed<mozilla::dom::DeleteTextTxn>
CreateTxnForDeleteText(nsGenericDOMDataNode& aElement,
uint32_t aOffset, uint32_t aLength);
nsresult CreateTxnForDeleteCharacter(nsIDOMCharacterData* aData,
uint32_t aOffset,
EDirection aDirection,
DeleteTextTxn** aTxn);
already_AddRefed<mozilla::dom::DeleteTextTxn>
CreateTxnForDeleteCharacter(nsGenericDOMDataNode& aData, uint32_t aOffset,
EDirection aDirection);
NS_IMETHOD CreateTxnForSplitNode(nsIDOMNode *aNode,
uint32_t aOffset,

View File

@ -569,7 +569,7 @@ nsHTMLEditor::AbsolutelyPositionElement(nsIDOMElement * aElement,
NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
nsresult res = htmlRules->MakeSureElemStartsOrEndsOnCR(aElement);
NS_ENSURE_SUCCESS(res, res);
res = RemoveContainer(aElement);
res = RemoveContainer(element);
NS_ENSURE_SUCCESS(res, res);
}
}

View File

@ -613,7 +613,7 @@ nsHTMLCSSUtils::RemoveCSSInlineStyle(nsIDOMNode *aNode, nsIAtom *aProperty, cons
return NS_OK;
}
return mHTMLEditor->RemoveContainer(aNode);
return mHTMLEditor->RemoveContainer(element);
}
// Answers true is the property can be removed by setting a "none" CSS value

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More