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

View File

@ -9,6 +9,21 @@ var FullScreen = {
delete this._fullScrToggler; delete this._fullScrToggler;
return this._fullScrToggler = document.getElementById("fullscr-toggler"); 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) { toggle: function (event) {
var enterFS = window.fullScreen; var enterFS = window.fullScreen;
@ -95,9 +110,12 @@ var FullScreen = {
switch (event.type) { switch (event.type) {
case "activate": case "activate":
if (document.mozFullScreen) { if (document.mozFullScreen) {
this.showWarning(this.fullscreenDoc); this.showWarning(this.fullscreenOrigin);
} }
break; break;
case "fullscreen":
this.toggle(event);
break;
case "transitionend": case "transitionend":
if (event.propertyName == "opacity") if (event.propertyName == "opacity")
this.cancelWarning(); 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) if (!document.mozFullScreen)
return; return;
// However, if we receive a "MozEnteredDomFullScreen" event for a document // If we've received a fullscreen notification, we have to ensure that the
// which is not a subdocument of a currently active (ie. visible) browser // element that's requesting fullscreen belongs to the browser that's currently
// or iframe, we know that we've switched to a different frame since the // active. If not, we exit fullscreen since the "full-screen document" isn't
// request to enter full-screen was made, so we should exit full-screen // actually visible now.
// since the "full-screen document" isn't acutally visible. if (gBrowser.selectedBrowser != aBrowser) {
if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell).isActive) {
document.mozCancelFullScreen(); document.mozCancelFullScreen();
return; return;
} }
@ -136,7 +169,7 @@ var FullScreen = {
if (gFindBarInitialized) if (gFindBarInitialized)
gFindBar.close(); gFindBar.close();
this.showWarning(event.target); this.showWarning(aOrigin);
// Exit DOM full-screen mode upon open, close, or change tab. // Exit DOM full-screen mode upon open, close, or change tab.
gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen); gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
@ -178,7 +211,9 @@ var FullScreen = {
gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen); gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
if (!this.useLionFullScreen) if (!this.useLionFullScreen)
window.removeEventListener("activate", this); 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). // the permission manager can't handle (documents with URIs without a host).
// We simply require those to be approved every time instead. // We simply require those to be approved every time instead.
let rememberCheckbox = document.getElementById("full-screen-remember-decision"); 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.hidden) {
if (rememberCheckbox.checked) if (rememberCheckbox.checked)
Services.perms.add(uri, Services.perms.add(uri,
@ -370,27 +405,29 @@ var FullScreen = {
// If the document has been granted fullscreen, notify Gecko so it can resume // If the document has been granted fullscreen, notify Gecko so it can resume
// any pending pointer lock requests, otherwise exit fullscreen; the user denied // any pending pointer lock requests, otherwise exit fullscreen; the user denied
// the fullscreen request. // the fullscreen request.
if (isApproved) if (isApproved) {
Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", ""); gBrowser.selectedBrowser
else .messageManager
.sendAsyncMessage("DOMFullscreen:Approved");
} else {
document.mozCancelFullScreen(); document.mozCancelFullScreen();
}
}, },
warningBox: null, warningBox: null,
warningFadeOutTimeout: null, warningFadeOutTimeout: null,
fullscreenDoc: null,
// Shows the fullscreen approval UI, or if the domain has already been approved // 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 // for fullscreen, shows a warning that the site has entered fullscreen for a short
// duration. // duration.
showWarning: function(targetDoc) { showWarning: function(aOrigin) {
if (!document.mozFullScreen || if (!document.mozFullScreen ||
!gPrefService.getBoolPref("full-screen-api.approval-required")) !gPrefService.getBoolPref("full-screen-api.approval-required"))
return; return;
// Set the strings on the fullscreen approval UI. // Set the strings on the fullscreen approval UI.
this.fullscreenDoc = targetDoc; this.fullscreenOrigin = aOrigin;
let uri = this.fullscreenDoc.nodePrincipal.URI; let uri = BrowserUtils.makeURI(aOrigin);
let host = null; let host = null;
try { try {
host = uri.host; host = uri.host;

View File

@ -19,10 +19,6 @@ var FullZoom = {
// browser.zoom.updateBackgroundTabs preference cache // browser.zoom.updateBackgroundTabs preference cache
updateBackgroundTabs: undefined, 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 // This maps the browser to monotonically increasing integer
// tokens. _browserTokenMap[browser] is increased each time the zoom is // tokens. _browserTokenMap[browser] is increased each time the zoom is
// changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses. // changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
@ -49,8 +45,7 @@ var FullZoom = {
// Initialization & Destruction // Initialization & Destruction
init: function FullZoom_init() { init: function FullZoom_init() {
// Listen for scrollwheel events so we can save scrollwheel-based changes. gBrowser.addEventListener("ZoomChangeUsingMouseWheel", this);
window.addEventListener("DOMMouseScroll", this, false);
// Register ourselves with the service so we know when our pref changes. // Register ourselves with the service so we know when our pref changes.
this._cps2 = Cc["@mozilla.org/content-pref/service;1"]. this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
@ -81,7 +76,7 @@ var FullZoom = {
destroy: function FullZoom_destroy() { destroy: function FullZoom_destroy() {
gPrefService.removeObserver("browser.zoom.", this); gPrefService.removeObserver("browser.zoom.", this);
this._cps2.removeObserverForName(this.name, 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) { handleEvent: function FullZoom_handleEvent(event) {
switch (event.type) { switch (event.type) {
case "DOMMouseScroll": case "ZoomChangeUsingMouseWheel":
this._handleMouseScrolled(event); let browser = this._getTargetedBrowser(event);
this._ignorePendingZoomAccesses(browser);
this._applyZoomToPref(browser);
break; 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 // nsIObserver
observe: function (aSubject, aTopic, aData) { 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 * 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 * 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"); "resource://gre/modules/ShortcutUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager", XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
"resource://gre/modules/GMPInstallManager.jsm"); "resource://gre/modules/GMPInstallManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch", XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
"resource:///modules/ContentSearch.jsm"); "resource:///modules/ContentSearch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AboutHome", XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
@ -1294,17 +1296,7 @@ var gBrowserInit = {
if (Win7Features) if (Win7Features)
Win7Features.onOpenWindow(); Win7Features.onOpenWindow();
// called when we go into full screen, even if initiated by a web page script FullScreen.init();
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();
#ifdef MOZ_SERVICES_SYNC #ifdef MOZ_SERVICES_SYNC
// initialize the sync UI // initialize the sync UI
@ -1435,7 +1427,7 @@ var gBrowserInit = {
gHistorySwipeAnimation.uninit(); gHistorySwipeAnimation.uninit();
FullScreen.cleanup(); FullScreen.uninit();
#ifdef MOZ_SERVICES_SYNC #ifdef MOZ_SERVICES_SYNC
gFxAccounts.uninit(); gFxAccounts.uninit();
@ -2762,14 +2754,6 @@ function SwitchToMetro() {
#endif #endif
} }
function onFullScreen(event) {
FullScreen.toggle(event);
}
function onMozEnteredDomFullscreen(event) {
FullScreen.enterDomFullscreen(event);
}
function getWebNavigation() function getWebNavigation()
{ {
return gBrowser.webNavigation; return gBrowser.webNavigation;
@ -3109,7 +3093,7 @@ const BrowserSearch = {
let mm = gBrowser.selectedBrowser.messageManager; let mm = gBrowser.selectedBrowser.messageManager;
if (url === "about:home") { if (url === "about:home") {
AboutHome.focusInput(mm); AboutHome.focusInput(mm);
} else if (url === "about:newtab") { } else if (url === "about:newtab" && NewTabUtils.allPages.enabled) {
ContentSearch.focusInput(mm); ContentSearch.focusInput(mm);
} else { } else {
openUILinkIn("about:home", "current"); openUILinkIn("about:home", "current");

View File

@ -578,3 +578,40 @@ if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
Cu.import("resource:///modules/translation/TranslationContentHandler.jsm"); Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
trHandler = new TranslationContentHandler(global, docShell); 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 // If we're using remote tabs, we have to wait until after we've finalized
// switching the tabs. // 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 fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
let focusFlags = fm.FLAG_NOSCROLL; 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) 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] [browser_bug970746.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (directly gets elements from the content) 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] [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}] 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] [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 }); EventUtils.synthesizeKey("k", { accelKey: true });
is(searchBar.textbox.inputField, gWindow.document.activeElement, "Toolbar's search bar should be focused"); 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. // Done. Revert the current engine and remove the new engines.
Services.search.currentEngine = oldCurrentEngine; Services.search.currentEngine = oldCurrentEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
@ -414,3 +432,46 @@ function logoImg() {
function gSearch() { function gSearch() {
return getContentWindow().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) .getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://browser/content/sanitize.js", tmp); .loadSubScript("chrome://browser/content/sanitize.js", tmp);
Cu.import("resource://gre/modules/Timer.jsm", 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 uri = Services.io.newURI("about:newtab", null, null);
let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri); 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('');"). // result in a new frontmost window (e.g. "javascript:window.open('');").
w.focus(); w.focus();
let newTab;
switch (where) { switch (where) {
case "current": case "current":
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
@ -331,23 +333,30 @@ function openLinkIn(url, where, params) {
loadInBackground = !loadInBackground; loadInBackground = !loadInBackground;
// fall through // fall through
case "tab": case "tab":
let browser = w.gBrowser; newTab = w.gBrowser.loadOneTab(url, {
browser.loadOneTab(url, { referrerURI: aReferrerURI,
referrerURI: aReferrerURI, charset: aCharset,
charset: aCharset, postData: aPostData,
postData: aPostData, inBackground: loadInBackground,
inBackground: loadInBackground, allowThirdPartyFixup: aAllowThirdPartyFixup,
allowThirdPartyFixup: aAllowThirdPartyFixup, relatedToCurrent: aRelatedToCurrent,
relatedToCurrent: aRelatedToCurrent, skipAnimation: aSkipTabAnimation,
skipAnimation: aSkipTabAnimation, allowMixedContent: aAllowMixedContent
allowMixedContent: aAllowMixedContent }); });
break; break;
} }
w.gBrowser.selectedBrowser.focus(); 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(); w.focusAndSelectUrlBar();
}
} }
// Used as an onclick handler for UI elements with link-like behavior. // Used as an onclick handler for UI elements with link-like behavior.

View File

@ -1320,7 +1320,7 @@ BrowserGlue.prototype = {
_migrateUI: function BG__migrateUI() { _migrateUI: function BG__migrateUI() {
const UI_VERSION = 23; const UI_VERSION = 23;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul#"; const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion = 0; let currentUIVersion = 0;
try { try {
currentUIVersion = Services.prefs.getIntPref("browser.migration.version"); currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
@ -1328,28 +1328,22 @@ BrowserGlue.prototype = {
if (currentUIVersion >= UI_VERSION) if (currentUIVersion >= UI_VERSION)
return; return;
this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService); let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
this._dataSource = this._rdf.GetDataSource("rdf:local-store");
this._dirty = false;
if (currentUIVersion < 2) { if (currentUIVersion < 2) {
// This code adds the customizable bookmarks button. // This code adds the customizable bookmarks button.
let currentsetResource = this._rdf.GetResource("currentset"); let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
// Need to migrate only if toolbar is customized and the element is not found. // Need to migrate only if toolbar is customized and the element is not found.
if (currentset && if (currentset &&
currentset.indexOf("bookmarks-menu-button-container") == -1) { currentset.indexOf("bookmarks-menu-button-container") == -1) {
currentset += ",bookmarks-menu-button-container"; currentset += ",bookmarks-menu-button-container";
this._setPersist(toolbarResource, currentsetResource, currentset); xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
} }
} }
if (currentUIVersion < 3) { if (currentUIVersion < 3) {
// This code merges the reload/stop/go button into the url bar. // This code merges the reload/stop/go button into the url bar.
let currentsetResource = this._rdf.GetResource("currentset"); let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
// Need to migrate only if toolbar is customized and all 3 elements are found. // Need to migrate only if toolbar is customized and all 3 elements are found.
if (currentset && if (currentset &&
currentset.indexOf("reload-button") != -1 && currentset.indexOf("reload-button") != -1 &&
@ -1360,15 +1354,13 @@ BrowserGlue.prototype = {
.replace(/(^|,)stop-button($|,)/, "$1$2") .replace(/(^|,)stop-button($|,)/, "$1$2")
.replace(/(^|,)urlbar-container($|,)/, .replace(/(^|,)urlbar-container($|,)/,
"$1urlbar-container,reload-button,stop-button$2"); "$1urlbar-container,reload-button,stop-button$2");
this._setPersist(toolbarResource, currentsetResource, currentset); xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
} }
} }
if (currentUIVersion < 4) { if (currentUIVersion < 4) {
// This code moves the home button to the immediate left of the bookmarks menu button. // This code moves the home button to the immediate left of the bookmarks menu button.
let currentsetResource = this._rdf.GetResource("currentset"); let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
// Need to migrate only if toolbar is customized and the elements are found. // Need to migrate only if toolbar is customized and the elements are found.
if (currentset && if (currentset &&
currentset.indexOf("home-button") != -1 && currentset.indexOf("home-button") != -1 &&
@ -1376,24 +1368,21 @@ BrowserGlue.prototype = {
currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2") currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2")
.replace(/(^|,)bookmarks-menu-button-container($|,)/, .replace(/(^|,)bookmarks-menu-button-container($|,)/,
"$1home-button,bookmarks-menu-button-container$2"); "$1home-button,bookmarks-menu-button-container$2");
this._setPersist(toolbarResource, currentsetResource, currentset); xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
} }
} }
if (currentUIVersion < 5) { if (currentUIVersion < 5) {
// This code uncollapses PersonalToolbar if its collapsed status is not // This code uncollapses PersonalToolbar if its collapsed status is not
// persisted, and user customized it or changed default bookmarks. // 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 // If the user does not have a persisted value for the toolbar's
// "collapsed" attribute, try to determine whether it's customized. // "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 // We consider the toolbar customized if it has more than
// 3 children, or if it has a persisted currentset value. // 3 children, or if it has a persisted currentset value.
let currentsetResource = this._rdf.GetResource("currentset"); let toolbarIsCustomized = xulStore.hasValue(BROWSER_DOCURL,
let toolbarIsCustomized = !!this._getPersist(toolbarResource, "PersonalToolbar", "currentset");
currentsetResource);
let getToolbarFolderCount = function () { let getToolbarFolderCount = function () {
let toolbarFolder = let toolbarFolder =
PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root; PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
@ -1403,7 +1392,7 @@ BrowserGlue.prototype = {
}; };
if (toolbarIsCustomized || getToolbarFolderCount() > 3) { 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) { if (currentUIVersion < 9) {
// This code adds the customizable downloads buttons. // This code adds the customizable downloads buttons.
let currentsetResource = this._rdf.GetResource("currentset"); let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
// Since the Downloads button is located in the navigation bar by default, // Since the Downloads button is located in the navigation bar by default,
// migration needs to happen only if the toolbar was customized using a // migration needs to happen only if the toolbar was customized using a
@ -1442,7 +1429,7 @@ BrowserGlue.prototype = {
currentset = currentset.replace(/(^|,)window-controls($|,)/, currentset = currentset.replace(/(^|,)window-controls($|,)/,
"$1downloads-button,window-controls$2") "$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) { if (currentUIVersion < 12) {
// Remove bookmarks-menu-button-container, then place // Remove bookmarks-menu-button-container, then place
// bookmarks-menu-button into its position. // bookmarks-menu-button into its position.
let currentsetResource = this._rdf.GetResource("currentset"); let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
// Need to migrate only if toolbar is customized. // Need to migrate only if toolbar is customized.
if (currentset) { if (currentset) {
if (currentset.contains("bookmarks-menu-button-container")) { if (currentset.contains("bookmarks-menu-button-container")) {
currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/, currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
"$1bookmarks-menu-button$2"); "$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) { if (currentUIVersion < 16) {
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); let isCollapsed = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "collapsed");
let collapsedResource = this._rdf.GetResource("collapsed");
let isCollapsed = this._getPersist(toolbarResource, collapsedResource);
if (isCollapsed == "true") { 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 // Insert the bookmarks-menu-button into the nav-bar if it isn't already
// there. // there.
if (currentUIVersion < 17) { if (currentUIVersion < 17) {
let currentsetResource = this._rdf.GetResource("currentset"); let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
// Need to migrate only if toolbar is customized. // Need to migrate only if toolbar is customized.
if (currentset) { if (currentset) {
if (!currentset.contains("bookmarks-menu-button")) { if (!currentset.contains("bookmarks-menu-button")) {
@ -1531,7 +1512,7 @@ BrowserGlue.prototype = {
currentset = currentset.replace(/(^|,)window-controls($|,)/, currentset = currentset.replace(/(^|,)window-controls($|,)/,
"$1bookmarks-menu-button,window-controls$2") "$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", let toolbars = ["navigator-toolbox", "nav-bar", "PersonalToolbar",
"addon-bar", "TabsToolbar", "toolbar-menubar"]; "addon-bar", "TabsToolbar", "toolbar-menubar"];
for (let resourceName of ["mode", "iconsize"]) { for (let resourceName of ["mode", "iconsize"]) {
let resource = this._rdf.GetResource(resourceName);
for (let toolbarId of toolbars) { for (let toolbarId of toolbars) {
let toolbar = this._rdf.GetResource(BROWSER_DOCURL + toolbarId); xulStore.removeValue(BROWSER_DOCURL, toolbarId, resourceName);
if (this._getPersist(toolbar, resource)) {
this._setPersist(toolbar, resource);
}
} }
} }
} }
@ -1569,21 +1546,13 @@ BrowserGlue.prototype = {
if (currentUIVersion < 20) { if (currentUIVersion < 20) {
// Remove persisted collapsed state from TabsToolbar. // Remove persisted collapsed state from TabsToolbar.
let resource = this._rdf.GetResource("collapsed"); xulStore.removeValue(BROWSER_DOCURL, "TabsToolbar", "collapsed");
let toolbar = this._rdf.GetResource(BROWSER_DOCURL + "TabsToolbar");
if (this._getPersist(toolbar, resource)) {
this._setPersist(toolbar, resource);
}
} }
if (currentUIVersion < 21) { if (currentUIVersion < 21) {
// Make sure the 'toolbarbutton-1' class will always be present from here // Make sure the 'toolbarbutton-1' class will always be present from here
// on out. // on out.
let button = this._rdf.GetResource(BROWSER_DOCURL + "bookmarks-menu-button"); xulStore.removeValue(BROWSER_DOCURL, "bookmarks-menu-button", "class");
let classResource = this._rdf.GetResource("class");
if (this._getPersist(button, classResource)) {
this._setPersist(button, classResource);
}
} }
if (currentUIVersion < 22) { 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. // Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_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 // public nsIBrowserGlue members
// ------------------------------ // ------------------------------

View File

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

View File

@ -23,6 +23,14 @@ function PlacesTreeView(aFlatList, aOnOpenFlatContainer, aController) {
PlacesTreeView.prototype = { PlacesTreeView.prototype = {
get wrappedJSObject() this, 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, __dateService: null,
get _dateService() { get _dateService() {
if (!this.__dateService) { if (!this.__dateService) {
@ -307,11 +315,15 @@ PlacesTreeView.prototype = {
if (!this._flatList && if (!this._flatList &&
curChild instanceof Ci.nsINavHistoryContainerResultNode && curChild instanceof Ci.nsINavHistoryContainerResultNode &&
!this._controller.hasCachedLivemarkInfo(curChild)) { !this._controller.hasCachedLivemarkInfo(curChild)) {
let resource = this._getResourceForNode(curChild); let uri = curChild.uri;
let isopen = resource != null && let isopen = false;
PlacesUIUtils.localStore.HasAssertion(resource,
openLiteral, if (uri) {
trueLiteral, true); let docURI = this._getDocumentURI();
let val = this._xulStore.getValue(docURI, uri, "open");
isopen = (val == "true");
}
if (isopen != curChild.containerOpen) if (isopen != curChild.containerOpen)
aToOpen.push(curChild); aToOpen.push(curChild);
else if (curChild.containerOpen && curChild.childCount > 0) else if (curChild.containerOpen && curChild.childCount > 0)
@ -1109,11 +1121,16 @@ PlacesTreeView.prototype = {
return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE; return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
}, },
_getResourceForNode: function PTV_getResourceForNode(aNode) // Retrieves an nsIURI for the document
_documentURI: null,
_getDocumentURI: function()
{ {
let uri = aNode.uri; if (!this._documentURI) {
NS_ASSERT(uri, "if there is no uri, we can't persist the open state"); let ioService = Cc["@mozilla.org/network/io-service;1"].
return uri ? PlacesUIUtils.RDF.GetResource(uri) : null; getService(Ci.nsIIOService);
this._documentURI = ioService.newURI(document.URL, null, null);
}
return this._documentURI;
}, },
// nsITreeView // nsITreeView
@ -1497,15 +1514,16 @@ PlacesTreeView.prototype = {
// Persist containers open status, but never persist livemarks. // Persist containers open status, but never persist livemarks.
if (!this._controller.hasCachedLivemarkInfo(node)) { if (!this._controller.hasCachedLivemarkInfo(node)) {
let resource = this._getResourceForNode(node); let uri = node.uri;
if (resource) {
const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
if (node.containerOpen) if (uri) {
PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral); let docURI = this._getDocumentURI();
else
PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true); 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. * Tests PersonalToolbar migration path.
*/ */
let bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver); let bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
let gOriginalMigrationVersion; let gOriginalMigrationVersion;
const BROWSER_URL = getBrowserURL(); const BROWSER_URL = getBrowserURL();
let localStore = { let localStore = {
get RDF() Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService), get xulStore() Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore),
get store() this.RDF.GetDataSource("rdf:local-store"),
get toolbar() getValue: function getValue(aProperty)
{ {
delete this.toolbar; return this.xulStore.getValue(BROWSER_URL, "PersonalToolbar", aProperty);
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;
}, },
getPersist: function getPersist(aProperty) setValue: function setValue(aProperty, aValue)
{ {
let property = this.RDF.GetResource(aProperty); if (aValue) {
let target = this.store.GetTarget(this.toolbar, property, true); this.xulStore.setValue(BROWSER_URL, "PersonalToolbar", aProperty, aValue);
if (target instanceof Ci.nsIRDFLiteral) } else {
return target.Value; this.xulStore.removeValue(BROWSER_URL, "PersonalToolbar", aProperty);
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;
}
} }
catch(ex) {
return;
}
this.store.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
} }
}; };
@ -69,17 +32,17 @@ let gTests = [
function test_explicitly_collapsed_toolbar() function test_explicitly_collapsed_toolbar()
{ {
info("An explicitly collapsed toolbar should not be uncollapsed."); 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"); 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() function test_customized_toolbar()
{ {
info("A customized toolbar should be uncollapsed."); info("A customized toolbar should be uncollapsed.");
localStore.setPersist("currentset", "splitter"); localStore.setValue("currentset", "splitter");
bg.observe(null, "browser-glue-test", "force-ui-migration"); 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() function test_many_bookmarks_toolbar()
@ -98,8 +61,12 @@ function test_many_bookmarks_toolbar()
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId, PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX) PlacesUtils.bookmarks.DEFAULT_INDEX)
); );
ids.push(
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX)
);
bg.observe(null, "browser-glue-test", "force-ui-migration"); 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"); gOriginalMigrationVersion = Services.prefs.getIntPref("browser.migration.version");
registerCleanupFunction(clean); registerCleanupFunction(clean);
if (localStore.getPersist("currentset") !== null) { if (localStore.getValue("currentset") !== null) {
info("Toolbar currentset was persisted by a previous test, fixing it."); 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."); info("Toolbar collapsed status was persisted by a previous test, fixing it.");
localStore.setPersist("collapsed", null); localStore.setValue("collapsed", null);
} }
while (gTests.length) { while (gTests.length) {
@ -129,7 +96,7 @@ function test()
function clean() function clean()
{ {
Services.prefs.setIntPref("browser.migration.version", gOriginalMigrationVersion); Services.prefs.setIntPref("browser.migration.version", gOriginalMigrationVersion);
localStore.setPersist("currentset", null); localStore.setValue("currentset", null);
localStore.setPersist("collapsed", null); localStore.setValue("collapsed", null);
} }

View File

@ -7,24 +7,75 @@ this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/Chr
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
"resource://gre/modules/FormHistory.jsm"); "resource://gre/modules/FormHistory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
function test() { function expectedURL(aSearchTerms) {
waitForExplicitFinish();
const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html"; 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"]; function simulateClick(aEvent, aTarget) {
var searchBar = BrowserSearch.searchBar; var event = document.createEvent("MouseEvent");
var searchButton = document.getAnonymousElementByAttribute(searchBar, var ctrlKeyArg = aEvent.ctrlKey || false;
"anonid", "search-go-button"); var altKeyArg = aEvent.altKey || false;
ok(searchButton, "got search-go-button"); 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; var ss = Services.search;
let testIterator;
function observer(aSub, aTopic, aData) { function observer(aSub, aTopic, aData) {
switch (aData) { switch (aData) {
case "engine-added": case "engine-added":
@ -34,266 +85,221 @@ function test() {
break; break;
case "engine-current": case "engine-current":
ok(ss.currentEngine.name == "Bug 426329", "currentEngine set"); ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
testReturn(); searchBar = BrowserSearch.searchBar;
break; searchButton = document.getAnonymousElementByAttribute(searchBar,
case "engine-removed": "anonid", "search-go-button");
ok(searchButton, "got search-go-button");
searchBar.value = "test";
Services.obs.removeObserver(observer, "browser-search-engine-modified"); Services.obs.removeObserver(observer, "browser-search-engine-modified");
finish(); deferred.resolve();
break; break;
} }
} };
Services.obs.addObserver(observer, "browser-search-engine-modified", false); Services.obs.addObserver(observer, "browser-search-engine-modified", false);
ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml", ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00", Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00",
false); false);
var preSelectedBrowser, preTabNo; return deferred.promise;
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;
}
} }
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. /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */ * http://creativecommons.org/publicdomain/zero/1.0/ */
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
function whenNewWindowLoaded(aOptions, aCallback) { function whenNewWindowLoaded(aOptions, aCallback) {
let win = OpenBrowserWindow(aOptions); let win = OpenBrowserWindow(aOptions);
let gotLoad = false; let gotLoad = false;
@ -88,6 +91,18 @@ function waitForPopupShown(aPopupId, aCallback) {
registerCleanupFunction(removePopupShownListener); 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) { function waitForBrowserContextMenu(aCallback) {
waitForPopupShown(gBrowser.selectedBrowser.contextMenu, aCallback); waitForPopupShown(gBrowser.selectedBrowser.contextMenu, aCallback);
} }
@ -106,3 +121,16 @@ function doOnloadOnce(aCallback) {
gBrowser.addEventListener("load", doOnloadOnceListener, true); gBrowser.addEventListener("load", doOnloadOnceListener, true);
registerCleanupFunction(removeDoOnloadOnceListener); 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 FORBIDDEN_IDS = new Set(["toolbox", ""]);
const MAX_ORDINAL = 99; const MAX_ORDINAL = 99;
/** /**
* DevTools is a class that represents a set of developer tools, it holds a * 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. * set of tools and keeps track of open toolboxes in the browser.
*/ */
this.DevTools = function DevTools() { this.DevTools = function DevTools() {
this._tools = new Map(); // Map<toolId, tool> this._tools = new Map(); // Map<toolId, tool>
this._themes = new Map(); // Map<themeId, theme>
this._toolboxes = new Map(); // Map<target, toolbox> this._toolboxes = new Map(); // Map<target, toolbox>
// destroy() is an observer's handler so we need to preserve context. // destroy() is an observer's handler so we need to preserve context.
@ -229,6 +229,136 @@ DevTools.prototype = {
return definitions.sort(this.ordinalSort); 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 * 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) * 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_js_iframe.html
browser_toolbox_options_disable_cache.sjs browser_toolbox_options_disable_cache.sjs
head.js head.js
doc_theme.css
[browser_devtools_api.js] [browser_devtools_api.js]
[browser_dynamic_tool_enabling.js] [browser_dynamic_tool_enabling.js]
@ -32,6 +33,7 @@ skip-if = e10s # Bug 1030318
[browser_toolbox_window_title_changes.js] [browser_toolbox_window_title_changes.js]
[browser_toolbox_zoom.js] [browser_toolbox_zoom.js]
[browser_toolbox_custom_host.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: # We want this test to run for mochitest-dt as well, so we include it here:
[../../../base/content/test/general/browser_parsable_css.js] [../../../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.isReady = false;
this._prefChanged = this._prefChanged.bind(this); this._prefChanged = this._prefChanged.bind(this);
this._themeRegistered = this._themeRegistered.bind(this);
this._themeUnregistered = this._themeUnregistered.bind(this);
this._addListeners(); this._addListeners();
@ -101,7 +103,9 @@ OptionsPanel.prototype = {
return targetPromise.then(() => { return targetPromise.then(() => {
this.setupToolsList(); this.setupToolsList();
this.setupToolbarButtonsList(); this.setupToolbarButtonsList();
this.setupThemeList();
this.populatePreferences(); this.populatePreferences();
this.updateDefaultTheme();
this._disableJSClicked = this._disableJSClicked.bind(this); this._disableJSClicked = this._disableJSClicked.bind(this);
@ -119,10 +123,14 @@ OptionsPanel.prototype = {
_addListeners: function() { _addListeners: function() {
gDevTools.on("pref-changed", this._prefChanged); gDevTools.on("pref-changed", this._prefChanged);
gDevTools.on("theme-registered", this._themeRegistered);
gDevTools.on("theme-unregistered", this._themeUnregistered);
}, },
_removeListeners: function() { _removeListeners: function() {
gDevTools.off("pref-changed", this._prefChanged); gDevTools.off("pref-changed", this._prefChanged);
gDevTools.off("theme-registered", this._themeRegistered);
gDevTools.off("theme-unregistered", this._themeUnregistered);
}, },
_prefChanged: function(event, data) { _prefChanged: function(event, data) {
@ -132,6 +140,22 @@ OptionsPanel.prototype = {
cbx.checked = cacheDisabled; 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() { setupToolbarButtonsList: function() {
@ -229,6 +253,26 @@ OptionsPanel.prototype = {
this.panelWin.focus(); 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() { populatePreferences: function() {
let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]"); let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
for (let checkbox of prefCheckboxes) { for (let checkbox of prefCheckboxes) {
@ -258,9 +302,13 @@ OptionsPanel.prototype = {
pref: this.getAttribute("data-pref"), pref: this.getAttribute("data-pref"),
newValue: this.selectedItem.getAttribute("value") newValue: this.selectedItem.getAttribute("value")
}; };
data.oldValue = GetPref(data.pref); data.oldValue = GetPref(data.pref);
SetPref(data.pref, data.newValue); SetPref(data.pref, data.newValue);
gDevTools.emit("pref-changed", data);
if (data.newValue != data.oldValue) {
gDevTools.emit("pref-changed", data);
}
}.bind(radiogroup)); }.bind(radiogroup));
} }
let prefMenulists = this.panelDoc.querySelectorAll("menulist[data-pref]"); 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() { _populateDisableJSCheckbox: function() {
let cbx = this.panelDoc.getElementById("devtools-disable-javascript"); let cbx = this.panelDoc.getElementById("devtools-disable-javascript");
cbx.checked = !this._origJavascriptEnabled; cbx.checked = !this._origJavascriptEnabled;

View File

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

View File

@ -357,6 +357,31 @@ for (let definition of defaultTools) {
gDevTools.registerTool(definition); 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 = { var unloadObserver = {
observe: function(subject, topic, data) { observe: function(subject, topic, data) {
if (subject.wrappedJSObject === require("@loader/unload")) { if (subject.wrappedJSObject === require("@loader/unload")) {
@ -364,6 +389,9 @@ var unloadObserver = {
for (let definition of gDevTools.getToolDefinitionArray()) { for (let definition of gDevTools.getToolDefinitionArray()) {
gDevTools.unregisterTool(definition.id); gDevTools.unregisterTool(definition.id);
} }
for (let definition of gDevTools.getThemeDefinitionArray()) {
gDevTools.unregisterTheme(definition.id);
}
} }
} }
}; };

View File

@ -4,6 +4,8 @@
"use strict"; "use strict";
let { utils: Cu, interfaces: Ci } = Components;
addMessageListener("devtools:test:history", function ({ data }) { addMessageListener("devtools:test:history", function ({ data }) {
content.history[data.direction](); content.history[data.direction]();
}); });
@ -16,3 +18,11 @@ addMessageListener("devtools:test:reload", function ({ data }) {
data = data || {}; data = data || {};
content.location.reload(data.forceget); 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; return;
} }
if (oldTheme && newTheme != oldTheme) { let oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
StylesheetUtils.removeSheet( let newThemeDef = gDevTools.getThemeDefinition(newTheme);
window,
DEVTOOLS_SKIN_URL + oldTheme + "-theme.css", // Unload all theme stylesheets related to the old theme.
"author" if (oldThemeDef) {
); for (let url of oldThemeDef.stylesheets) {
StylesheetUtils.removeSheet(window, url, "author");
}
} }
StylesheetUtils.loadSheet( // Load all stylesheets associated with the new theme.
window, let newThemeDef = gDevTools.getThemeDefinition(newTheme);
DEVTOOLS_SKIN_URL + newTheme + "-theme.css",
"author"
);
// 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"] let hiddenDOMWindow = Cc["@mozilla.org/appshell/appShellService;1"]
.getService(Ci.nsIAppShellService) .getService(Ci.nsIAppShellService)
.hiddenDOMWindow; .hiddenDOMWindow;
// TODO: extensions might want to customize scrollbar styles too.
if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) { if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
let scrollbarsUrl = Services.io.newURI( let scrollbarsUrl = Services.io.newURI(
DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null); DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null);
@ -62,8 +73,26 @@
forceStyle(); forceStyle();
} }
documentElement.classList.remove("theme-" + oldTheme); if (oldThemeDef) {
documentElement.classList.add("theme-" + newTheme); 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) { function handlePrefChange(event, data) {

View File

@ -10,18 +10,23 @@ support-files =
doc_connect-toggle.html doc_connect-toggle.html
doc_connect-param.html doc_connect-param.html
doc_connect-multi-param.html doc_connect-multi-param.html
doc_change-param.html
440hz_sine.ogg 440hz_sine.ogg
head.js head.js
[browser_audionode-actor-get-set-param.js] [browser_audionode-actor-get-param-flags.js]
[browser_audionode-actor-get-type.js]
[browser_audionode-actor-get-params-01.js] [browser_audionode-actor-get-params-01.js]
[browser_audionode-actor-get-params-02.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_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-connect-param.js]
[browser_webaudio-actor-destroy-node.js]
[browser_webaudio-actor-simple.js]
[browser_wa_destroy-node-01.js] [browser_wa_destroy-node-01.js]
@ -48,4 +53,5 @@ support-files =
# [browser_wa_properties-view-edit-02.js] # [browser_wa_properties-view-edit-02.js]
# Disabled for too many intermittents bug 1010423 # Disabled for too many intermittents bug 1010423
[browser_wa_properties-view-params.js] [browser_wa_properties-view-params.js]
[browser_wa_properties-view-change-params.js]
[browser_wa_properties-view-params-objects.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 { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
let TargetFactory = devtools.TargetFactory; 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 EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html"; const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-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_TOGGLE_URL = EXAMPLE_URL + "doc_connect-toggle.html";
const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html"; const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html";
const CONNECT_MULTI_PARAM_URL = EXAMPLE_URL + "doc_connect-multi-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. // All tests are asynchronous.
waitForExplicitFinish(); waitForExplicitFinish();
@ -133,6 +136,8 @@ function initBackend(aUrl) {
yield target.makeRemote(); yield target.makeRemote();
let front = new WebAudioFront(target.client, target.form); let front = new WebAudioFront(target.client, target.form);
loadFrameScripts();
return [target, debuggee, front]; return [target, debuggee, front];
}); });
} }
@ -150,6 +155,8 @@ function initWebAudioEditor(aUrl) {
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true); Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor"); let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor");
let panel = toolbox.getCurrentPanel(); let panel = toolbox.getCurrentPanel();
loadFrameScripts();
return [target, debuggee, panel]; return [target, debuggee, panel];
}); });
} }
@ -387,9 +394,12 @@ function countGraphObjects (win) {
* Forces cycle collection and GC, used in AudioNode destruction tests. * Forces cycle collection and GC, used in AudioNode destruction tests.
*/ */
function forceCC () { function forceCC () {
SpecialPowers.DOMWindowUtils.cycleCollect(); mm.sendAsyncMessage("devtools:test:forceCC");
SpecialPowers.DOMWindowUtils.garbageCollect(); }
SpecialPowers.DOMWindowUtils.garbageCollect();
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 L10N = new ViewHelpers.L10N(STRINGS_URI);
const Telemetry = require("devtools/shared/telemetry"); const Telemetry = require("devtools/shared/telemetry");
const telemetry = new Telemetry(); const telemetry = new Telemetry();
let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {}); 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: // The panel's window global is an EventEmitter firing the following events:
const EVENTS = { const EVENTS = {
// Fired when the first AudioNode has been created, signifying // Fired when the first AudioNode has been created, signifying
@ -173,6 +174,8 @@ let WebAudioEditorController = {
telemetry.toolOpened("webaudioeditor"); telemetry.toolOpened("webaudioeditor");
this._onTabNavigated = this._onTabNavigated.bind(this); this._onTabNavigated = this._onTabNavigated.bind(this);
this._onThemeChange = this._onThemeChange.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("will-navigate", this._onTabNavigated);
gTarget.on("navigate", this._onTabNavigated); gTarget.on("navigate", this._onTabNavigated);
gFront.on("start-context", this._onStartContext); gFront.on("start-context", this._onStartContext);
@ -194,12 +197,15 @@ let WebAudioEditorController = {
window.on(EVENTS.DISCONNECT_NODE, this._onUpdatedContext); window.on(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
window.on(EVENTS.DESTROY_NODE, this._onUpdatedContext); window.on(EVENTS.DESTROY_NODE, this._onUpdatedContext);
window.on(EVENTS.CONNECT_PARAM, 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. * Remove events emitted by the current tab target.
*/ */
destroy: function() { destroy: Task.async(function* () {
telemetry.toolClosed("webaudioeditor"); telemetry.toolClosed("webaudioeditor");
gTarget.off("will-navigate", this._onTabNavigated); gTarget.off("will-navigate", this._onTabNavigated);
gTarget.off("navigate", this._onTabNavigated); gTarget.off("navigate", this._onTabNavigated);
@ -215,8 +221,11 @@ let WebAudioEditorController = {
window.off(EVENTS.DISCONNECT_NODE, this._onUpdatedContext); window.off(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
window.off(EVENTS.DESTROY_NODE, this._onUpdatedContext); window.off(EVENTS.DESTROY_NODE, this._onUpdatedContext);
window.off(EVENTS.CONNECT_PARAM, this._onUpdatedContext); window.off(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
window.off(EVENTS.UI_SELECT_NODE, this._onSelectNode);
gDevTools.off("pref-changed", this._onThemeChange); gDevTools.off("pref-changed", this._onThemeChange);
},
yield gFront.disableChangeParamEvents();
}),
/** /**
* Called when page is reloaded to show the reload notice and waiting * 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. * Called when a node param is changed.
*/ */
_onChangeParam: function({ actor, param, value }) { _onChangeParam: function (args) {
window.emit(EVENTS.CHANGE_PARAM, getViewNodeByActor(actor), param, value); 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._onNodeSelect = this._onNodeSelect.bind(this);
this._onTogglePaneClick = this._onTogglePaneClick.bind(this); this._onTogglePaneClick = this._onTogglePaneClick.bind(this);
this._onDestroyNode = this._onDestroyNode.bind(this); this._onDestroyNode = this._onDestroyNode.bind(this);
this._onChangeParam = this._onChangeParam.bind(this);
this._inspectorPaneToggleButton.addEventListener("mousedown", this._onTogglePaneClick, false); this._inspectorPaneToggleButton.addEventListener("mousedown", this._onTogglePaneClick, false);
this._propsView = new VariablesView($("#properties-tabpanel-content"), GENERIC_VARIABLES_VIEW_SETTINGS); 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.UI_SELECT_NODE, this._onNodeSelect);
window.on(EVENTS.DESTROY_NODE, this._onDestroyNode); 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); this._inspectorPaneToggleButton.removeEventListener("mousedown", this._onTogglePaneClick);
window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect); window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
window.off(EVENTS.DESTROY_NODE, this._onDestroyNode); window.off(EVENTS.DESTROY_NODE, this._onDestroyNode);
window.off(EVENTS.CHANGE_PARAM, this._onChangeParam);
this._inspectorPane = null; this._inspectorPane = null;
this._inspectorPaneToggleButton = null; this._inspectorPaneToggleButton = null;
@ -612,7 +615,22 @@ let WebAudioInspectorView = {
if (this._currentNode && this._currentNode.id === id) { if (this._currentNode && this._currentNode.id === id) {
this.setCurrentAudioNode(null); 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-console-api-stackframe.html
test_bug_1010953_cspro.html^headers^ test_bug_1010953_cspro.html^headers^
test_bug_1010953_cspro.html 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_bug664688_sandbox_update_after_navigation.js]
[browser_bug_638949_copy_link_location.js] [browser_bug_638949_copy_link_location.js]
[browser_bug_862916_console_dir_and_filter_off.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_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
<menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/> <menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
<menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_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/> <menuseparator/>
<menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/> <menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
<menuitem command="cmd_stop" accesskey="&projectMenu_stop_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 // crashes. For forensics purposes, keep the last few log
// messages in memory and upload them in case of crash. // messages in memory and upload them in case of crash.
this._forensicsLogs = []; this._forensicsLogs = [];
this._forensicsLogs.length = 3; this._forensicsLogs.length = 10;
this._log = Object.create(log); this._log = Object.create(log);
this._log.log = (level, string, params) => { this._log.log = (level, string, params) => {
this._forensicsLogs.shift(); this._forensicsLogs.shift();

View File

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

View File

@ -114,8 +114,6 @@
- the heading of the radiobox corresponding to the theme of the developer - the heading of the radiobox corresponding to the theme of the developer
- tools. --> - tools. -->
<!ENTITY options.selectDevToolsTheme.label "Choose DevTools theme:"> <!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 <!-- LOCALIZATION NOTE (options.webconsole.label): This is the label for the
- heading of the group of Web Console preferences in the options panel. --> - 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) # LOCALIZATION NOTE (pickButton.tooltip)
# This is the tooltip of the pick button in the toolbox toolbar # This is the tooltip of the pick button in the toolbox toolbar
pickButton.tooltip=Pick an element from the page 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_label "Open Hosted App…">
<!ENTITY projectMenu_importHostedApp_accesskey "H"> <!ENTITY projectMenu_importHostedApp_accesskey "H">
<!ENTITY projectMenu_selectApp_label "Open App…"> <!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_label "Install and Run">
<!ENTITY projectMenu_play_accesskey "I"> <!ENTITY projectMenu_play_accesskey "I">
<!ENTITY projectMenu_stop_label "Stop App"> <!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_stable=stable
addons_unstable=unstable 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_simulator_label=Firefox OS %1$S Simulator (%2$S)
addons_install_button=install addons_install_button=install
addons_uninstall_button=uninstall addons_uninstall_button=uninstall

View File

@ -77,6 +77,10 @@ menu[disabled="true"].subviewbutton > .menu-right {
-moz-image-region: rect(0, 32px, 16px, 16px); -moz-image-region: rect(0, 32px, 16px, 16px);
} }
menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
transform: scaleX(-1);
}
.subviewbutton > .toolbarbutton-icon { .subviewbutton > .toolbarbutton-icon {
-moz-margin-end: 5px !important; -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); -moz-image-region: rect(0, 32px, 16px, 16px);
} }
menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
transform: scaleX(-1);
}
%ifdef WINDOWS_AERO %ifdef WINDOWS_AERO
/* Win8 and beyond. */ /* Win8 and beyond. */
@media not all and (-moz-os-version: windows-vista) { @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:gsmsdp_add_default_video_formats_to_local_sdp
leak:CCAPI_CallInfo_getMediaStreams 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. ### Many leaks only affect some test suites. The suite annotations are not checked.

View File

@ -784,6 +784,17 @@ nsCSPParser::directiveName()
return nullptr; 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 // Make sure the directive does not already exist
// (see http://www.w3.org/TR/CSP11/#parsing) // (see http://www.w3.org/TR/CSP11/#parsing)
if (mPolicy->directiveExists(CSP_DirectiveToEnum(mCurToken))) { if (mPolicy->directiveExists(CSP_DirectiveToEnum(mCurToken))) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -33,9 +33,6 @@
#include "nsViewManager.h" #include "nsViewManager.h"
#include "nsIContentViewer.h" #include "nsIContentViewer.h"
#include "nsIDOMXULElement.h" #include "nsIDOMXULElement.h"
#include "nsIRDFNode.h"
#include "nsIRDFRemoteDataSource.h"
#include "nsIRDFService.h"
#include "nsIStreamListener.h" #include "nsIStreamListener.h"
#include "nsITimer.h" #include "nsITimer.h"
#include "nsDocShell.h" #include "nsDocShell.h"
@ -44,11 +41,10 @@
#include "nsXULContentSink.h" #include "nsXULContentSink.h"
#include "nsXULContentUtils.h" #include "nsXULContentUtils.h"
#include "nsIXULOverlayProvider.h" #include "nsIXULOverlayProvider.h"
#include "nsIStringEnumerator.h"
#include "nsNetUtil.h" #include "nsNetUtil.h"
#include "nsParserCIID.h" #include "nsParserCIID.h"
#include "nsPIBoxObject.h" #include "nsPIBoxObject.h"
#include "nsRDFCID.h"
#include "nsILocalStore.h"
#include "nsXPIDLString.h" #include "nsXPIDLString.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h" #include "nsPIWindowRoot.h"
@ -137,11 +133,6 @@ const uint32_t kMaxAttributeLength = 4096;
int32_t XULDocument::gRefCnt = 0; int32_t XULDocument::gRefCnt = 0;
nsIRDFService* XULDocument::gRDFService;
nsIRDFResource* XULDocument::kNC_persist;
nsIRDFResource* XULDocument::kNC_attribute;
nsIRDFResource* XULDocument::kNC_value;
PRLogModuleInfo* XULDocument::gXULLog; PRLogModuleInfo* XULDocument::gXULLog;
//---------------------------------------------------------------------- //----------------------------------------------------------------------
@ -230,26 +221,11 @@ XULDocument::~XULDocument()
PL_DHashTableDestroy(mBroadcasterMap); PL_DHashTableDestroy(mBroadcasterMap);
} }
if (mLocalStore) {
nsCOMPtr<nsIRDFRemoteDataSource> remote =
do_QueryInterface(mLocalStore);
if (remote)
remote->Flush();
}
delete mTemplateBuilderTable; delete mTemplateBuilderTable;
Preferences::UnregisterCallback(XULDocument::DirectionChanged, Preferences::UnregisterCallback(XULDocument::DirectionChanged,
"intl.uidirection.", this); "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) { if (mOffThreadCompileStringBuf) {
js_free(mOffThreadCompileStringBuf); js_free(mOffThreadCompileStringBuf);
} }
@ -349,6 +325,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
tmp->mTemplateBuilderTable = nullptr; tmp->mTemplateBuilderTable = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
//XXX We should probably unlink all the objects we traverse. //XXX We should probably unlink all the objects we traverse.
NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -1331,10 +1308,7 @@ XULDocument::Persist(const nsAString& aID,
nameSpaceID = kNameSpaceID_None; nameSpaceID = kNameSpaceID_None;
} }
rv = Persist(element, nameSpaceID, tag); return Persist(element, nameSpaceID, tag);
if (NS_FAILED(rv)) return rv;
return NS_OK;
} }
nsresult nsresult
@ -1345,102 +1319,39 @@ XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
return NS_ERROR_NOT_AVAILABLE; return NS_ERROR_NOT_AVAILABLE;
// First make sure we _have_ a local store to stuff the persisted if (!mLocalStore) {
// information into. (We might not have one if profile information mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
// hasn't been loaded yet...) if (NS_WARN_IF(!mLocalStore)) {
if (!mLocalStore) return NS_ERROR_NOT_INITIALIZED;
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;
} }
nsCOMPtr<nsIRDFResource> attr; nsAutoString id;
rv = gRDFService->GetResource(attrstr,
getter_AddRefs(attr)); aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
if (NS_FAILED(rv)) return rv; nsAtomString attrstr(aAttribute);
// Turn the value into a literal
nsAutoString valuestr; nsAutoString valuestr;
aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr); aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
// prevent over-long attributes that choke the parser (bug 319846) nsAutoCString utf8uri;
// (can't simply Truncate without testing, it's implemented nsresult rv = mDocumentURI->GetSpec(utf8uri);
// using SetLength and will grow a short string) if (NS_WARN_IF(NS_FAILED(rv))) {
if (valuestr.Length() > kMaxAttributeLength) { return rv;
NS_WARNING("Truncating persisted attribute value"); }
valuestr.Truncate(kMaxAttributeLength); 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... if (hasAttr && valuestr.IsEmpty()) {
nsCOMPtr<nsIRDFNode> oldvalue; return mLocalStore->RemoveValue(uri, id, attrstr);
rv = mLocalStore->GetTarget(element, attr, true, getter_AddRefs(oldvalue)); } else {
if (NS_FAILED(rv)) return rv; return mLocalStore->SetValue(uri, id, attrstr, valuestr);
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);
} }
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); mCommandDispatcher = new nsXULCommandDispatcher(this);
NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY); 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) { 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, // ensure that the XUL prototype cache is instantiated successfully,
// so that we can use nsXULPrototypeCache::GetInstance() without // so that we can use nsXULPrototypeCache::GetInstance() without
// null-checks in the rest of the class. // null-checks in the rest of the class.
@ -2175,8 +2068,12 @@ XULDocument::ApplyPersistentAttributes()
// Add all of the 'persisted' attributes into the content // Add all of the 'persisted' attributes into the content
// model. // model.
if (!mLocalStore) if (!mLocalStore) {
return NS_OK; mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if (NS_WARN_IF(!mLocalStore)) {
return NS_ERROR_NOT_INITIALIZED;
}
}
mApplyingPersistedAttrs = true; mApplyingPersistedAttrs = true;
ApplyPersistentAttributesInternal(); ApplyPersistentAttributesInternal();
@ -2191,56 +2088,49 @@ XULDocument::ApplyPersistentAttributes()
} }
nsresult nsresult
XULDocument::ApplyPersistentAttributesInternal() XULDocument::ApplyPersistentAttributesInternal()
{ {
nsCOMArray<nsIContent> elements; nsCOMArray<nsIContent> elements;
nsAutoCString docurl; nsAutoCString utf8uri;
mDocumentURI->GetSpec(docurl); nsresult rv = mDocumentURI->GetSpec(utf8uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
NS_ConvertUTF8toUTF16 uri(utf8uri);
nsCOMPtr<nsIRDFResource> doc; // Get a list of element IDs for which persisted values are available
gRDFService->GetResource(docurl, getter_AddRefs(doc)); nsCOMPtr<nsIStringEnumerator> ids;
rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
nsCOMPtr<nsISimpleEnumerator> persisted; if (NS_WARN_IF(NS_FAILED(rv))) {
mLocalStore->GetTargets(doc, kNC_persist, true, getter_AddRefs(persisted)); return rv;
}
while (1) { while (1) {
bool hasmore = false; bool hasmore = false;
persisted->HasMoreElements(&hasmore); ids->HasMore(&hasmore);
if (! hasmore) if (!hasmore) {
break; break;
}
nsCOMPtr<nsISupports> isupports; nsAutoString id;
persisted->GetNext(getter_AddRefs(isupports)); ids->GetNext(id);
nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports); if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
if (! resource) {
NS_WARNING("expected element to be a resource");
continue; 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. // This will clear the array if there are no elements.
GetElementsForID(id, elements); GetElementsForID(id, elements);
if (!elements.Count()) {
if (!elements.Count())
continue; continue;
}
ApplyPersistentAttributesToElements(resource, elements); rv = ApplyPersistentAttributesToElements(id, elements);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} }
return NS_OK; return NS_OK;
@ -2248,71 +2138,53 @@ XULDocument::ApplyPersistentAttributesInternal()
nsresult nsresult
XULDocument::ApplyPersistentAttributesToElements(nsIRDFResource* aResource, XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
nsCOMArray<nsIContent>& aElements) 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; // Get a list of attributes for which persisted values are available
rv = mLocalStore->ArcLabelsOut(aResource, getter_AddRefs(attrs)); nsCOMPtr<nsIStringEnumerator> attrs;
if (NS_FAILED(rv)) return rv; rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
while (1) { while (1) {
bool hasmore; bool hasmore = PR_FALSE;
rv = attrs->HasMoreElements(&hasmore); attrs->HasMore(&hasmore);
if (NS_FAILED(rv)) return rv; if (!hasmore) {
if (! hasmore)
break; 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; nsAutoString attrstr;
rv = property->GetValueConst(&attrname); attrs->GetNext(attrstr);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIAtom> attr = do_GetAtom(attrname); nsAutoString value;
if (! attr) 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; 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(); uint32_t cnt = aElements.Count();
for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) { for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i); nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
if (!element) if (!element) {
continue; continue;
}
rv = element->SetAttr(/* XXX */ kNameSpaceID_None, rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
attr,
wrapper,
true);
} }
} }

View File

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

View File

@ -115,31 +115,9 @@ public:
nsIRDFResource* aResource, nsIRDFResource* aResource,
nsIContent** aResult); nsIContent** aResult);
static nsresult
GetElementResource(nsIContent* aElement, nsIRDFResource** aResult);
static nsresult static nsresult
GetTextForNode(nsIRDFNode* aNode, nsAString& aResult); 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 static nsresult
GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult); GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult);

View File

@ -8,7 +8,6 @@
#include "nsIContent.h" #include "nsIContent.h"
#include "mozilla/dom/NodeInfo.h" #include "mozilla/dom/NodeInfo.h"
#include "nsIDOMElement.h" #include "nsIDOMElement.h"
#include "nsILocalStore.h"
#include "nsIBoxObject.h" #include "nsIBoxObject.h"
#include "nsITreeBoxObject.h" #include "nsITreeBoxObject.h"
#include "nsITreeSelection.h" #include "nsITreeSelection.h"
@ -31,6 +30,7 @@
#include "nsDOMClassInfoID.h" #include "nsDOMClassInfoID.h"
#include "nsWhitespaceTokenizer.h" #include "nsWhitespaceTokenizer.h"
#include "nsTreeContentView.h" #include "nsTreeContentView.h"
#include "nsIXULStore.h"
// For security check // For security check
#include "nsIDocument.h" #include "nsIDocument.h"
@ -139,13 +139,10 @@ protected:
RemoveMatchesFor(nsTreeRows::Subtree& subtree); 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 bool
IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen); IsContainerOpen(nsIXULTemplateResult* aResource);
nsresult
IsContainerOpen(nsIRDFResource* aResource, bool* aOpen);
/** /**
* A sorting callback for NS_QuickSort(). * A sorting callback for NS_QuickSort().
@ -242,6 +239,11 @@ protected:
* The builder observers. * The builder observers.
*/ */
nsCOMArray<nsIXULTreeBuilderObserver> mObservers; 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, mBoxObject,
mSelection, mSelection,
mPersistStateStore, mPersistStateStore,
mLocalStore,
mObservers) mObservers)
DOMCI_DATA(XULTreeBuilder, nsXULTreeBuilder) DOMCI_DATA(XULTreeBuilder, nsXULTreeBuilder)
@ -528,8 +531,7 @@ nsXULTreeBuilder::IsContainerOpen(int32_t aIndex, bool* aOpen)
nsTreeRows::iterator iter = mRows[aIndex]; nsTreeRows::iterator iter = mRows[aIndex];
if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) { if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) {
bool isOpen; bool isOpen = IsContainerOpen(iter->mMatch->mResult);
IsContainerOpen(iter->mMatch->mResult, &isOpen);
iter->mContainerState = isOpen iter->mContainerState = isOpen
? nsTreeRows::eContainerState_Open ? nsTreeRows::eContainerState_Open
@ -757,42 +759,16 @@ nsXULTreeBuilder::SetTree(nsITreeBoxObject* aTree)
} }
NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED); 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; bool isTrusted = false;
nsresult rv = IsSystemPrincipal(mRoot->NodePrincipal(), &isTrusted); nsresult rv = IsSystemPrincipal(mRoot->NodePrincipal(), &isTrusted);
if (NS_SUCCEEDED(rv) && isTrusted) { if (NS_SUCCEEDED(rv) && isTrusted) {
// Get the datasource we intend to use to remember open state. mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
nsAutoString datasourceStr; if(NS_WARN_IF(!mLocalStore)){
mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::statedatasource, datasourceStr); return NS_ERROR_NOT_INITIALIZED;
// 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));
} }
} }
// 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(); Rebuild();
EnsureSortVariables(); EnsureSortVariables();
@ -830,34 +806,36 @@ nsXULTreeBuilder::ToggleOpenState(int32_t aIndex)
observer->OnToggleOpenState(aIndex); observer->OnToggleOpenState(aIndex);
} }
if (mPersistStateStore) { if (mLocalStore && mRoot) {
bool isOpen; bool isOpen;
IsContainerOpen(aIndex, &isOpen); IsContainerOpen(aIndex, &isOpen);
nsCOMPtr<nsIRDFResource> container; nsIDocument* doc = mRoot->GetDocument();
GetResourceFor(aIndex, getter_AddRefs(container)); if (!doc) {
if (! container)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
}
bool hasProperty; nsIURI* docURI = doc->GetDocumentURI();
IsContainerOpen(container, &hasProperty); 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 (isOpen) {
if (hasProperty) { mLocalStore->RemoveValue(uri, nodeid, NS_LITERAL_STRING("open"));
mPersistStateStore->Unassert(container,
nsXULContentUtils::NC_open,
nsXULContentUtils::true_);
}
CloseContainer(aIndex); CloseContainer(aIndex);
} } else {
else { mLocalStore->SetValue(uri, nodeid, NS_LITERAL_STRING("open"),
if (! hasProperty) { NS_LITERAL_STRING("true"));
mPersistStateStore->Assert(container,
nsXULContentUtils::NC_open,
nsXULContentUtils::true_,
true);
}
OpenContainer(aIndex, result); OpenContainer(aIndex, result);
} }
@ -1225,10 +1203,9 @@ nsXULTreeBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
if (NS_FAILED(rv) || ! mayProcessChildren) return NS_OK; if (NS_FAILED(rv) || ! mayProcessChildren) return NS_OK;
} }
bool open; if (IsContainerOpen(result)) {
IsContainerOpen(result, &open);
if (open)
OpenContainer(iter.GetRowIndex(), 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 // If this is open, then remember it so we can recursively add
// *its* rows to the tree. // *its* rows to the tree.
bool isOpen = false; if (IsContainerOpen(nextresult)) {
IsContainerOpen(nextresult, &isOpen);
if (isOpen) {
if (open.AppendElement(count) == nullptr) if (open.AppendElement(count) == nullptr)
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
} }
@ -1722,36 +1697,42 @@ nsXULTreeBuilder::RemoveMatchesFor(nsTreeRows::Subtree& subtree)
return NS_OK; return NS_OK;
} }
nsresult
nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen) bool
nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult)
{ {
// items are never open if recursion is disabled // items are never open if recursion is disabled
if ((mFlags & eDontRecurse) && aResult != mRootResult) { if ((mFlags & eDontRecurse) && aResult != mRootResult) {
*aOpen = false; return false;
return NS_OK; }
}
nsCOMPtr<nsIRDFResource> id; if (!mLocalStore) {
nsresult rv = GetResultResource(aResult, getter_AddRefs(id)); return false;
if (NS_FAILED(rv)) }
return rv;
return IsContainerOpen(id, aOpen); nsIDocument* doc = mRoot->GetDocument();
} if (!doc) {
return false;
}
nsresult nsIURI* docURI = doc->GetDocumentURI();
nsXULTreeBuilder::IsContainerOpen(nsIRDFResource* aResource, bool* aOpen)
{
if (mPersistStateStore)
mPersistStateStore->HasAssertion(aResource,
nsXULContentUtils::NC_open,
nsXULContentUtils::true_,
true,
aOpen);
else
*aOpen = false;
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 int

View File

@ -10752,12 +10752,6 @@ class CGDescriptor(CGThing):
if props.isCrossOriginGetter: if props.isCrossOriginGetter:
crossOriginGetters.add(m.identifier.name) crossOriginGetters.add(m.identifier.name)
if not m.readonly: 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(): if m.isStatic():
assert descriptor.interface.hasInterfaceObject() assert descriptor.interface.hasInterfaceObject()
cgThings.append(CGStaticSetter(descriptor, m)) cgThings.append(CGStaticSetter(descriptor, m))

View File

@ -957,10 +957,15 @@ class IDLInterface(IDLObjectWithScope):
list(i.location for i in list(i.location for i in
self.interfacesBasedOnSelf if i.parent == self)) self.interfacesBasedOnSelf if i.parent == self))
for member in self.members: for member in self.members:
member.validate() 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 # Check that PutForwards refers to another attribute and that no
# cycles exist in forwarded assignments. # cycles exist in forwarded assignments.
if member.isAttr(): if member.isAttr():
@ -3236,6 +3241,15 @@ class IDLAttribute(IDLInterfaceMember):
raise WebIDLError("[PutForwards] takes an identifier", raise WebIDLError("[PutForwards] takes an identifier",
[attr.location, self.location]) [attr.location, self.location])
elif identifier == "Replaceable": 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: if self.getExtendedAttribute("PutForwards") is not None:
raise WebIDLError("[PutForwards] and [Replaceable] can't both " raise WebIDLError("[PutForwards] and [Replaceable] can't both "
"appear on the same attribute", "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) { function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
debug("Creating new BrowserElementParent object for " + frameLoader); debug("Creating new BrowserElementParent object for " + frameLoader);
this._domRequestCounter = 0; this._domRequestCounter = 0;
this._domRequestReady = false;
this._pendingAPICalls = [];
this._pendingDOMRequests = {}; this._pendingDOMRequests = {};
this._pendingSetInputMethodActive = []; this._pendingSetInputMethodActive = [];
this._hasRemoteFrame = hasRemoteFrame; this._hasRemoteFrame = hasRemoteFrame;
@ -94,7 +96,7 @@ function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
let defineNoReturnMethod = function(name, fn) { let defineNoReturnMethod = function(name, fn) {
XPCNativeWrapper.unwrap(self._frameElement)[name] = function method() { 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. // Remote browser haven't been created, we just queue the API call.
let args = Array.slice(arguments); let args = Array.slice(arguments);
args.unshift(self); args.unshift(self);
@ -181,7 +183,6 @@ function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
} else { } else {
// if we are a pending frame, we setup message manager after // if we are a pending frame, we setup message manager after
// observing remote-browser-frame-shown // observing remote-browser-frame-shown
this._pendingAPICalls = [];
Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true); Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
} }
} }
@ -370,6 +371,13 @@ BrowserElementParent.prototype = {
this._ownerVisibilityChange(); 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 { return {
name: this._frameElement.getAttribute('name'), name: this._frameElement.getAttribute('name'),
fullscreenAllowed: fullscreenAllowed:
@ -531,7 +539,7 @@ BrowserElementParent.prototype = {
Services.DOMRequest.fireErrorAsync(req, "fail"); Services.DOMRequest.fireErrorAsync(req, "fail");
} }
}; };
if (this._mm) { if (this._domRequestReady) {
send(); send();
} else { } else {
// Child haven't been loaded. // Child haven't been loaded.
@ -797,7 +805,7 @@ BrowserElementParent.prototype = {
if (self._nextPaintListeners.push(listener) == 1) if (self._nextPaintListeners.push(listener) == 1)
self._sendAsyncMsg('activate-next-paint-listener'); self._sendAsyncMsg('activate-next-paint-listener');
}; };
if (!this._mm) { if (!this._domRequestReady) {
this._pendingAPICalls.push(run); this._pendingAPICalls.push(run);
} else { } else {
run(); run();
@ -820,7 +828,7 @@ BrowserElementParent.prototype = {
if (self._nextPaintListeners.length == 0) if (self._nextPaintListeners.length == 0)
self._sendAsyncMsg('deactivate-next-paint-listener'); self._sendAsyncMsg('deactivate-next-paint-listener');
}; };
if (!this._mm) { if (!this._domRequestReady) {
this._pendingAPICalls.push(run); this._pendingAPICalls.push(run);
} else { } else {
run(); run();
@ -908,7 +916,6 @@ BrowserElementParent.prototype = {
if (!this._mm) { if (!this._mm) {
this._setupMessageListener(); this._setupMessageListener();
this._registerAppManifest(); this._registerAppManifest();
this._runPendingAPICall();
} }
Services.obs.removeObserver(this, 'remote-browser-frame-shown'); Services.obs.removeObserver(this, 'remote-browser-frame-shown');
} }

View File

@ -51,11 +51,16 @@ function runTest() {
// should have 'backgroundLRU' equals 1 // should have 'backgroundLRU' equals 1
var p = expectPriorityWithBackgroundLRUSet(childID, '1'); var p = expectPriorityWithBackgroundLRUSet(childID, '1');
iframe2.setVisible(false); iframe2.setVisible(false);
document.body.removeChild(iframe2);
return p; 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); document.body.appendChild(iframe1);
} }

View File

@ -2021,6 +2021,9 @@ EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
} else { } else {
ChangeTextSize(change); 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): # LOCALIZATION NOTE (hostNameMightBeKeyword):
# %1$S is the hostname in question and %2$S is the keyword # %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). 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: # CSP Errors:
policyURINotAlone = policy-uri directive can only appear alone 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@1.5x.png',
'res/text_caret_tilt_right@2.25x.png', 'res/text_caret_tilt_right@2.25x.png',
'res/text_caret_tilt_right@2x.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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <algorithm>
#include <stdio.h> #include <stdio.h>
#include "CreateElementTxn.h"
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/Casting.h"
#include "CreateElementTxn.h"
#include "nsAlgorithm.h" #include "nsAlgorithm.h"
#include "nsAString.h"
#include "nsDebug.h" #include "nsDebug.h"
#include "nsEditor.h" #include "nsEditor.h"
#include "nsError.h" #include "nsError.h"
@ -21,14 +27,19 @@
#include "nsReadableUtils.h" #include "nsReadableUtils.h"
#include "nsStringFwd.h" #include "nsStringFwd.h"
#include "nsString.h" #include "nsString.h"
#include "nsAString.h"
#include <algorithm>
using namespace mozilla; using namespace mozilla;
using namespace mozilla::dom; using namespace mozilla::dom;
CreateElementTxn::CreateElementTxn() CreateElementTxn::CreateElementTxn(nsEditor& aEditor,
nsIAtom& aTag,
nsINode& aParent,
int32_t aOffsetInParent)
: EditTxn() : 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_IMPL_RELEASE_INHERITED(CreateElementTxn, EditTxn)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTxn) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTxn)
NS_INTERFACE_MAP_END_INHERITING(EditTxn) NS_INTERFACE_MAP_END_INHERITING(EditTxn)
NS_IMETHODIMP CreateElementTxn::Init(nsEditor *aEditor,
const nsAString &aTag,
nsIDOMNode *aParent, NS_IMETHODIMP
uint32_t aOffsetInParent) CreateElementTxn::DoTransaction()
{ {
NS_ASSERTION(aEditor&&aParent, "null args"); MOZ_ASSERT(mEditor && mTag && mParent);
if (!aEditor || !aParent) { return NS_ERROR_NULL_POINTER; }
mEditor = aEditor; mNewNode = mEditor->CreateHTMLContent(mTag);
mTag = aTag; NS_ENSURE_STATE(mNewNode);
mParent = do_QueryInterface(aParent);
mOffsetInParent = aOffsetInParent;
return NS_OK;
}
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: // Try to insert formatting whitespace for the new node:
mEditor->MarkNodeDirty(mNewNode); mEditor->MarkNodeDirty(GetAsDOMNode(mNewNode));
// insert the new node // Insert the new node
if (CreateElementTxn::eAppend == int32_t(mOffsetInParent)) { ErrorResult rv;
nsCOMPtr<nsIDOMNode> resultNode; if (mOffsetInParent == -1) {
return mParent->AppendChild(mNewNode, getter_AddRefs(resultNode)); mParent->AppendChild(*mNewNode, rv);
return rv.ErrorCode();
} }
nsCOMPtr<nsINode> parent = do_QueryInterface(mParent); mOffsetInParent = std::min(mOffsetInParent,
NS_ENSURE_STATE(parent); 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 mParent->InsertBefore(*mNewNode, mRefNode, rv);
nsIContent* refNode = parent->GetChildAt(mOffsetInParent); NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());
mRefNode = refNode ? refNode->AsDOMNode() : nullptr;
nsCOMPtr<nsIDOMNode> resultNode; // Only set selection to insertion point if editor gives permission
nsresult result = mParent->InsertBefore(mNewNode, mRefNode, getter_AddRefs(resultNode)); if (!mEditor->GetShouldTxnSetSelection()) {
NS_ENSURE_SUCCESS(result, result); // Do nothing - DOM range gravity will adjust selection
// 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
return NS_OK; return NS_OK;
} }
nsCOMPtr<nsISelection> selection; nsRefPtr<Selection> selection = mEditor->GetSelection();
result = mEditor->GetSelection(getter_AddRefs(selection));
NS_ENSURE_SUCCESS(result, result);
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIContent> parentContent = do_QueryInterface(mParent); rv = selection->CollapseNative(mParent, mParent->IndexOf(mNewNode) + 1);
NS_ENSURE_STATE(parentContent); NS_ASSERTION(!rv.Failed(),
"selection could not be collapsed after insert");
result = selection->CollapseNative(parentContent, return NS_OK;
parentContent->IndexOf(newContent) + 1);
NS_ASSERTION((NS_SUCCEEDED(result)), "selection could not be collapsed after insert.");
return result;
} }
NS_IMETHODIMP CreateElementTxn::UndoTransaction(void) NS_IMETHODIMP
CreateElementTxn::UndoTransaction()
{ {
NS_ASSERTION(mEditor && mParent, "bad state"); MOZ_ASSERT(mEditor && mParent);
NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsIDOMNode> resultNode; ErrorResult rv;
return mParent->RemoveChild(mNewNode, getter_AddRefs(resultNode)); mParent->RemoveChild(*mNewNode, rv);
return rv.ErrorCode();
} }
NS_IMETHODIMP CreateElementTxn::RedoTransaction(void) NS_IMETHODIMP
CreateElementTxn::RedoTransaction()
{ {
NS_ASSERTION(mEditor && mParent, "bad state"); MOZ_ASSERT(mEditor && mParent);
NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
// first, reset mNewNode so it has no attributes or content // First, reset mNewNode so it has no attributes or content
nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(mNewNode); // XXX We never actually did this, we only cleared mNewNode's contents if it
if (nodeAsText) // was a CharacterData node (which it's not, it's an Element)
{
nodeAsText->SetData(EmptyString()); // Now, reinsert mNewNode
} ErrorResult rv;
mParent->InsertBefore(*mNewNode, mRefNode, rv);
// now, reinsert mNewNode return rv.ErrorCode();
nsCOMPtr<nsIDOMNode> resultNode;
return mParent->InsertBefore(mNewNode, mRefNode, getter_AddRefs(resultNode));
} }
NS_IMETHODIMP CreateElementTxn::GetTxnDescription(nsAString& aString) NS_IMETHODIMP
CreateElementTxn::GetTxnDescription(nsAString& aString)
{ {
aString.AssignLiteral("CreateElementTxn: "); aString.AssignLiteral("CreateElementTxn: ");
aString += mTag; aString += nsDependentAtomString(mTag);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP CreateElementTxn::GetNewNode(nsIDOMNode **aNewNode) already_AddRefed<Element>
CreateElementTxn::GetNewNode()
{ {
NS_ENSURE_TRUE(aNewNode, NS_ERROR_NULL_POINTER); return nsCOMPtr<Element>(mNewNode).forget();
NS_ENSURE_TRUE(mNewNode, NS_ERROR_NOT_INITIALIZED);
*aNewNode = mNewNode;
NS_ADDREF(*aNewNode);
return NS_OK;
} }

View File

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

View File

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

View File

@ -19,14 +19,19 @@
using namespace mozilla; using namespace mozilla;
using namespace mozilla::dom; using namespace mozilla::dom;
DeleteTextTxn::DeleteTextTxn() : DeleteTextTxn::DeleteTextTxn(nsEditor& aEditor,
EditTxn(), nsGenericDOMDataNode& aCharData, uint32_t aOffset,
mEditor(nullptr), uint32_t aNumCharsToDelete,
mCharData(), nsRangeUpdater* aRangeUpdater)
mOffset(0), : EditTxn()
mNumCharsToDelete(0), , mEditor(aEditor)
mRangeUpdater(nullptr) , 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, 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_BEGIN_CYCLE_COLLECTION(DeleteTextTxn)
NS_INTERFACE_MAP_END_INHERITING(EditTxn) NS_INTERFACE_MAP_END_INHERITING(EditTxn)
NS_IMETHODIMP nsresult
DeleteTextTxn::Init(nsEditor* aEditor, DeleteTextTxn::Init()
nsIDOMCharacterData* aCharData,
uint32_t aOffset,
uint32_t aNumCharsToDelete,
nsRangeUpdater* aRangeUpdater)
{ {
MOZ_ASSERT(aEditor && aCharData); // Do nothing if the node is read-only
if (!mEditor.IsModifiableNode(mCharData)) {
mEditor = aEditor;
mCharData = aCharData;
// do nothing if the node is read-only
if (!mEditor->IsModifiableNode(mCharData)) {
return NS_ERROR_FAILURE; 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; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
DeleteTextTxn::DoTransaction() 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, nsresult res = mCharData->SubstringData(mOffset, mNumCharsToDelete,
mDeletedText); mDeletedText);
MOZ_ASSERT(NS_SUCCEEDED(res)); MOZ_ASSERT(NS_SUCCEEDED(res));
@ -81,27 +67,25 @@ DeleteTextTxn::DoTransaction()
mRangeUpdater->SelAdjDeleteText(mCharData, mOffset, mNumCharsToDelete); mRangeUpdater->SelAdjDeleteText(mCharData, mOffset, mNumCharsToDelete);
} }
// only set selection to deletion point if editor gives permission // Only set selection to deletion point if editor gives permission
bool bAdjustSelection; if (mEditor.GetShouldTxnSetSelection()) {
mEditor->ShouldTxnSetSelection(&bAdjustSelection); nsRefPtr<Selection> selection = mEditor.GetSelection();
if (bAdjustSelection) {
nsRefPtr<Selection> selection = mEditor->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
res = selection->Collapse(mCharData, mOffset); res = selection->Collapse(mCharData, mOffset);
NS_ASSERTION(NS_SUCCEEDED(res), 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); 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; return NS_OK;
} }
//XXX: we may want to store the selection state and restore it properly //XXX: We may want to store the selection state and restore it properly. Was
// was it an insertion point or an extended selection? // it an insertion point or an extended selection?
NS_IMETHODIMP NS_IMETHODIMP
DeleteTextTxn::UndoTransaction() DeleteTextTxn::UndoTransaction()
{ {
MOZ_ASSERT(mEditor && mCharData); MOZ_ASSERT(mCharData);
return mCharData->InsertData(mOffset, mDeletedText); return mCharData->InsertData(mOffset, mDeletedText);
} }

View File

@ -9,14 +9,17 @@
#include "EditTxn.h" #include "EditTxn.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h" #include "nsCycleCollectionParticipant.h"
#include "nsGenericDOMDataNode.h"
#include "nsID.h" #include "nsID.h"
#include "nsIDOMCharacterData.h"
#include "nsString.h" #include "nsString.h"
#include "nscore.h" #include "nscore.h"
class nsEditor; class nsEditor;
class nsRangeUpdater; class nsRangeUpdater;
namespace mozilla {
namespace dom {
/** /**
* A transaction that removes text from a content node. * 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 aOffset the location in aElement to begin the deletion
* @param aNumCharsToDelete the number of characters to delete. Not the number of bytes! * @param aNumCharsToDelete the number of characters to delete. Not the number of bytes!
*/ */
NS_IMETHOD Init(nsEditor* aEditor, DeleteTextTxn(nsEditor& aEditor,
nsIDOMCharacterData* aCharData, nsGenericDOMDataNode& aCharData,
uint32_t aOffset, uint32_t aOffset,
uint32_t aNumCharsToDelete, uint32_t aNumCharsToDelete,
nsRangeUpdater* aRangeUpdater); nsRangeUpdater* aRangeUpdater);
DeleteTextTxn(); nsresult Init();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteTextTxn, EditTxn) NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteTextTxn, EditTxn)
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr); NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
@ -49,10 +52,10 @@ public:
protected: protected:
/** the provider of basic editing operations */ /** the provider of basic editing operations */
nsEditor* mEditor; nsEditor& mEditor;
/** the CharacterData node to operate upon */ /** the CharacterData node to operate upon */
nsCOMPtr<nsIDOMCharacterData> mCharData; nsRefPtr<nsGenericDOMDataNode> mCharData;
/** the offset into mCharData where the deletion is to take place */ /** the offset into mCharData where the deletion is to take place */
uint32_t mOffset; uint32_t mOffset;
@ -67,4 +70,7 @@ protected:
nsRangeUpdater* mRangeUpdater; nsRangeUpdater* mRangeUpdater;
}; };
}
}
#endif #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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef InsertElementTxn_h__ #ifndef InsertNodeTxn_h__
#define InsertElementTxn_h__ #define InsertNodeTxn_h__
#include "EditTxn.h" // for EditTxn, NS_DECL_EDITTXN #include "EditTxn.h" // for EditTxn, NS_DECL_EDITTXN
#include "nsCOMPtr.h" // for nsCOMPtr #include "nsCOMPtr.h" // for nsCOMPtr
#include "nsCycleCollectionParticipant.h" #include "nsCycleCollectionParticipant.h"
#include "nsIDOMNode.h" // for nsIDOMNode #include "nsIContent.h" // for nsIContent
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS_INHERITED #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 * A transaction that inserts a single element
*/ */
class InsertElementTxn : public EditTxn class InsertNodeTxn : public EditTxn
{ {
public: public:
/** initialize the transaction. /** initialize the transaction.
@ -26,32 +28,31 @@ public:
* @param aParent the node to insert into * @param aParent the node to insert into
* @param aOffset the offset in aParent to insert aNode * @param aOffset the offset in aParent to insert aNode
*/ */
NS_IMETHOD Init(nsIDOMNode *aNode, InsertNodeTxn(nsIContent& aNode, nsINode& aParent, int32_t aOffset,
nsIDOMNode *aParent, nsEditor& aEditor);
int32_t aOffset,
nsIEditor *aEditor);
InsertElementTxn();
NS_DECL_ISUPPORTS_INHERITED NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertElementTxn, EditTxn) NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertNodeTxn, EditTxn)
NS_DECL_EDITTXN NS_DECL_EDITTXN
protected: protected:
virtual ~InsertElementTxn(); virtual ~InsertNodeTxn();
/** the element to insert */ /** the element to insert */
nsCOMPtr<nsIDOMNode> mNode; nsCOMPtr<nsIContent> mNode;
/** the node into which the new node will be inserted */ /** the node into which the new node will be inserted */
nsCOMPtr<nsIDOMNode> mParent; nsCOMPtr<nsINode> mParent;
/** the editor for this transaction */
nsIEditor* mEditor;
/** the index in mParent for the new node */ /** the index in mParent for the new node */
int32_t mOffset; int32_t mOffset;
/** the editor for this transaction */
nsEditor& mEditor;
}; };
}
}
#endif #endif

View File

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

View File

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

View File

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

View File

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

View File

@ -613,7 +613,7 @@ nsHTMLCSSUtils::RemoveCSSInlineStyle(nsIDOMNode *aNode, nsIAtom *aProperty, cons
return NS_OK; 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 // 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