mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
e9982eeaf4
@ -56,7 +56,7 @@
|
||||
<popupnotification id="pointerLock-notification" hidden="true">
|
||||
<popupnotificationcontent orient="vertical" align="start">
|
||||
<separator class="thin"/>
|
||||
<label id="pointerLock-cancel" value="&pointerLock.notification.message;"/>
|
||||
<label id="pointerLock-cancel">&pointerLock.notification.message;</label>
|
||||
</popupnotificationcontent>
|
||||
</popupnotification>
|
||||
|
||||
|
@ -35,21 +35,31 @@ function runTests() {
|
||||
expected.type = "sponsored";
|
||||
expected.action = "view";
|
||||
expected.pinned = false;
|
||||
yield addNewTabPageTab();
|
||||
yield null; // wait for reportSitesAction
|
||||
addNewTabPageTab();
|
||||
|
||||
// Wait for addNewTabPageTab and reportSitesAction
|
||||
yield null;
|
||||
yield null;
|
||||
|
||||
// Click the pin button on the link in the 1th tile spot
|
||||
let siteNode = getCell(1).node.querySelector(".newtab-site");
|
||||
let pinButton = siteNode.querySelector(".newtab-control-pin");
|
||||
expected.action = "pin";
|
||||
expected.pinned = true;
|
||||
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
|
||||
// Wait for reportSitesAction
|
||||
yield null;
|
||||
|
||||
// Unpin that link
|
||||
expected.action = "unpin";
|
||||
expected.pinned = false;
|
||||
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
yield whenPagesUpdated();
|
||||
whenPagesUpdated();
|
||||
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
|
||||
// Wait for whenPagesUpdated and reportSitesAction
|
||||
yield null;
|
||||
yield null;
|
||||
|
||||
// Block the site in the 0th tile spot
|
||||
let blockedSite = getCell(0).node.querySelector(".newtab-site");
|
||||
@ -57,11 +67,18 @@ function runTests() {
|
||||
expected.type = "organic";
|
||||
expected.action = "block";
|
||||
expected.pinned = false;
|
||||
yield EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
|
||||
yield whenPagesUpdated();
|
||||
whenPagesUpdated();
|
||||
EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
|
||||
|
||||
// Wait for whenPagesUpdated and reportSitesAction
|
||||
yield null;
|
||||
yield null;
|
||||
|
||||
// Click the 1th link now in the 0th tile spot
|
||||
expected.type = "sponsored";
|
||||
expected.action = "click";
|
||||
yield EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
|
||||
EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
|
||||
|
||||
// Wait for reportSitesAction
|
||||
yield null;
|
||||
}
|
||||
|
@ -109,8 +109,14 @@ let MozLoopPushHandler = {
|
||||
+ (onRegistered ? "" : " onRegistered")
|
||||
+ (onNotification ? "" : " onNotification"));
|
||||
}
|
||||
// Only register new channels
|
||||
if (!(channelID in this.channels)) {
|
||||
|
||||
// If the channel is already registered, callback with an error immediately
|
||||
// so we don't leave code hanging waiting for an onRegistered callback.
|
||||
if (channelID in this.channels) {
|
||||
onRegistered("error: channel already registered: " + channelID);
|
||||
return;
|
||||
}
|
||||
|
||||
this.channels[channelID] = {
|
||||
onRegistered: onRegistered,
|
||||
onNotification: onNotification
|
||||
@ -123,7 +129,6 @@ let MozLoopPushHandler = {
|
||||
} else {
|
||||
this._registerChannels();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -46,8 +46,40 @@
|
||||
Assert.equal(version, 15, "Should have version number 15");
|
||||
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_register_twice_same_channel() {
|
||||
MozLoopPushHandler.register(
|
||||
"chan-2",
|
||||
function(err, url, id) {
|
||||
Assert.equal(err, null, "Should return null for success");
|
||||
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
|
||||
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
|
||||
Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
|
||||
"Should have the url from preferences");
|
||||
Assert.equal(mockWebSocket.origin, kServerPushUrl,
|
||||
"Should have the origin url from preferences");
|
||||
Assert.equal(mockWebSocket.protocol, "push-notification",
|
||||
"Should have the protocol set to push-notifications");
|
||||
|
||||
// Register again for the same channel
|
||||
MozLoopPushHandler.register(
|
||||
"chan-2",
|
||||
function(err, url, id) {
|
||||
Assert.notEqual(err, null, "Should have returned an error");
|
||||
// Notify the first registration to make sure that still works.
|
||||
mockWebSocket.notify(16);
|
||||
},
|
||||
mockWebSocket);
|
||||
function(version, id) {
|
||||
Assert.ok(false, "The 2nd onNotification callback shouldn't be called");
|
||||
});
|
||||
},
|
||||
function(version, id) {
|
||||
Assert.equal(version, 16, "Should have version number 16");
|
||||
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_reconnect_websocket() {
|
||||
|
@ -4,6 +4,8 @@ support-files = file_pdfjs_test.pdf
|
||||
|
||||
[browser_pdfjs_main.js]
|
||||
skip-if = debug # bug 1058695
|
||||
[browser_pdfjs_navigation.js]
|
||||
skip-if = debug # bug 1058695
|
||||
[browser_pdfjs_savedialog.js]
|
||||
[browser_pdfjs_views.js]
|
||||
skip-if = debug # bug 1058695
|
||||
|
206
browser/extensions/pdfjs/test/browser_pdfjs_navigation.js
Normal file
206
browser/extensions/pdfjs/test/browser_pdfjs_navigation.js
Normal file
@ -0,0 +1,206 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
|
||||
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
|
||||
|
||||
const PDF_OUTLINE_ITEMS = 17;
|
||||
const TESTS = [
|
||||
{
|
||||
action: {
|
||||
selector: "button#next",
|
||||
event: "click"
|
||||
},
|
||||
expectedPage: 2,
|
||||
message: "navigated to next page using NEXT button"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: "button#previous",
|
||||
event: "click"
|
||||
},
|
||||
expectedPage: 1,
|
||||
message: "navigated to previous page using PREV button"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: "button#next",
|
||||
event: "click"
|
||||
},
|
||||
expectedPage: 2,
|
||||
message: "navigated to next page using NEXT button"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: "input#pageNumber",
|
||||
value: 1,
|
||||
event: "change"
|
||||
},
|
||||
expectedPage: 1,
|
||||
message: "navigated to first page using pagenumber"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: "#thumbnailView a:nth-child(4)",
|
||||
event: "click"
|
||||
},
|
||||
expectedPage: 4,
|
||||
message: "navigated to 4th page using thumbnail view"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: "#thumbnailView a:nth-child(2)",
|
||||
event: "click"
|
||||
},
|
||||
expectedPage: 2,
|
||||
message: "navigated to 2nd page using thumbnail view"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: ".outlineItem:nth-child(1) a",
|
||||
event: "click"
|
||||
},
|
||||
expectedPage: 1,
|
||||
message: "navigated to 1st page using outline view"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: ".outlineItem:nth-child(" + PDF_OUTLINE_ITEMS + ") a",
|
||||
event: "click"
|
||||
},
|
||||
expectedPage: 4,
|
||||
message: "navigated to 4th page using outline view"
|
||||
},
|
||||
{
|
||||
action: {
|
||||
selector: "input#pageNumber",
|
||||
value: 5,
|
||||
event: "change"
|
||||
},
|
||||
expectedPage: 5,
|
||||
message: "navigated to 5th page using pagenumber"
|
||||
}
|
||||
];
|
||||
|
||||
function test() {
|
||||
var tab;
|
||||
|
||||
let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
||||
let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
|
||||
|
||||
// Make sure pdf.js is the default handler.
|
||||
is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
|
||||
is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
|
||||
|
||||
info('Pref action: ' + handlerInfo.preferredAction);
|
||||
|
||||
waitForExplicitFinish();
|
||||
registerCleanupFunction(function() {
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
tab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
|
||||
gBrowser.selectedTab = tab;
|
||||
|
||||
var newTabBrowser = gBrowser.getBrowserForTab(tab);
|
||||
newTabBrowser.addEventListener("load", function eventHandler() {
|
||||
newTabBrowser.removeEventListener("load", eventHandler, true);
|
||||
|
||||
var document = newTabBrowser.contentDocument,
|
||||
window = newTabBrowser.contentWindow;
|
||||
|
||||
// Runs tests after all 'load' event handlers have fired off
|
||||
window.addEventListener("documentload", function() {
|
||||
runTests(document, window, function () {
|
||||
var pageNumber = document.querySelector('input#pageNumber');
|
||||
is(pageNumber.value, pageNumber.max, "Document is left on the last page");
|
||||
finish();
|
||||
});
|
||||
}, false, true);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function runTests(document, window, finish) {
|
||||
// Check if PDF is opened with internal viewer
|
||||
ok(document.querySelector('div#viewer'), "document content has viewer UI");
|
||||
ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
|
||||
|
||||
// Wait for outline items, the start the navigation actions
|
||||
waitForOutlineItems(document).then(function () {
|
||||
runNextTest(document, window, finish);
|
||||
}, function () {
|
||||
ok(false, "Outline items have ben found");
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* As the page changes asynchronously, we have to wait for the event after
|
||||
* we trigger the action so we will be at the expected page number after each action
|
||||
*
|
||||
* @param document
|
||||
* @param window
|
||||
* @param test
|
||||
* @param callback
|
||||
*/
|
||||
function runNextTest(document, window, endCallback) {
|
||||
var test = TESTS.shift(),
|
||||
deferred = Promise.defer(),
|
||||
pageNumber = document.querySelector('input#pageNumber');
|
||||
|
||||
// Add an event-listener to wait for page to change, afterwards resolve the promise
|
||||
var timeout = window.setTimeout(() => deferred.reject(), 5000);
|
||||
window.addEventListener('pagechange', function pageChange() {
|
||||
if (pageNumber.value == test.expectedPage) {
|
||||
window.removeEventListener('pagechange', pageChange);
|
||||
window.clearTimeout(timeout);
|
||||
deferred.resolve(pageNumber.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Get the element and trigger the action for changing the page
|
||||
var el = document.querySelector(test.action.selector);
|
||||
ok(el, "Element '" + test.action.selector + "' has been found");
|
||||
|
||||
// The value option is for input case
|
||||
if (test.action.value)
|
||||
el.value = test.action.value;
|
||||
|
||||
// Dispatch the event for changing the page
|
||||
el.dispatchEvent(new Event(test.action.event));
|
||||
|
||||
// When the promise gets resolved we call the next test if there are any left
|
||||
// or else we call the final callback which will end the test
|
||||
deferred.promise.then(function (pgNumber) {
|
||||
is(pgNumber, test.expectedPage, test.message);
|
||||
|
||||
if (TESTS.length)
|
||||
runNextTest(document, window, endCallback);
|
||||
else
|
||||
endCallback();
|
||||
}, function () {
|
||||
ok(false, "Test '" + test.message + "' failed with timeout.");
|
||||
endCallback();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Outline Items gets appended to the document latter on we have to
|
||||
* wait for them before we start to navigate though document
|
||||
*
|
||||
* @param document
|
||||
* @returns {deferred.promise|*}
|
||||
*/
|
||||
function waitForOutlineItems(document) {
|
||||
var deferred = Promise.defer();
|
||||
var timeout = setTimeout(() => deferred.reject(), 10000);
|
||||
var interval = setInterval(function () {
|
||||
if (document.querySelectorAll(".outlineItem").length == PDF_OUTLINE_ITEMS) {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
deferred.resolve();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
139
dom/base/AnonymousContent.cpp
Normal file
139
dom/base/AnonymousContent.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
/* -*- Mode: C++; tab-width: 4; 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 "AnonymousContent.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/AnonymousContentBinding.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIDOMHTMLCollection.h"
|
||||
#include "nsStyledElement.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// Ref counting and cycle collection
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AnonymousContent, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AnonymousContent, Release)
|
||||
NS_IMPL_CYCLE_COLLECTION(AnonymousContent, mContentNode)
|
||||
|
||||
AnonymousContent::AnonymousContent(Element* aContentNode) :
|
||||
mContentNode(aContentNode)
|
||||
{}
|
||||
|
||||
AnonymousContent::~AnonymousContent()
|
||||
{
|
||||
}
|
||||
|
||||
nsCOMPtr<Element>
|
||||
AnonymousContent::GetContentNode()
|
||||
{
|
||||
return mContentNode;
|
||||
}
|
||||
|
||||
void
|
||||
AnonymousContent::SetContentNode(Element* aContentNode)
|
||||
{
|
||||
mContentNode = aContentNode;
|
||||
}
|
||||
|
||||
void
|
||||
AnonymousContent::SetTextContentForElement(const nsAString& aElementId,
|
||||
const nsAString& aText,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
Element* element = GetElementById(aElementId);
|
||||
if (!element) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
element->SetTextContent(aText, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
AnonymousContent::GetTextContentForElement(const nsAString& aElementId,
|
||||
DOMString& aText,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
Element* element = GetElementById(aElementId);
|
||||
if (!element) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
element->GetTextContent(aText, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
AnonymousContent::SetAttributeForElement(const nsAString& aElementId,
|
||||
const nsAString& aName,
|
||||
const nsAString& aValue,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
Element* element = GetElementById(aElementId);
|
||||
if (!element) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
element->SetAttribute(aName, aValue, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
AnonymousContent::GetAttributeForElement(const nsAString& aElementId,
|
||||
const nsAString& aName,
|
||||
DOMString& aValue,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
Element* element = GetElementById(aElementId);
|
||||
if (!element) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
element->GetAttribute(aName, aValue);
|
||||
}
|
||||
|
||||
void
|
||||
AnonymousContent::RemoveAttributeForElement(const nsAString& aElementId,
|
||||
const nsAString& aName,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
Element* element = GetElementById(aElementId);
|
||||
if (!element) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
element->RemoveAttribute(aName, aRv);
|
||||
}
|
||||
|
||||
Element*
|
||||
AnonymousContent::GetElementById(const nsAString& aElementId)
|
||||
{
|
||||
// This can be made faster in the future if needed.
|
||||
nsCOMPtr<nsIAtom> elementId = do_GetAtom(aElementId);
|
||||
for (nsIContent* kid = mContentNode->GetFirstChild(); kid;
|
||||
kid = kid->GetNextNode(mContentNode)) {
|
||||
if (!kid->IsElement()) {
|
||||
continue;
|
||||
}
|
||||
nsIAtom* id = kid->AsElement()->GetID();
|
||||
if (id && id == elementId) {
|
||||
return kid->AsElement();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
AnonymousContent::WrapObject(JSContext* aCx)
|
||||
{
|
||||
return AnonymousContentBinding::Wrap(aCx, this);
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
64
dom/base/AnonymousContent.h
Normal file
64
dom/base/AnonymousContent.h
Normal file
@ -0,0 +1,64 @@
|
||||
/* -*- Mode: C++; tab-width: 3; 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/. */
|
||||
|
||||
#ifndef mozilla_dom_AnonymousContent_h
|
||||
#define mozilla_dom_AnonymousContent_h
|
||||
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsICSSDeclaration.h"
|
||||
#include "nsIDocument.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class Element;
|
||||
|
||||
class AnonymousContent MOZ_FINAL
|
||||
{
|
||||
public:
|
||||
// Ref counting and cycle collection
|
||||
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnonymousContent)
|
||||
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnonymousContent)
|
||||
|
||||
explicit AnonymousContent(Element* aContentNode);
|
||||
nsCOMPtr<Element> GetContentNode();
|
||||
void SetContentNode(Element* aContentNode);
|
||||
JSObject* WrapObject(JSContext* aCx);
|
||||
|
||||
// WebIDL methods
|
||||
void SetTextContentForElement(const nsAString& aElementId,
|
||||
const nsAString& aText,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void GetTextContentForElement(const nsAString& aElementId,
|
||||
DOMString& aText,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void SetAttributeForElement(const nsAString& aElementId,
|
||||
const nsAString& aName,
|
||||
const nsAString& aValue,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void GetAttributeForElement(const nsAString& aElementId,
|
||||
const nsAString& aName,
|
||||
DOMString& aValue,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void RemoveAttributeForElement(const nsAString& aElementId,
|
||||
const nsAString& aName,
|
||||
ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
~AnonymousContent();
|
||||
Element* GetElementById(const nsAString& aElementId);
|
||||
nsCOMPtr<Element> mContentNode;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_AnonymousContent_h
|
@ -142,6 +142,7 @@ EXPORTS.mozilla += [
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'AnonymousContent.h',
|
||||
'Attr.h',
|
||||
'BarProps.h',
|
||||
'BlobSet.h',
|
||||
@ -196,6 +197,7 @@ EXPORTS.mozilla.dom += [
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AnonymousContent.cpp',
|
||||
'Attr.cpp',
|
||||
'BarProps.cpp',
|
||||
'ChildIterator.cpp',
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "mozilla/dom/HTMLTemplateElement.h"
|
||||
#include "mozilla/dom/HTMLContentElement.h"
|
||||
#include "mozilla/dom/HTMLShadowElement.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/TextDecoder.h"
|
||||
#include "mozilla/dom/TouchEvent.h"
|
||||
@ -5081,7 +5082,7 @@ nsContentUtils::LeaveMicroTask()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (--sMicroTaskLevel == 0) {
|
||||
nsDOMMutationObserver::HandleMutations();
|
||||
PerformMainThreadMicroTaskCheckpoint();
|
||||
nsDocument::ProcessBaseElementQueue();
|
||||
}
|
||||
}
|
||||
@ -5107,6 +5108,14 @@ nsContentUtils::SetMicroTaskLevel(uint32_t aLevel)
|
||||
sMicroTaskLevel = aLevel;
|
||||
}
|
||||
|
||||
void
|
||||
nsContentUtils::PerformMainThreadMicroTaskCheckpoint()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsDOMMutationObserver::HandleMutations();
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for nsContentUtils::ProcessViewportInfo.
|
||||
*
|
||||
|
@ -1542,6 +1542,8 @@ public:
|
||||
static uint32_t MicroTaskLevel();
|
||||
static void SetMicroTaskLevel(uint32_t aLevel);
|
||||
|
||||
static void PerformMainThreadMicroTaskCheckpoint();
|
||||
|
||||
/* Process viewport META data. This gives us information for the scale
|
||||
* and zoom of a page on mobile devices. We stick the information in
|
||||
* the document header and use it later on after rendering.
|
||||
|
@ -68,6 +68,7 @@
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsIServiceWorkerManager.h"
|
||||
|
||||
#include "nsCanvasFrame.h"
|
||||
#include "nsContentCID.h"
|
||||
#include "nsError.h"
|
||||
#include "nsPresShell.h"
|
||||
@ -182,6 +183,7 @@
|
||||
#include "nsWrapperCacheInlines.h"
|
||||
#include "nsSandboxFlags.h"
|
||||
#include "nsIAppsService.h"
|
||||
#include "mozilla/dom/AnonymousContent.h"
|
||||
#include "mozilla/dom/AnimationTimeline.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/DocumentFragment.h"
|
||||
@ -1986,6 +1988,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
|
||||
|
||||
// Traverse all our nsCOMArrays.
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
|
||||
@ -5138,6 +5141,78 @@ nsDocument::StyleRuleRemoved(nsIStyleSheet* aSheet,
|
||||
|
||||
#undef DO_STYLESHEET_NOTIFICATION
|
||||
|
||||
already_AddRefed<AnonymousContent>
|
||||
nsIDocument::InsertAnonymousContent(Element& aElement, ErrorResult& aRv)
|
||||
{
|
||||
nsIPresShell* shell = GetShell();
|
||||
if (!shell || !shell->GetCanvasFrame()) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<Element> container = shell->GetCanvasFrame()
|
||||
->GetCustomContentContainer();
|
||||
if (!container) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Clone the node to avoid returning a direct reference
|
||||
nsCOMPtr<nsINode> clonedElement = aElement.CloneNode(true, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Insert the element into the container
|
||||
nsresult rv;
|
||||
rv = container->AppendChildTo(clonedElement->AsContent(), true);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<AnonymousContent> anonymousContent =
|
||||
new AnonymousContent(clonedElement->AsElement());
|
||||
mAnonymousContents.AppendElement(anonymousContent);
|
||||
|
||||
return anonymousContent.forget();
|
||||
}
|
||||
|
||||
void
|
||||
nsIDocument::RemoveAnonymousContent(AnonymousContent& aContent,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsIPresShell* shell = GetShell();
|
||||
if (!shell) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<Element> container = shell->GetCanvasFrame()
|
||||
->GetCustomContentContainer();
|
||||
if (!container) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over know customContents to get and remove the right one
|
||||
for (int32_t i = mAnonymousContents.Length() - 1; i >= 0; --i) {
|
||||
if (mAnonymousContents[i] == &aContent) {
|
||||
// Get the node from the customContent
|
||||
nsCOMPtr<Element> node = aContent.GetContentNode();
|
||||
|
||||
// Remove the entry in mAnonymousContents
|
||||
mAnonymousContents.RemoveElementAt(i);
|
||||
|
||||
// Remove the node from its container
|
||||
container->RemoveChild(*node, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// nsIDOMDocument interface
|
||||
|
@ -92,6 +92,7 @@ class ImageLoader;
|
||||
|
||||
namespace dom {
|
||||
class AnimationTimeline;
|
||||
class AnonymousContent;
|
||||
class Attr;
|
||||
class BoxObject;
|
||||
class CDATASection;
|
||||
@ -715,6 +716,15 @@ public:
|
||||
return mDidDocumentOpen;
|
||||
}
|
||||
|
||||
already_AddRefed<mozilla::dom::AnonymousContent>
|
||||
InsertAnonymousContent(mozilla::dom::Element& aElement,
|
||||
mozilla::ErrorResult& aError);
|
||||
void RemoveAnonymousContent(mozilla::dom::AnonymousContent& aContent,
|
||||
mozilla::ErrorResult& aError);
|
||||
nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>>& GetAnonymousContents() {
|
||||
return mAnonymousContents;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual Element *GetRootElementInternal() const = 0;
|
||||
|
||||
@ -2747,6 +2757,8 @@ protected:
|
||||
|
||||
nsRefPtr<mozilla::dom::XPathEvaluator> mXPathEvaluator;
|
||||
|
||||
nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>> mAnonymousContents;
|
||||
|
||||
uint32_t mBlockDOMContentLoaded;
|
||||
bool mDidFireDOMContentLoaded:1;
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ support-files =
|
||||
file_url.jsm
|
||||
file_empty.html
|
||||
|
||||
[test_anonymousContent_xul_window.xul]
|
||||
[test_bug715041.xul]
|
||||
[test_bug715041_removal.xul]
|
||||
[test_domrequesthelper.xul]
|
||||
|
@ -239,6 +239,10 @@ support-files =
|
||||
w3element_traversal.svg
|
||||
wholeTexty-helper.xml
|
||||
|
||||
[test_anonymousContent_api.html]
|
||||
[test_anonymousContent_append_after_reflow.html]
|
||||
[test_anonymousContent_insert.html]
|
||||
[test_anonymousContent_manipulate_content.html]
|
||||
[test_appname_override.html]
|
||||
[test_audioWindowUtils.html]
|
||||
[test_audioNotification.html]
|
||||
|
55
dom/base/test/test_anonymousContent_api.html
Normal file
55
dom/base/test/test_anonymousContent_api.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1020244 - Test the chrome-only AnonymousContent API</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
// Testing the presence of the chrome-only API
|
||||
ok(!document.insertAnonymousContent,
|
||||
"Content document shouldn't have access to insertAnonymousContent");
|
||||
ok(!document.removeAnonymousContent,
|
||||
"Content document shouldn't have access to removeAnonymousContent");
|
||||
|
||||
let chromeDocument = SpecialPowers.wrap(document);
|
||||
ok(chromeDocument.insertAnonymousContent,
|
||||
"Chrome document should have access to insertAnonymousContent");
|
||||
ok(chromeDocument.removeAnonymousContent,
|
||||
"Chrome document should have access to removeAnonymousContent");
|
||||
|
||||
// Testing invalid inputs
|
||||
let invalidNodes = [null, undefined, false, 1, "string"];
|
||||
for (let node of invalidNodes) {
|
||||
let didThrow = false;
|
||||
try {
|
||||
chromeDocument.insertAnonymousContent(node);
|
||||
} catch (e) {
|
||||
didThrow = true;
|
||||
}
|
||||
ok(didThrow, "Passing an invalid node to insertAnonymousContent should throw");
|
||||
}
|
||||
|
||||
// Testing the API of the returned object
|
||||
let div = document.createElement("div");
|
||||
div.textContent = "this is a test element";
|
||||
let anonymousContent = chromeDocument.insertAnonymousContent(div);
|
||||
ok(anonymousContent, "AnonymousContent object returned");
|
||||
|
||||
let members = ["getTextContentForElement", "setTextContentForElement",
|
||||
"getAttributeForElement", "setAttributeForElement",
|
||||
"removeAttributeForElement"];
|
||||
for (let member of members) {
|
||||
ok(member in anonymousContent, "AnonymousContent object defines " + member);
|
||||
}
|
||||
chromeDocument.removeAnonymousContent(anonymousContent);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
40
dom/base/test/test_anonymousContent_append_after_reflow.html
Normal file
40
dom/base/test/test_anonymousContent_append_after_reflow.html
Normal file
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1020244 -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1020244 - Make sure anonymous content still works after a reflow (after the canvasframe has been reconstructed)</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div id="test-element" style="color:red;">text content</div>
|
||||
</div>
|
||||
<script type="application/javascript;version=1.8">
|
||||
info("Inserting anonymous content into the document frame");
|
||||
let chromeDocument = SpecialPowers.wrap(document);
|
||||
let testElement = document.querySelector("div");
|
||||
let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
|
||||
|
||||
info("Modifying the style of an element in the anonymous content");
|
||||
let style = anonymousContent.setAttributeForElement("test-element",
|
||||
"style", "color:green;");
|
||||
|
||||
info("Toggling the display style on the document element to force reframing");
|
||||
// Note that we force sync reflows to make sure the canvasframe is recreated
|
||||
// synchronously.
|
||||
document.documentElement.style.display = "none";
|
||||
let forceFlush = document.documentElement.offsetHeight;
|
||||
document.documentElement.style.display = "block";
|
||||
forceFlush = document.documentElement.offsetHeight;
|
||||
|
||||
info("Checking that the anonymous content can be retrieved still");
|
||||
let style = anonymousContent.getAttributeForElement("test-element", "style");
|
||||
is(style, "color:green;", "The anonymous content still exists after reflow");
|
||||
|
||||
info("Removing the anonymous content");
|
||||
chromeDocument.removeAnonymousContent(anonymousContent);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
45
dom/base/test/test_anonymousContent_insert.html
Normal file
45
dom/base/test/test_anonymousContent_insert.html
Normal file
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1020244 - Insert content using the AnonymousContent API, several times, and don't remove it</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
|
||||
<div>
|
||||
<div id="id" class="test">text content</div>
|
||||
</div>
|
||||
<script type="application/javascript;version=1.8">
|
||||
const INSERTED_NB = 5;
|
||||
|
||||
// Insert the same content several times
|
||||
let chromeDocument = SpecialPowers.wrap(document);
|
||||
let testElement = document.querySelector("div");
|
||||
|
||||
let anonymousContents = [];
|
||||
for (let i = 0; i < INSERTED_NB; i ++) {
|
||||
let content = chromeDocument.insertAnonymousContent(testElement);
|
||||
// Adding an expando pointing to the document to make sure this doesn't
|
||||
// cause leaks.
|
||||
content.dummyExpando = testElement.ownerDocument;
|
||||
anonymousContents.push(content);
|
||||
}
|
||||
|
||||
// Make sure that modifying one of the inserted elements does not modify the
|
||||
// other ones.
|
||||
anonymousContents[0].setAttributeForElement("id", "class", "updated");
|
||||
for (let i = 1; i < INSERTED_NB; i ++) {
|
||||
is(anonymousContents[i].getAttributeForElement("id", "class"),
|
||||
"test",
|
||||
"Element " + i + " didn't change when element 0 was changed");
|
||||
}
|
||||
|
||||
// Do not remove inserted elements on purpose to test for potential leaks too.
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
66
dom/base/test/test_anonymousContent_manipulate_content.html
Normal file
66
dom/base/test/test_anonymousContent_manipulate_content.html
Normal file
@ -0,0 +1,66 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1020244 - Manipulate content created with the AnonymousContent API</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
|
||||
<div>
|
||||
<div id="test-element" class="test-class" test="test">text content</div>
|
||||
</div>
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
// Insert content
|
||||
let chromeDocument = SpecialPowers.wrap(document);
|
||||
let testElement = document.querySelector("div");
|
||||
let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
|
||||
|
||||
// Test getting/setting text content.
|
||||
is(anonymousContent.getTextContentForElement("test-element"),
|
||||
"text content", "Textcontent for the test element is correct");
|
||||
|
||||
anonymousContent.setTextContentForElement("test-element",
|
||||
"updated text content");
|
||||
is(anonymousContent.getTextContentForElement("test-element"),
|
||||
"updated text content",
|
||||
"Textcontent for the test element is correct after update");
|
||||
|
||||
// Test that modifying the original DOM element doesn't change the inserted
|
||||
// element.
|
||||
testElement.removeAttribute("test");
|
||||
is(anonymousContent.getAttributeForElement("test-element", "test"),
|
||||
"test",
|
||||
"Removing attributes on the original DOM node does not change the inserted node");
|
||||
|
||||
testElement.setAttribute("test", "test-updated");
|
||||
is(anonymousContent.getAttributeForElement("test-element", "test"),
|
||||
"test",
|
||||
"Setting attributes on the original DOM node does not change the inserted node");
|
||||
|
||||
// Test getting/setting/removing attributes on the inserted element and test
|
||||
// that this doesn't change the original DOM element.
|
||||
is(anonymousContent.getAttributeForElement("test-element", "class"),
|
||||
"test-class", "Class attribute for the test element is correct");
|
||||
|
||||
anonymousContent.setAttributeForElement("test-element", "class",
|
||||
"updated-test-class");
|
||||
is(anonymousContent.getAttributeForElement("test-element", "class"),
|
||||
"updated-test-class",
|
||||
"Class attribute for the test element is correct after update");
|
||||
ok(testElement.getAttribute("class") !== "updated-test-class",
|
||||
"Class attribute change on the inserted node does not change the original DOM node");
|
||||
|
||||
anonymousContent.removeAttributeForElement("test-element", "class");
|
||||
is(anonymousContent.getAttributeForElement("test-element", "class"), null,
|
||||
"Class attribute for the test element was removed");
|
||||
|
||||
chromeDocument.removeAnonymousContent(anonymousContent);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
30
dom/base/test/test_anonymousContent_xul_window.xul
Normal file
30
dom/base/test/test_anonymousContent_xul_window.xul
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
|
||||
Check that XUL windows aren't supported, that the API throws, but doesn't crash.
|
||||
-->
|
||||
<window title="Anonymous content in a XUL window"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml"></body>
|
||||
<box>This is a test box</box>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
// Insert content
|
||||
let testElement = document.querySelector("box");
|
||||
|
||||
let didThrow = false;
|
||||
try {
|
||||
document.insertAnonymousContent(testElement);
|
||||
} catch (e) {
|
||||
didThrow = true;
|
||||
}
|
||||
|
||||
ok(didThrow,
|
||||
"Inserting anonymous content in a XUL document did throw an exception")
|
||||
</script>
|
||||
</window>
|
@ -101,6 +101,10 @@ DOMInterfaces = {
|
||||
'concrete': False
|
||||
},
|
||||
|
||||
'AnonymousContent': {
|
||||
'wrapperCache': False
|
||||
},
|
||||
|
||||
'ArchiveReader': {
|
||||
'nativeType': 'mozilla::dom::archivereader::ArchiveReader',
|
||||
},
|
||||
|
@ -100,7 +100,7 @@ AbortablePromise::Abort()
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &AbortablePromise::DoAbort);
|
||||
Promise::DispatchToMainOrWorkerThread(runnable);
|
||||
Promise::DispatchToMicroTask(runnable);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -35,78 +35,55 @@ using namespace workers;
|
||||
|
||||
NS_IMPL_ISUPPORTS0(PromiseNativeHandler)
|
||||
|
||||
// PromiseTask
|
||||
|
||||
// This class processes the promise's callbacks with promise's result.
|
||||
class PromiseTask MOZ_FINAL : public nsRunnable
|
||||
class PromiseCallbackTask MOZ_FINAL : public nsRunnable
|
||||
{
|
||||
public:
|
||||
explicit PromiseTask(Promise* aPromise)
|
||||
: mPromise(aPromise)
|
||||
{
|
||||
MOZ_ASSERT(aPromise);
|
||||
MOZ_COUNT_CTOR(PromiseTask);
|
||||
}
|
||||
|
||||
protected:
|
||||
~PromiseTask()
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(PromiseTask);
|
||||
MOZ_COUNT_DTOR(PromiseTask);
|
||||
}
|
||||
|
||||
public:
|
||||
NS_IMETHOD
|
||||
Run() MOZ_OVERRIDE
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(PromiseTask);
|
||||
mPromise->mTaskPending = false;
|
||||
mPromise->RunTask();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<Promise> mPromise;
|
||||
NS_DECL_OWNINGTHREAD
|
||||
};
|
||||
|
||||
// This class processes the promise's callbacks with promise's result.
|
||||
class PromiseResolverTask MOZ_FINAL : public nsRunnable
|
||||
{
|
||||
public:
|
||||
PromiseResolverTask(Promise* aPromise,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
Promise::PromiseState aState)
|
||||
PromiseCallbackTask(Promise* aPromise,
|
||||
PromiseCallback* aCallback,
|
||||
const JS::Value& aValue)
|
||||
: mPromise(aPromise)
|
||||
, mCallback(aCallback)
|
||||
, mValue(CycleCollectedJSRuntime::Get()->Runtime(), aValue)
|
||||
, mState(aState)
|
||||
{
|
||||
MOZ_ASSERT(aPromise);
|
||||
MOZ_ASSERT(mState != Promise::Pending);
|
||||
MOZ_COUNT_CTOR(PromiseResolverTask);
|
||||
MOZ_ASSERT(aCallback);
|
||||
MOZ_COUNT_CTOR(PromiseCallbackTask);
|
||||
}
|
||||
|
||||
virtual
|
||||
~PromiseResolverTask()
|
||||
~PromiseCallbackTask()
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(PromiseResolverTask);
|
||||
MOZ_COUNT_DTOR(PromiseResolverTask);
|
||||
NS_ASSERT_OWNINGTHREAD(PromiseCallbackTask);
|
||||
MOZ_COUNT_DTOR(PromiseCallbackTask);
|
||||
}
|
||||
|
||||
protected:
|
||||
NS_IMETHOD
|
||||
Run() MOZ_OVERRIDE
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(PromiseResolverTask);
|
||||
mPromise->RunResolveTask(
|
||||
JS::Handle<JS::Value>::fromMarkedLocation(mValue.address()),
|
||||
mState, Promise::SyncTask);
|
||||
NS_ASSERT_OWNINGTHREAD(PromiseCallbackTask);
|
||||
ThreadsafeAutoJSContext cx;
|
||||
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
|
||||
MOZ_ASSERT(wrapper); // It was preserved!
|
||||
JSAutoCompartment ac(cx, wrapper);
|
||||
|
||||
JS::Rooted<JS::Value> value(cx, mValue);
|
||||
if (!MaybeWrapValue(cx, &value)) {
|
||||
NS_WARNING("Failed to wrap value into the right compartment.");
|
||||
JS_ClearPendingException(cx);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mCallback->Call(cx, value);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<Promise> mPromise;
|
||||
nsRefPtr<PromiseCallback> mCallback;
|
||||
JS::PersistentRooted<JS::Value> mValue;
|
||||
Promise::PromiseState mState;
|
||||
NS_DECL_OWNINGTHREAD;
|
||||
};
|
||||
|
||||
@ -202,9 +179,6 @@ protected:
|
||||
ThreadsafeAutoJSContext cx;
|
||||
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
|
||||
MOZ_ASSERT(wrapper); // It was preserved!
|
||||
if (!wrapper) {
|
||||
return NS_OK;
|
||||
}
|
||||
JSAutoCompartment ac(cx, wrapper);
|
||||
|
||||
JS::Rooted<JSObject*> resolveFunc(cx,
|
||||
@ -247,7 +221,7 @@ protected:
|
||||
NS_WARNING("Failed to wrap value into the right compartment.");
|
||||
}
|
||||
|
||||
mPromise->RejectInternal(cx, exn, Promise::SyncTask);
|
||||
mPromise->RejectInternal(cx, exn);
|
||||
}
|
||||
// At least one of resolveFunc or rejectFunc have been called, so ignore
|
||||
// the exception. FIXME(nsm): This should be reported to the error
|
||||
@ -306,7 +280,6 @@ Promise::Promise(nsIGlobalObject* aGlobal)
|
||||
, mRejectionStack(nullptr)
|
||||
, mFullfillmentStack(nullptr)
|
||||
, mState(Pending)
|
||||
, mTaskPending(false)
|
||||
, mHadRejectCallback(false)
|
||||
, mResolvePending(false)
|
||||
{
|
||||
@ -381,6 +354,23 @@ Promise::MaybeReject(JSContext* aCx,
|
||||
MaybeRejectInternal(aCx, aValue);
|
||||
}
|
||||
|
||||
void
|
||||
Promise::PerformMicroTaskCheckpoint()
|
||||
{
|
||||
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
||||
nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
|
||||
runtime->GetPromiseMicroTaskQueue();
|
||||
|
||||
while (!microtaskQueue.IsEmpty()) {
|
||||
nsRefPtr<nsIRunnable> runnable = microtaskQueue.ElementAt(0);
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// This function can re-enter, so we remove the element before calling.
|
||||
microtaskQueue.RemoveElementAt(0);
|
||||
runnable->Run();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
||||
{
|
||||
@ -941,13 +931,11 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
|
||||
RemoveFeature();
|
||||
}
|
||||
|
||||
// If promise's state is resolved, queue a task to process our resolve
|
||||
// If promise's state is fulfilled, queue a task to process our fulfill
|
||||
// callbacks with promise's result. If promise's state is rejected, queue a
|
||||
// task to process our reject callbacks with promise's result.
|
||||
if (mState != Pending && !mTaskPending) {
|
||||
nsRefPtr<PromiseTask> task = new PromiseTask(this);
|
||||
DispatchToMainOrWorkerThread(task);
|
||||
mTaskPending = true;
|
||||
if (mState != Pending) {
|
||||
EnqueueCallbackTasks();
|
||||
}
|
||||
}
|
||||
|
||||
@ -983,43 +971,15 @@ private:
|
||||
};
|
||||
|
||||
/* static */ void
|
||||
Promise::DispatchToMainOrWorkerThread(nsIRunnable* aRunnable)
|
||||
Promise::DispatchToMicroTask(nsIRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(aRunnable);
|
||||
if (NS_IsMainThread()) {
|
||||
NS_DispatchToCurrentThread(aRunnable);
|
||||
return;
|
||||
}
|
||||
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
||||
MOZ_ASSERT(worker);
|
||||
nsRefPtr<WrappedWorkerRunnable> task = new WrappedWorkerRunnable(worker, aRunnable);
|
||||
task->Dispatch(worker->GetJSContext());
|
||||
}
|
||||
|
||||
void
|
||||
Promise::RunTask()
|
||||
{
|
||||
MOZ_ASSERT(mState != Pending);
|
||||
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
||||
nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
|
||||
runtime->GetPromiseMicroTaskQueue();
|
||||
|
||||
nsTArray<nsRefPtr<PromiseCallback>> callbacks;
|
||||
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
|
||||
: mRejectCallbacks);
|
||||
mResolveCallbacks.Clear();
|
||||
mRejectCallbacks.Clear();
|
||||
|
||||
ThreadsafeAutoJSContext cx;
|
||||
JS::Rooted<JS::Value> value(cx, mResult);
|
||||
JS::Rooted<JSObject*> wrapper(cx, GetWrapper());
|
||||
MOZ_ASSERT(wrapper); // We preserved it
|
||||
|
||||
JSAutoCompartment ac(cx, wrapper);
|
||||
if (!MaybeWrapValue(cx, &value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
|
||||
callbacks[i]->Call(cx, value);
|
||||
}
|
||||
microtaskQueue.AppendElement(aRunnable);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1069,26 +1029,24 @@ Promise::MaybeReportRejected()
|
||||
|
||||
void
|
||||
Promise::MaybeResolveInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aAsynchronous)
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
if (mResolvePending) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResolveInternal(aCx, aValue, aAsynchronous);
|
||||
ResolveInternal(aCx, aValue);
|
||||
}
|
||||
|
||||
void
|
||||
Promise::MaybeRejectInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aAsynchronous)
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
if (mResolvePending) {
|
||||
return;
|
||||
}
|
||||
|
||||
RejectInternal(aCx, aValue, aAsynchronous);
|
||||
RejectInternal(aCx, aValue);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1097,14 +1055,13 @@ Promise::HandleException(JSContext* aCx)
|
||||
JS::Rooted<JS::Value> exn(aCx);
|
||||
if (JS_GetPendingException(aCx, &exn)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
RejectInternal(aCx, exn, SyncTask);
|
||||
RejectInternal(aCx, exn);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Promise::ResolveInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aAsynchronous)
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
mResolvePending = true;
|
||||
|
||||
@ -1126,45 +1083,27 @@ Promise::ResolveInternal(JSContext* aCx,
|
||||
new PromiseInit(thenObj, mozilla::dom::GetIncumbentGlobal());
|
||||
nsRefPtr<ThenableResolverTask> task =
|
||||
new ThenableResolverTask(this, valueObj, thenCallback);
|
||||
DispatchToMainOrWorkerThread(task);
|
||||
DispatchToMicroTask(task);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the synchronous flag is set, process our resolve callbacks with
|
||||
// value. Otherwise, the synchronous flag is unset, queue a task to process
|
||||
// own resolve callbacks with value. Otherwise, the synchronous flag is
|
||||
// unset, queue a task to process our resolve callbacks with value.
|
||||
RunResolveTask(aValue, Resolved, aAsynchronous);
|
||||
MaybeSettle(aValue, Resolved);
|
||||
}
|
||||
|
||||
void
|
||||
Promise::RejectInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aAsynchronous)
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
mResolvePending = true;
|
||||
|
||||
// If the synchronous flag is set, process our reject callbacks with
|
||||
// value. Otherwise, the synchronous flag is unset, queue a task to process
|
||||
// promise's reject callbacks with value.
|
||||
RunResolveTask(aValue, Rejected, aAsynchronous);
|
||||
MaybeSettle(aValue, Rejected);
|
||||
}
|
||||
|
||||
void
|
||||
Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
|
||||
PromiseState aState,
|
||||
PromiseTaskSync aAsynchronous)
|
||||
Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
|
||||
PromiseState aState)
|
||||
{
|
||||
// If the synchronous flag is unset, queue a task to process our
|
||||
// accept callbacks with value.
|
||||
if (aAsynchronous == AsyncTask) {
|
||||
nsRefPtr<PromiseResolverTask> task =
|
||||
new PromiseResolverTask(this, aValue, aState);
|
||||
DispatchToMainOrWorkerThread(task);
|
||||
return;
|
||||
}
|
||||
|
||||
// Promise.all() or Promise.race() implementations will repeatedly call
|
||||
// Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
|
||||
// from asserting.
|
||||
@ -1195,7 +1134,23 @@ Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
|
||||
}
|
||||
}
|
||||
|
||||
RunTask();
|
||||
EnqueueCallbackTasks();
|
||||
}
|
||||
|
||||
void
|
||||
Promise::EnqueueCallbackTasks()
|
||||
{
|
||||
nsTArray<nsRefPtr<PromiseCallback>> callbacks;
|
||||
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
|
||||
: mRejectCallbacks);
|
||||
mResolveCallbacks.Clear();
|
||||
mRejectCallbacks.Clear();
|
||||
|
||||
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
|
||||
nsRefPtr<PromiseCallbackTask> task =
|
||||
new PromiseCallbackTask(this, callbacks[i], mResult);
|
||||
DispatchToMicroTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -1295,8 +1250,7 @@ public:
|
||||
|
||||
// TODO Bug 975246 - nsRefPtr should support operator |nsRefPtr->*funcType|.
|
||||
(workerPromise.get()->*mFunc)(aCx,
|
||||
value,
|
||||
Promise::PromiseTaskSync::SyncTask);
|
||||
value);
|
||||
|
||||
// Release the Promise because it has been resolved/rejected for sure.
|
||||
mPromiseWorkerProxy->CleanUp(aCx);
|
||||
|
@ -111,6 +111,9 @@ public:
|
||||
// specializations in the .cpp for
|
||||
// the T values we support.
|
||||
|
||||
// Called by DOM to let us execute our callbacks. May be called recursively.
|
||||
static void PerformMicroTaskCheckpoint();
|
||||
|
||||
// WebIDL
|
||||
|
||||
nsIGlobalObject* GetParentObject() const
|
||||
@ -165,9 +168,9 @@ protected:
|
||||
|
||||
virtual ~Promise();
|
||||
|
||||
// Queue an async task to current main or worker thread.
|
||||
// Queue an async microtask to current main or worker thread.
|
||||
static void
|
||||
DispatchToMainOrWorkerThread(nsIRunnable* aRunnable);
|
||||
DispatchToMicroTask(nsIRunnable* aRunnable);
|
||||
|
||||
// Do JS-wrapping after Promise creation.
|
||||
void CreateWrapper(ErrorResult& aRv);
|
||||
@ -194,11 +197,6 @@ private:
|
||||
Rejected
|
||||
};
|
||||
|
||||
enum PromiseTaskSync {
|
||||
SyncTask,
|
||||
AsyncTask
|
||||
};
|
||||
|
||||
void SetState(PromiseState aState)
|
||||
{
|
||||
MOZ_ASSERT(mState == Pending);
|
||||
@ -211,15 +209,14 @@ private:
|
||||
mResult = aValue;
|
||||
}
|
||||
|
||||
// This method processes promise's resolve/reject callbacks with promise's
|
||||
// This method enqueues promise's resolve/reject callbacks with promise's
|
||||
// result. It's executed when the resolver.resolve() or resolver.reject() is
|
||||
// called or when the promise already has a result and new callbacks are
|
||||
// appended by then(), catch() or done().
|
||||
void RunTask();
|
||||
void EnqueueCallbackTasks();
|
||||
|
||||
void RunResolveTask(JS::Handle<JS::Value> aValue,
|
||||
Promise::PromiseState aState,
|
||||
PromiseTaskSync aAsynchronous);
|
||||
void MaybeSettle(JS::Handle<JS::Value> aValue,
|
||||
Promise::PromiseState aState);
|
||||
|
||||
void AppendCallbacks(PromiseCallback* aResolveCallback,
|
||||
PromiseCallback* aRejectCallback);
|
||||
@ -236,19 +233,14 @@ private:
|
||||
}
|
||||
|
||||
void MaybeResolveInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aSync = AsyncTask);
|
||||
JS::Handle<JS::Value> aValue);
|
||||
void MaybeRejectInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aSync = AsyncTask);
|
||||
JS::Handle<JS::Value> aValue);
|
||||
|
||||
void ResolveInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aSync = AsyncTask);
|
||||
|
||||
JS::Handle<JS::Value> aValue);
|
||||
void RejectInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
PromiseTaskSync aSync = AsyncTask);
|
||||
JS::Handle<JS::Value> aValue);
|
||||
|
||||
template <typename T>
|
||||
void MaybeSomething(T& aArgument, MaybeFunc aFunc) {
|
||||
@ -313,7 +305,6 @@ private:
|
||||
// have a fulfillment stack.
|
||||
JS::Heap<JSObject*> mFullfillmentStack;
|
||||
PromiseState mState;
|
||||
bool mTaskPending;
|
||||
bool mHadRejectCallback;
|
||||
|
||||
bool mResolvePending;
|
||||
|
@ -84,7 +84,7 @@ ResolvePromiseCallback::Call(JSContext* aCx,
|
||||
return;
|
||||
}
|
||||
|
||||
mPromise->ResolveInternal(aCx, value, Promise::SyncTask);
|
||||
mPromise->ResolveInternal(aCx, value);
|
||||
}
|
||||
|
||||
// RejectPromiseCallback
|
||||
@ -143,7 +143,7 @@ RejectPromiseCallback::Call(JSContext* aCx,
|
||||
}
|
||||
|
||||
|
||||
mPromise->RejectInternal(aCx, value, Promise::SyncTask);
|
||||
mPromise->RejectInternal(aCx, value);
|
||||
}
|
||||
|
||||
// WrapperPromiseCallback
|
||||
@ -279,7 +279,7 @@ WrapperPromiseCallback::Call(JSContext* aCx,
|
||||
return;
|
||||
}
|
||||
|
||||
mNextPromise->RejectInternal(aCx, typeError, Promise::SyncTask);
|
||||
mNextPromise->RejectInternal(aCx, typeError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -91,8 +91,7 @@ private:
|
||||
|
||||
// Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
|
||||
typedef void (Promise::*RunCallbackFunc)(JSContext*,
|
||||
JS::Handle<JS::Value>,
|
||||
Promise::PromiseTaskSync);
|
||||
JS::Handle<JS::Value>);
|
||||
|
||||
void RunCallback(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
|
@ -116,20 +116,57 @@ function promiseGC() {
|
||||
resolve(42);
|
||||
}
|
||||
|
||||
function promiseAsync() {
|
||||
var global = "foo";
|
||||
var f = new Promise(function(r1, r2) {
|
||||
is(global, "foo", "Global should be foo");
|
||||
r1(42);
|
||||
is(global, "foo", "Global should still be foo");
|
||||
function promiseAsync_TimeoutResolveThen() {
|
||||
var handlerExecuted = false;
|
||||
|
||||
setTimeout(function() {
|
||||
is(global, "bar", "Global should still be bar!");
|
||||
runTest();
|
||||
ok(handlerExecuted, "Handler should have been called before the timeout.");
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
}, 0);
|
||||
}).then(function() {
|
||||
global = "bar";
|
||||
|
||||
Promise.resolve().then(function() {
|
||||
handlerExecuted = true;
|
||||
});
|
||||
is(global, "foo", "Global should still be foo (2)");
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseAsync_ResolveTimeoutThen() {
|
||||
var handlerExecuted = false;
|
||||
|
||||
var promise = Promise.resolve();
|
||||
|
||||
setTimeout(function() {
|
||||
ok(handlerExecuted, "Handler should have been called before the timeout.");
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
}, 0);
|
||||
|
||||
promise.then(function() {
|
||||
handlerExecuted = true;
|
||||
});
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseAsync_ResolveThenTimeout() {
|
||||
var handlerExecuted = false;
|
||||
|
||||
Promise.resolve().then(function() {
|
||||
handlerExecuted = true;
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
ok(handlerExecuted, "Handler should have been called before the timeout.");
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
}, 0);
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseDoubleThen() {
|
||||
@ -715,7 +752,10 @@ function promiseWrapperAsyncResolution()
|
||||
}
|
||||
|
||||
var tests = [ promiseResolve, promiseReject,
|
||||
promiseException, promiseGC, promiseAsync,
|
||||
promiseException, promiseGC,
|
||||
promiseAsync_TimeoutResolveThen,
|
||||
promiseAsync_ResolveTimeoutThen,
|
||||
promiseAsync_ResolveThenTimeout,
|
||||
promiseDoubleThen, promiseThenException,
|
||||
promiseThenCatchThen, promiseRejectThenCatchThen,
|
||||
promiseRejectThenCatchThen2,
|
||||
|
56
dom/webidl/AnonymousContent.webidl
Normal file
56
dom/webidl/AnonymousContent.webidl
Normal file
@ -0,0 +1,56 @@
|
||||
/* -*- Mode: IDL; 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/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file declares the AnonymousContent interface which is used to
|
||||
* manipulate content that has been inserted into the document's canvasFrame
|
||||
* anonymous container.
|
||||
* See Document.insertAnonymousContent.
|
||||
*
|
||||
* This API never returns a reference to the actual inserted DOM node on
|
||||
* purpose. This is to make sure the content cannot be randomly changed and the
|
||||
* DOM cannot be traversed from the node, so that Gecko can remain in control of
|
||||
* the inserted content.
|
||||
*/
|
||||
|
||||
[ChromeOnly]
|
||||
interface AnonymousContent {
|
||||
/**
|
||||
* Get the text content of an element inside this custom anonymous content.
|
||||
*/
|
||||
[Throws]
|
||||
DOMString getTextContentForElement(DOMString elementId);
|
||||
|
||||
/**
|
||||
* Set the text content of an element inside this custom anonymous content.
|
||||
*/
|
||||
[Throws]
|
||||
void setTextContentForElement(DOMString elementId, DOMString text);
|
||||
|
||||
/**
|
||||
* Get the value of an attribute of an element inside this custom anonymous
|
||||
* content.
|
||||
*/
|
||||
[Throws]
|
||||
DOMString? getAttributeForElement(DOMString elementId,
|
||||
DOMString attributeName);
|
||||
|
||||
/**
|
||||
* Set the value of an attribute of an element inside this custom anonymous
|
||||
* content.
|
||||
*/
|
||||
[Throws]
|
||||
void setAttributeForElement(DOMString elementId,
|
||||
DOMString attributeName,
|
||||
DOMString value);
|
||||
|
||||
/**
|
||||
* Remove an attribute from an element inside this custom anonymous content.
|
||||
*/
|
||||
[Throws]
|
||||
void removeAttributeForElement(DOMString elementId,
|
||||
DOMString attributeName);
|
||||
};
|
@ -355,6 +355,30 @@ partial interface Document {
|
||||
[ChromeOnly] readonly attribute boolean isSrcdocDocument;
|
||||
};
|
||||
|
||||
/**
|
||||
* Chrome document anonymous content management.
|
||||
* This is a Chrome-only API that allows inserting fixed positioned anonymous
|
||||
* content on top of the current page displayed in the document.
|
||||
* The supplied content is cloned and inserted into the document's CanvasFrame.
|
||||
* Note that this only works for HTML documents.
|
||||
*/
|
||||
partial interface Document {
|
||||
/**
|
||||
* Deep-clones the provided element and inserts it into the CanvasFrame.
|
||||
* Returns an AnonymousContent instance that can be used to manipulate the
|
||||
* inserted element.
|
||||
*/
|
||||
[ChromeOnly, NewObject, Throws]
|
||||
AnonymousContent insertAnonymousContent(Element aElement);
|
||||
|
||||
/**
|
||||
* Removes the element inserted into the CanvasFrame given an AnonymousContent
|
||||
* instance.
|
||||
*/
|
||||
[ChromeOnly, Throws]
|
||||
void removeAnonymousContent(AnonymousContent aContent);
|
||||
};
|
||||
|
||||
Document implements XPathEvaluator;
|
||||
Document implements GlobalEventHandlers;
|
||||
Document implements TouchEventHandlers;
|
||||
|
@ -27,6 +27,7 @@ WEBIDL_FILES = [
|
||||
'AnimationEvent.webidl',
|
||||
'AnimationPlayer.webidl',
|
||||
'AnimationTimeline.webidl',
|
||||
'AnonymousContent.webidl',
|
||||
'AppInfo.webidl',
|
||||
'AppNotificationServiceOptions.webidl',
|
||||
'Apps.webidl',
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "mozilla/dom/MessageEvent.h"
|
||||
#include "mozilla/dom/MessageEventBinding.h"
|
||||
#include "mozilla/dom/MessagePortList.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/StructuredClone.h"
|
||||
#include "mozilla/dom/WorkerBinding.h"
|
||||
@ -4112,6 +4113,10 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
// Process a single runnable from the main queue.
|
||||
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
|
||||
|
||||
// Only perform the Promise microtask checkpoint on the outermost event
|
||||
// loop. Don't run it, for example, during sync XHR or importScripts.
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
|
||||
if (NS_HasPendingEvents(mThread)) {
|
||||
// Now *might* be a good time to GC. Let the JS engine make the decision.
|
||||
if (workerCompartment) {
|
||||
|
@ -95,20 +95,81 @@ function promiseException() {
|
||||
});
|
||||
}
|
||||
|
||||
function promiseAsync() {
|
||||
var global = "foo";
|
||||
var f = new Promise(function(r1, r2) {
|
||||
is(global, "foo", "Global should be foo");
|
||||
r1(42);
|
||||
is(global, "foo", "Global should still be foo");
|
||||
function promiseAsync_TimeoutResolveThen() {
|
||||
var handlerExecuted = false;
|
||||
|
||||
setTimeout(function() {
|
||||
is(global, "bar", "Global should still be bar!");
|
||||
runTest();
|
||||
ok(handlerExecuted, "Handler should have been called before the timeout.");
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
}, 0);
|
||||
}).then(function() {
|
||||
global = "bar";
|
||||
|
||||
Promise.resolve().then(function() {
|
||||
handlerExecuted = true;
|
||||
});
|
||||
is(global, "foo", "Global should still be foo (2)");
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseAsync_ResolveTimeoutThen() {
|
||||
var handlerExecuted = false;
|
||||
|
||||
var promise = Promise.resolve();
|
||||
|
||||
setTimeout(function() {
|
||||
ok(handlerExecuted, "Handler should have been called before the timeout.");
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
}, 0);
|
||||
|
||||
promise.then(function() {
|
||||
handlerExecuted = true;
|
||||
});
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseAsync_ResolveThenTimeout() {
|
||||
var handlerExecuted = false;
|
||||
|
||||
Promise.resolve().then(function() {
|
||||
handlerExecuted = true;
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
ok(handlerExecuted, "Handler should have been called before the timeout.");
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
}, 0);
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseAsync_SyncHXRAndImportScripts()
|
||||
{
|
||||
var handlerExecuted = false;
|
||||
|
||||
Promise.resolve().then(function() {
|
||||
handlerExecuted = true;
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
});
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called until the next microtask.");
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "testXHR.txt", false);
|
||||
xhr.send(null);
|
||||
|
||||
ok(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
|
||||
|
||||
importScripts("relativeLoad_import.js");
|
||||
|
||||
ok(!handlerExecuted, "importScripts should not trigger microtask execution.");
|
||||
}
|
||||
|
||||
function promiseDoubleThen() {
|
||||
@ -683,11 +744,53 @@ function promiseResolveThenableCleanStack() {
|
||||
},1000);
|
||||
}
|
||||
|
||||
// Bug 1062323
|
||||
function promiseWrapperAsyncResolution()
|
||||
{
|
||||
var p = new Promise(function(resolve, reject){
|
||||
resolve();
|
||||
});
|
||||
|
||||
var results = [];
|
||||
var q = p.then(function () {
|
||||
results.push("1-1");
|
||||
}).then(function () {
|
||||
results.push("1-2");
|
||||
}).then(function () {
|
||||
results.push("1-3");
|
||||
});
|
||||
|
||||
var r = p.then(function () {
|
||||
results.push("2-1");
|
||||
}).then(function () {
|
||||
results.push("2-2");
|
||||
}).then(function () {
|
||||
results.push("2-3");
|
||||
});
|
||||
|
||||
Promise.all([q, r]).then(function() {
|
||||
var match = results[0] == "1-1" &&
|
||||
results[1] == "2-1" &&
|
||||
results[2] == "1-2" &&
|
||||
results[3] == "2-2" &&
|
||||
results[4] == "1-3" &&
|
||||
results[5] == "2-3";
|
||||
ok(match, "Chained promises should resolve asynchronously.");
|
||||
runTest();
|
||||
}, function() {
|
||||
ok(false, "promiseWrapperAsyncResolution: One of the promises failed.");
|
||||
runTest();
|
||||
});
|
||||
}
|
||||
|
||||
var tests = [
|
||||
promiseResolve,
|
||||
promiseReject,
|
||||
promiseException,
|
||||
promiseAsync,
|
||||
promiseAsync_TimeoutResolveThen,
|
||||
promiseAsync_ResolveTimeoutThen,
|
||||
promiseAsync_ResolveThenTimeout,
|
||||
promiseAsync_SyncHXRAndImportScripts,
|
||||
promiseDoubleThen,
|
||||
promiseThenException,
|
||||
promiseThenCatchThen,
|
||||
@ -729,6 +832,8 @@ var tests = [
|
||||
promiseResolvePromise,
|
||||
|
||||
promiseResolveThenableCleanStack,
|
||||
|
||||
promiseWrapperAsyncResolution,
|
||||
];
|
||||
|
||||
function runTest() {
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
#include "mozilla/dom/PromiseBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/TextDecoderBinding.h"
|
||||
#include "mozilla/dom/TextEncoderBinding.h"
|
||||
#include "mozilla/dom/DOMErrorBinding.h"
|
||||
@ -1053,7 +1053,10 @@ nsXPConnect::AfterProcessNextEvent(nsIThreadInternal *aThread,
|
||||
// Call cycle collector occasionally.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsJSContext::MaybePokeCC();
|
||||
nsDOMMutationObserver::HandleMutations();
|
||||
|
||||
nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
|
||||
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
|
||||
PopJSContextNoScriptContext();
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "nsFrameManager.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "mozilla/dom/AnonymousContent.h"
|
||||
// for touchcaret
|
||||
#include "nsContentList.h"
|
||||
#include "nsContentCreatorFunctions.h"
|
||||
@ -35,6 +36,7 @@
|
||||
//#define DEBUG_CANVAS_FOCUS
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::layout;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
@ -63,7 +65,7 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
|
||||
ErrorResult er;
|
||||
// We won't create touch caret element if preference is not enabled.
|
||||
if (PresShell::TouchCaretPrefEnabled()) {
|
||||
nsRefPtr<dom::NodeInfo> nodeInfo;
|
||||
nsRefPtr<NodeInfo> nodeInfo;
|
||||
|
||||
// Create and append touch caret frame.
|
||||
nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
|
||||
@ -72,7 +74,7 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
|
||||
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
rv = NS_NewHTMLElement(getter_AddRefs(mTouchCaretElement), nodeInfo.forget(),
|
||||
mozilla::dom::NOT_FROM_PARSER);
|
||||
NOT_FROM_PARSER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
aElements.AppendElement(mTouchCaretElement);
|
||||
|
||||
@ -103,6 +105,23 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Create the custom content container.
|
||||
mCustomContentContainer = doc->CreateHTMLElement(nsGkAtoms::div);
|
||||
aElements.AppendElement(mCustomContentContainer);
|
||||
|
||||
// XXX add :moz-native-anonymous or will that be automatically set?
|
||||
rv = mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
|
||||
NS_LITERAL_STRING("moz-custom-content-container"),
|
||||
true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Append all existing AnonymousContent nodes stored at document level if any.
|
||||
int32_t anonymousContentCount = doc->GetAnonymousContents().Length();
|
||||
for (int32_t i = 0; i < anonymousContentCount; ++i) {
|
||||
nsCOMPtr<Element> node = doc->GetAnonymousContents()[i]->GetContentNode();
|
||||
mCustomContentContainer->AppendChildTo(node->AsContent(), true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -120,6 +139,8 @@ nsCanvasFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, uint32
|
||||
if (mSelectionCaretsEndElement) {
|
||||
aElements.AppendElement(mSelectionCaretsEndElement);
|
||||
}
|
||||
|
||||
aElements.AppendElement(mCustomContentContainer);
|
||||
}
|
||||
|
||||
void
|
||||
@ -134,6 +155,22 @@ nsCanvasFrame::DestroyFrom(nsIFrame* aDestructRoot)
|
||||
nsContentUtils::DestroyAnonymousContent(&mTouchCaretElement);
|
||||
nsContentUtils::DestroyAnonymousContent(&mSelectionCaretsStartElement);
|
||||
nsContentUtils::DestroyAnonymousContent(&mSelectionCaretsEndElement);
|
||||
|
||||
// Elements inserted in the custom content container have the same lifetime as
|
||||
// the document, so before destroying the container, make sure to keep a clone
|
||||
// of each of them at document level so they can be re-appended on reframe.
|
||||
if (mCustomContentContainer) {
|
||||
nsCOMPtr<nsIDocument> doc = mContent->OwnerDoc();
|
||||
ErrorResult rv;
|
||||
|
||||
for (int32_t i = doc->GetAnonymousContents().Length() - 1; i >= 0; --i) {
|
||||
AnonymousContent* content = doc->GetAnonymousContents()[i];
|
||||
nsCOMPtr<nsINode> clonedElement = content->GetContentNode()->CloneNode(true, rv);
|
||||
content->SetContentNode(clonedElement->AsElement());
|
||||
}
|
||||
}
|
||||
nsContentUtils::DestroyAnonymousContent(&mCustomContentContainer);
|
||||
|
||||
nsContainerFrame::DestroyFrom(aDestructRoot);
|
||||
}
|
||||
|
||||
|
@ -87,6 +87,11 @@ public:
|
||||
return mSelectionCaretsEndElement;
|
||||
}
|
||||
|
||||
mozilla::dom::Element* GetCustomContentContainer() const
|
||||
{
|
||||
return mCustomContentContainer;
|
||||
}
|
||||
|
||||
/** SetHasFocus tells the CanvasFrame to draw with focus ring
|
||||
* @param aHasFocus true to show focus ring, false to hide it
|
||||
*/
|
||||
@ -138,6 +143,7 @@ protected:
|
||||
nsCOMPtr<mozilla::dom::Element> mTouchCaretElement;
|
||||
nsCOMPtr<mozilla::dom::Element> mSelectionCaretsStartElement;
|
||||
nsCOMPtr<mozilla::dom::Element> mSelectionCaretsEndElement;
|
||||
nsCOMPtr<mozilla::dom::Element> mCustomContentContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -6914,8 +6914,12 @@ nsIFrame::GetFrameFromDirection(nsDirection aDirection, bool aVisual,
|
||||
frameTraversal->Prev();
|
||||
|
||||
traversedFrame = frameTraversal->CurrentItem();
|
||||
if (!traversedFrame)
|
||||
|
||||
// Skip anonymous elements
|
||||
if (!traversedFrame ||
|
||||
traversedFrame->GetContent()->IsRootOfNativeAnonymousSubtree())
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
traversedFrame->IsSelectable(&selectable, nullptr);
|
||||
} // while (!selectable)
|
||||
|
||||
|
@ -2410,7 +2410,7 @@ public:
|
||||
virtual nsresult PeekOffset(nsPeekOffsetStruct *aPos);
|
||||
|
||||
/**
|
||||
* called to find the previous/next selectable leaf frame.
|
||||
* called to find the previous/next non-anonymous selectable leaf frame.
|
||||
* @param aDirection [in] the direction to move in (eDirPrevious or eDirNext)
|
||||
* @param aVisual [in] whether bidi caret behavior is visual (true) or logical (false)
|
||||
* @param aJumpLines [in] whether to allow jumping across line boundaries
|
||||
|
@ -390,3 +390,17 @@ div:-moz-native-anonymous.moz-selectioncaret-right.hidden {
|
||||
margin: 0px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Custom content container in the CanvasFrame, fixed positioned on top of
|
||||
everything else, not reacting to pointer events. */
|
||||
div:-moz-native-anonymous.moz-custom-content-container {
|
||||
pointer-events: none;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
z-index: 2147483648;
|
||||
}
|
||||
|
@ -1190,6 +1190,8 @@ public abstract class GeckoApp
|
||||
Class.forName("android.os.AsyncTask");
|
||||
} catch (ClassNotFoundException e) {}
|
||||
|
||||
MemoryMonitor.getInstance().init(getApplicationContext());
|
||||
|
||||
// GeckoAppShell is tightly coupled to us, rather than
|
||||
// the app context, because various parts of Fennec (e.g.,
|
||||
// GeckoScreenOrientation) use GAS to access the Activity in
|
||||
@ -1199,6 +1201,13 @@ public abstract class GeckoApp
|
||||
GeckoAppShell.setContextGetter(this);
|
||||
GeckoAppShell.setGeckoInterface(this);
|
||||
|
||||
Tabs.getInstance().attachToContext(this);
|
||||
try {
|
||||
Favicons.initializeWithContext(this);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
|
||||
}
|
||||
|
||||
// Did the OS locale change while we were backgrounded? If so,
|
||||
// we need to die so that Gecko will re-init add-ons that touch
|
||||
// the UI.
|
||||
@ -1245,20 +1254,6 @@ public abstract class GeckoApp
|
||||
}, 1000 * 5 /* 5 seconds */);
|
||||
}
|
||||
|
||||
// Heavy load on the Gecko thread can slow down the time it takes for UI to appear on
|
||||
// single-core devices. By minimizing the Gecko thread priority, we ensure that the UI
|
||||
// appears quickly. The priority is reset to normal once thumbnails are loaded.
|
||||
ThreadUtils.reduceGeckoPriority();
|
||||
|
||||
MemoryMonitor.getInstance().init(getApplicationContext());
|
||||
|
||||
Tabs.getInstance().attachToContext(this);
|
||||
try {
|
||||
Favicons.initializeWithContext(this);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
|
||||
}
|
||||
|
||||
Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE);
|
||||
if (stateBundle != null) {
|
||||
// Use the state bundle if it was given as an intent extra. This is
|
||||
@ -1635,10 +1630,6 @@ public abstract class GeckoApp
|
||||
} else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
|
||||
NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
|
||||
}
|
||||
|
||||
// Reset Gecko to normal priority. We may reduce the
|
||||
// priority again later, e.g. for loading thumbnails.
|
||||
ThreadUtils.resetGeckoPriority();
|
||||
}
|
||||
|
||||
private String restoreSessionTabs(final boolean isExternalURL) throws SessionRestoreException {
|
||||
|
@ -51,7 +51,6 @@ public class GeckoThread extends Thread implements GeckoEventListener {
|
||||
if (isCreated())
|
||||
return false;
|
||||
sGeckoThread = new GeckoThread(sArgs, sAction, sUri);
|
||||
ThreadUtils.sGeckoThread = sGeckoThread;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -165,6 +164,7 @@ public class GeckoThread extends Thread implements GeckoEventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
ThreadUtils.sGeckoThread = this;
|
||||
ThreadUtils.sGeckoHandler = new Handler();
|
||||
ThreadUtils.sGeckoQueue = Looper.myQueue();
|
||||
|
||||
|
@ -98,6 +98,9 @@ public class TopSitesPanel extends HomeFragment {
|
||||
// Max number of entries shown in the grid from the cursor.
|
||||
private int mMaxGridEntries;
|
||||
|
||||
// Time in ms until the Gecko thread is reset to normal priority.
|
||||
private static final long PRIORITY_RESET_TIMEOUT = 10000;
|
||||
|
||||
public static TopSitesPanel newInstance() {
|
||||
return new TopSitesPanel();
|
||||
}
|
||||
@ -344,7 +347,7 @@ public class TopSitesPanel extends HomeFragment {
|
||||
// appear, especially during startup (bug 897162). By minimizing the
|
||||
// Gecko thread priority, we ensure that the UI appears quickly. The
|
||||
// priority is reset to normal once thumbnails are loaded.
|
||||
ThreadUtils.reduceGeckoPriority();
|
||||
ThreadUtils.reduceGeckoPriority(PRIORITY_RESET_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,10 @@
|
||||
skip-if = android_version == "10"
|
||||
[testAddonManager]
|
||||
# disabled on x86 only; bug 936216
|
||||
skip-if = processor == "x86"
|
||||
#skip-if = processor == "x86"
|
||||
# disabled across the board - bug 941624, bug 1063509, bug 1073374, bug 1087221,
|
||||
# bug 1088023, bug 1088027, bug 1090206
|
||||
skip-if = true
|
||||
[testAddSearchEngine]
|
||||
# disabled on Android 2.3; bug 979552
|
||||
skip-if = android_version == "10"
|
||||
|
@ -14,8 +14,8 @@ public class testLinkContextMenu extends ContentContextMenuTest {
|
||||
|
||||
LINK_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BIG_LINK_URL);
|
||||
BLANK_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
|
||||
loadAndPaint(LINK_PAGE_URL);
|
||||
verifyPageTitle(LINK_PAGE_TITLE, LINK_PAGE_URL);
|
||||
inputAndLoadUrl(LINK_PAGE_URL);
|
||||
waitForText(LINK_PAGE_TITLE);
|
||||
|
||||
verifyContextMenuItems(linkMenuItems); // Verify context menu items are correct
|
||||
openTabFromContextMenu(linkMenuItems[0],2); // Test the "Open in New Tab" option - expecting 2 tabs: the original and the new one
|
||||
|
@ -12,8 +12,8 @@ public class testMailToContextMenu extends ContentContextMenuTest {
|
||||
blockForGeckoReady();
|
||||
|
||||
MAILTO_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BIG_MAILTO_URL);
|
||||
loadAndPaint(MAILTO_PAGE_URL);
|
||||
verifyPageTitle(MAILTO_PAGE_TITLE, MAILTO_PAGE_URL);
|
||||
inputAndLoadUrl(MAILTO_PAGE_URL);
|
||||
waitForText(MAILTO_PAGE_TITLE);
|
||||
|
||||
verifyContextMenuItems(mailtoMenuItems);
|
||||
verifyCopyOption(mailtoMenuItems[0], "foo.bar@example.com"); // Test the "Copy Email Address" option
|
||||
|
@ -17,9 +17,6 @@ import android.util.Log;
|
||||
public final class ThreadUtils {
|
||||
private static final String LOGTAG = "ThreadUtils";
|
||||
|
||||
// Time in ms until the Gecko thread is reset to normal priority.
|
||||
private static final long PRIORITY_RESET_TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* Controls the action taken when a method like
|
||||
* {@link ThreadUtils#assertOnUiThread(AssertBehavior)} detects a problem.
|
||||
@ -212,8 +209,10 @@ public final class ThreadUtils {
|
||||
*
|
||||
* Note that there are no guards in place to prevent multiple calls
|
||||
* to this method from conflicting with each other.
|
||||
*
|
||||
* @param timeout Timeout in ms after which the priority will be reset
|
||||
*/
|
||||
public static void reduceGeckoPriority() {
|
||||
public static void reduceGeckoPriority(long timeout) {
|
||||
if (Runtime.getRuntime().availableProcessors() > 1) {
|
||||
// Don't reduce priority for multicore devices. We use availableProcessors()
|
||||
// for its fast performance. It may give false negatives (i.e. multicore
|
||||
@ -223,7 +222,7 @@ public final class ThreadUtils {
|
||||
if (!sIsGeckoPriorityReduced && sGeckoThread != null) {
|
||||
sIsGeckoPriorityReduced = true;
|
||||
sGeckoThread.setPriority(Thread.MIN_PRIORITY);
|
||||
getUiHandler().postDelayed(sPriorityResetRunnable, PRIORITY_RESET_TIMEOUT);
|
||||
getUiHandler().postDelayed(sPriorityResetRunnable, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ MappableExtractFile::Create(const char *name, Zip *zip, Zip::Stream *stream)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
z_stream zStream = stream->GetZStream(buffer);
|
||||
zxx_stream zStream = stream->GetZStream(buffer);
|
||||
|
||||
/* Decompress */
|
||||
if (inflateInit2(&zStream, -MAX_WBITS) != Z_OK) {
|
||||
|
@ -180,7 +180,7 @@ private:
|
||||
mozilla::UniquePtr<_MappableBuffer> buffer;
|
||||
|
||||
/* Zlib data */
|
||||
z_stream zStream;
|
||||
zxx_stream zStream;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -72,8 +72,7 @@ SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length)
|
||||
|
||||
DEBUG_LOG("DecompressChunk #%" PRIdSize " @%p (%" PRIdSize "/% " PRIdSize ")",
|
||||
chunk, where, length, chunkLen);
|
||||
z_stream zStream;
|
||||
memset(&zStream, 0, sizeof(zStream));
|
||||
zxx_stream zStream;
|
||||
zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1]))
|
||||
- uint32_t(offsetTable[chunk]);
|
||||
zStream.next_in = const_cast<Bytef *>(buffer + uint32_t(offsetTable[chunk]));
|
||||
|
@ -10,7 +10,98 @@
|
||||
#include <vector>
|
||||
#include <zlib.h>
|
||||
#include "Utils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
/**
|
||||
* Helper class wrapping z_stream to avoid malloc() calls during
|
||||
* inflate. Do not use for deflate.
|
||||
* inflateInit allocates two buffers:
|
||||
* - one for its internal state, which is "approximately 10K bytes" according
|
||||
* to inflate.h from zlib.
|
||||
* - one for the compression window, which depends on the window size passed
|
||||
* to inflateInit2, but is never greater than 32K (1 << MAX_WBITS).
|
||||
* Those buffers are created at instantiation time instead of when calling
|
||||
* inflateInit2. When inflateInit2 is called, it will call zxx_stream::Alloc
|
||||
* to get both these buffers. zxx_stream::Alloc will choose one of the
|
||||
* pre-allocated buffers depending on the requested size.
|
||||
*/
|
||||
class zxx_stream: public z_stream
|
||||
{
|
||||
public:
|
||||
zxx_stream() {
|
||||
memset(this, 0, sizeof(z_stream));
|
||||
zalloc = Alloc;
|
||||
zfree = Free;
|
||||
opaque = this;
|
||||
}
|
||||
|
||||
private:
|
||||
static void *Alloc(void *data, uInt items, uInt size)
|
||||
{
|
||||
size_t buf_size = items * size;
|
||||
zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data);
|
||||
|
||||
if (items == 1 && buf_size <= zStream->stateBuf.size) {
|
||||
return zStream->stateBuf.get();
|
||||
} else if (buf_size == zStream->windowBuf.size) {
|
||||
return zStream->windowBuf.get();
|
||||
} else {
|
||||
MOZ_CRASH("No ZStreamBuf for allocation");
|
||||
}
|
||||
}
|
||||
|
||||
static void Free(void *data, void *ptr)
|
||||
{
|
||||
zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data);
|
||||
|
||||
if (zStream->stateBuf.Equals(ptr)) {
|
||||
zStream->stateBuf.Release();
|
||||
} else if (zStream->windowBuf.Equals(ptr)) {
|
||||
zStream->windowBuf.Release();
|
||||
} else {
|
||||
MOZ_CRASH("Pointer doesn't match a ZStreamBuf");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for each buffer.
|
||||
*/
|
||||
template <size_t Size>
|
||||
class ZStreamBuf
|
||||
{
|
||||
public:
|
||||
ZStreamBuf() : buf(new char[Size]), inUse(false) { }
|
||||
|
||||
char *get()
|
||||
{
|
||||
if (!inUse) {
|
||||
inUse = true;
|
||||
return buf.get();
|
||||
} else {
|
||||
MOZ_CRASH("ZStreamBuf already in use");
|
||||
}
|
||||
}
|
||||
|
||||
void Release()
|
||||
{
|
||||
memset(buf.get(), 0, Size);
|
||||
inUse = false;
|
||||
}
|
||||
|
||||
bool Equals(const void *other) { return other == buf.get(); }
|
||||
|
||||
static const size_t size = Size;
|
||||
|
||||
private:
|
||||
mozilla::UniquePtr<char[]> buf;
|
||||
bool inUse;
|
||||
};
|
||||
|
||||
ZStreamBuf<0x3000> stateBuf; // 0x3000 is an arbitrary size above 10K.
|
||||
ZStreamBuf<1 << MAX_WBITS> windowBuf;
|
||||
};
|
||||
|
||||
/**
|
||||
* Forward declaration
|
||||
@ -87,14 +178,13 @@ public:
|
||||
Type GetType() { return type; }
|
||||
|
||||
/**
|
||||
* Returns a z_stream for use with inflate functions using the given
|
||||
* Returns a zxx_stream for use with inflate functions using the given
|
||||
* buffer as inflate output. The caller is expected to allocate enough
|
||||
* memory for the Stream uncompressed size.
|
||||
*/
|
||||
z_stream GetZStream(void *buf)
|
||||
zxx_stream GetZStream(void *buf)
|
||||
{
|
||||
z_stream zStream;
|
||||
memset(&zStream, 0, sizeof(zStream));
|
||||
zxx_stream zStream;
|
||||
zStream.avail_in = compressedSize;
|
||||
zStream.next_in = reinterpret_cast<Bytef *>(
|
||||
const_cast<void *>(compressedBuf));
|
||||
|
@ -618,39 +618,6 @@ File.prototype = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Read a number of bytes from the file and into a buffer.
|
||||
*
|
||||
* @param {Typed array | C pointer} buffer This buffer will be
|
||||
* modified by another thread. Using this buffer before the |read|
|
||||
* operation has completed is a BAD IDEA.
|
||||
* @param {JSON} options
|
||||
*
|
||||
* @return {promise}
|
||||
* @resolves {number} The number of bytes effectively read.
|
||||
* @rejects {OS.File.Error}
|
||||
*/
|
||||
readTo: function readTo(buffer, options = {}) {
|
||||
// If |buffer| is a typed array and there is no |bytes| options, we
|
||||
// need to extract the |byteLength| now, as it will be lost by
|
||||
// communication.
|
||||
// Options might be a nullish value, so better check for that before using
|
||||
// the |in| operator.
|
||||
if (isTypedArray(buffer) && !(options && "bytes" in options)) {
|
||||
// Preserve reference to option |outExecutionDuration|, if it is passed.
|
||||
options = clone(options, ["outExecutionDuration"]);
|
||||
options.bytes = buffer.byteLength;
|
||||
}
|
||||
// Note: Type.void_t.out_ptr.toMsg ensures that
|
||||
// - the buffer is effectively shared (not neutered) between both
|
||||
// threads;
|
||||
// - we take care of any |byteOffset|.
|
||||
return Scheduler.post("File_prototype_readTo",
|
||||
[this._fdmsg,
|
||||
Type.void_t.out_ptr.toMsg(buffer),
|
||||
options],
|
||||
buffer/*Ensure that |buffer| is not gc-ed*/);
|
||||
},
|
||||
/**
|
||||
* Write bytes from a buffer to this file.
|
||||
*
|
||||
|
@ -52,43 +52,22 @@ AbstractFile.prototype = {
|
||||
/**
|
||||
* Read bytes from this file to a new buffer.
|
||||
*
|
||||
* @param {number=} bytes If unspecified, read all the remaining bytes from
|
||||
* this file. If specified, read |bytes| bytes, or less if the file does notclone
|
||||
* contain that many bytes.
|
||||
* @param {number=} maybeBytes (deprecated, please use options.bytes)
|
||||
* @param {JSON} options
|
||||
* @return {Uint8Array} An array containing the bytes read.
|
||||
*/
|
||||
read: function read(bytes, options = {}) {
|
||||
options = clone(options);
|
||||
options.bytes = bytes == null ? this.stat().size : bytes;
|
||||
let buffer = new Uint8Array(options.bytes);
|
||||
let size = this.readTo(buffer, options);
|
||||
if (size == options.bytes) {
|
||||
return buffer;
|
||||
read: function read(maybeBytes, options = {}) {
|
||||
if (typeof maybeBytes === "object") {
|
||||
// Caller has skipped `maybeBytes` and provided an options object.
|
||||
options = clone(maybeBytes);
|
||||
maybeBytes = null;
|
||||
} else {
|
||||
return buffer.subarray(0, size);
|
||||
options = clone(options || {});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Read bytes from this file to an existing buffer.
|
||||
*
|
||||
* Note that, by default, this function may perform several I/O
|
||||
* operations to ensure that the buffer is as full as possible.
|
||||
*
|
||||
* @param {Typed Array | C pointer} buffer The buffer in which to
|
||||
* store the bytes. The buffer must be large enough to
|
||||
* accomodate |bytes| bytes.
|
||||
* @param {*=} options Optionally, an object that may contain the
|
||||
* following fields:
|
||||
* - {number} bytes The number of |bytes| to write from the buffer. If
|
||||
* unspecified, this is |buffer.byteLength|. Note that |bytes| is required
|
||||
* if |buffer| is a C pointer.
|
||||
*
|
||||
* @return {number} The number of bytes actually read, which may be
|
||||
* less than |bytes| if the file did not contain that many bytes left.
|
||||
*/
|
||||
readTo: function readTo(buffer, options = {}) {
|
||||
if(!("bytes" in options)) {
|
||||
options.bytes = maybeBytes == null ? this.stat().size : maybeBytes;
|
||||
}
|
||||
let buffer = new Uint8Array(options.bytes);
|
||||
let {ptr, bytes} = SharedAll.normalizeToPointer(buffer, options.bytes);
|
||||
let pos = 0;
|
||||
while (pos < bytes) {
|
||||
@ -99,8 +78,11 @@ AbstractFile.prototype = {
|
||||
pos += chunkSize;
|
||||
ptr = SharedAll.offsetBy(ptr, chunkSize);
|
||||
}
|
||||
|
||||
return pos;
|
||||
if (pos == options.bytes) {
|
||||
return buffer;
|
||||
} else {
|
||||
return buffer.subarray(0, pos);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -152,7 +152,6 @@ let test = maketest("Main", function main(test) {
|
||||
yield test_stat();
|
||||
yield test_debug();
|
||||
yield test_info_features_detect();
|
||||
yield test_read_write();
|
||||
yield test_position();
|
||||
yield test_iter();
|
||||
yield test_exists();
|
||||
@ -220,62 +219,6 @@ let test_info_features_detect = maketest("features_detect", function features_de
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test OS.File.prototype.{read, readTo, write}
|
||||
*/
|
||||
let test_read_write = maketest("read_write", function read_write(test) {
|
||||
return Task.spawn(function() {
|
||||
// Test readTo/write
|
||||
let currentDir = yield OS.File.getCurrentDirectory();
|
||||
let pathSource = OS.Path.join(currentDir, EXISTING_FILE);
|
||||
let pathDest = OS.Path.join(OS.Constants.Path.tmpDir,
|
||||
"osfile async test.tmp");
|
||||
|
||||
let fileSource = yield OS.File.open(pathSource);
|
||||
test.info("Input file opened");
|
||||
let fileDest = yield OS.File.open(pathDest,
|
||||
{ truncate: true, read: true, write: true});
|
||||
test.info("Output file opened");
|
||||
|
||||
let stat = yield fileSource.stat();
|
||||
test.info("Input stat worked");
|
||||
let size = stat.size;
|
||||
let array = new Uint8Array(size);
|
||||
|
||||
try {
|
||||
test.info("Now calling readTo");
|
||||
let readLength = yield fileSource.readTo(array);
|
||||
test.info("ReadTo worked");
|
||||
test.is(readLength, size, "ReadTo got all bytes");
|
||||
let writeLength = yield fileDest.write(array);
|
||||
test.info("Write worked");
|
||||
test.is(writeLength, size, "Write wrote all bytes");
|
||||
|
||||
// Test read
|
||||
yield fileSource.setPosition(0);
|
||||
let readAllResult = yield fileSource.read();
|
||||
test.info("ReadAll worked");
|
||||
test.is(readAllResult.length, size, "ReadAll read all bytes");
|
||||
test.is(Array.prototype.join.call(readAllResult),
|
||||
Array.prototype.join.call(array),
|
||||
"ReadAll result is correct");
|
||||
} finally {
|
||||
// Close stuff
|
||||
yield fileSource.close();
|
||||
yield fileDest.close();
|
||||
test.info("Files are closed");
|
||||
}
|
||||
|
||||
stat = yield OS.File.stat(pathDest);
|
||||
test.is(stat.size, size, "Both files have the same size");
|
||||
yield reference_compare_files(pathSource, pathDest, test);
|
||||
|
||||
// Cleanup.
|
||||
OS.File.remove(pathDest);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Test file.{getPosition, setPosition}
|
||||
*/
|
||||
@ -284,13 +227,8 @@ let test_position = maketest("position", function position(test) {
|
||||
let file = yield OS.File.open(EXISTING_FILE);
|
||||
|
||||
try {
|
||||
let stat = yield file.stat();
|
||||
test.info("Obtained file length");
|
||||
|
||||
let view = new Uint8Array(stat.size);
|
||||
yield file.readTo(view);
|
||||
let view = yield file.read();
|
||||
test.info("First batch of content read");
|
||||
|
||||
let CHUNK_SIZE = 178;// An arbitrary number of bytes to read from the file
|
||||
let pos = yield file.getPosition();
|
||||
test.info("Obtained position");
|
||||
@ -299,8 +237,7 @@ let test_position = maketest("position", function position(test) {
|
||||
test.info("Changed position");
|
||||
test.is(pos, view.byteLength - CHUNK_SIZE, "setPosition returned the correct position");
|
||||
|
||||
let view2 = new Uint8Array(CHUNK_SIZE);
|
||||
yield file.readTo(view2);
|
||||
let view2 = yield file.read();
|
||||
test.info("Read the end of the file");
|
||||
for (let i = 0; i < CHUNK_SIZE; ++i) {
|
||||
if (view2[i] != view[i + view.byteLength - CHUNK_SIZE]) {
|
||||
|
@ -27,7 +27,6 @@ self.onmessage = function onmessage_start(msg) {
|
||||
test_open_non_existing_file();
|
||||
test_flush_open_file();
|
||||
test_copy_existing_file();
|
||||
test_readall_writeall_file();
|
||||
test_position();
|
||||
test_move_file();
|
||||
test_iter_dir();
|
||||
@ -184,190 +183,6 @@ function compare_files(test, sourcePath, destPath, prefix)
|
||||
info(test + ": Comparison complete");
|
||||
}
|
||||
|
||||
function test_readall_writeall_file()
|
||||
{
|
||||
let src_file_name =
|
||||
OS.Path.join("chrome", "toolkit", "components", "osfile", "tests", "mochi",
|
||||
"worker_test_osfile_front.js");
|
||||
let tmp_file_name =
|
||||
OS.Path.join(OS.Constants.Path.tmpDir, "test_osfile_front.tmp");
|
||||
info("Starting test_readall_writeall_file");
|
||||
|
||||
// read, ArrayBuffer
|
||||
|
||||
let source = OS.File.open(src_file_name);
|
||||
let dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
let size = source.stat().size;
|
||||
|
||||
let buf = new Uint8Array(size);
|
||||
let readResult = source.readTo(buf);
|
||||
is(readResult, size, "test_readall_writeall_file: read the right number of bytes");
|
||||
|
||||
dest.write(buf);
|
||||
|
||||
info("test_readall_writeall_file: copy complete (manual allocation)");
|
||||
source.close();
|
||||
dest.close();
|
||||
|
||||
compare_files("test_readall_writeall_file (manual allocation)", src_file_name, tmp_file_name);
|
||||
OS.File.remove(tmp_file_name);
|
||||
|
||||
// read, C buffer
|
||||
source = OS.File.open(src_file_name);
|
||||
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
buf = new ArrayBuffer(size);
|
||||
let ptr = OS.Shared.Type.voidptr_t.implementation(buf);
|
||||
readResult = source.readTo(ptr, {bytes: size});
|
||||
is(readResult, size, "test_readall_writeall_file: read the right number of bytes (C buffer)");
|
||||
|
||||
dest.write(ptr, {bytes: size});
|
||||
|
||||
info("test_readall_writeall_file: copy complete (C buffer)");
|
||||
source.close();
|
||||
dest.close();
|
||||
|
||||
compare_files("test_readall_writeall_file (C buffer)", src_file_name, tmp_file_name);
|
||||
OS.File.remove(tmp_file_name);
|
||||
|
||||
// read/write, C buffer, missing |bytes| option
|
||||
source = OS.File.open(src_file_name);
|
||||
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
let exn = should_throw(function() { source.readTo(ptr); });
|
||||
ok(exn != null && exn instanceof TypeError, "test_readall_writeall_file: read with C pointer and without bytes fails with the correct error");
|
||||
exn = should_throw(function() { dest.write(ptr); });
|
||||
ok(exn != null && exn instanceof TypeError, "test_readall_writeall_file: write with C pointer and without bytes fails with the correct error");
|
||||
|
||||
source.close();
|
||||
dest.close();
|
||||
|
||||
// readTo, ArrayBuffer + offset
|
||||
let OFFSET = 12;
|
||||
let LEFT = size - OFFSET;
|
||||
buf = new ArrayBuffer(size);
|
||||
let offset_view = new Uint8Array(buf, OFFSET);
|
||||
source = OS.File.open(src_file_name);
|
||||
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
|
||||
readResult = source.readTo(offset_view);
|
||||
is(readResult, LEFT, "test_readall_writeall_file: read the right number of bytes (with offset)");
|
||||
|
||||
dest.write(offset_view);
|
||||
is(dest.stat().size, LEFT, "test_readall_writeall_file: wrote the right number of bytes (with offset)");
|
||||
|
||||
info("test_readall_writeall_file: copy complete (with offset)");
|
||||
source.close();
|
||||
dest.close();
|
||||
|
||||
compare_files("test_readall_writeall_file (with offset)", src_file_name, tmp_file_name, LEFT);
|
||||
OS.File.remove(tmp_file_name);
|
||||
|
||||
// read
|
||||
buf = new Uint8Array(size);
|
||||
source = OS.File.open(src_file_name);
|
||||
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
|
||||
readResult = source.read();
|
||||
is(readResult.length, size, "test_readall_writeall_file: read the right number of bytes (auto allocation)");
|
||||
|
||||
dest.write(readResult);
|
||||
|
||||
info("test_readall_writeall_file: copy complete (auto allocation)");
|
||||
source.close();
|
||||
dest.close();
|
||||
|
||||
compare_files("test_readall_writeall_file (auto allocation)", src_file_name, tmp_file_name);
|
||||
OS.File.remove(tmp_file_name);
|
||||
|
||||
// File.readAll
|
||||
readResult = OS.File.read(src_file_name);
|
||||
is(readResult.length, size, "test_readall_writeall_file: read the right number of bytes (OS.File.readAll)");
|
||||
|
||||
// File.writeAtomic on top of nothing
|
||||
OS.File.writeAtomic(tmp_file_name, readResult,
|
||||
{tmpPath: tmp_file_name + ".tmp"});
|
||||
try {
|
||||
let stat = OS.File.stat(tmp_file_name);
|
||||
info("readAll + writeAtomic created a file");
|
||||
is(stat.size, size, "readAll + writeAtomic created a file of the right size");
|
||||
} catch (x) {
|
||||
ok(false, "readAll + writeAtomic somehow failed");
|
||||
if(x.becauseNoSuchFile) {
|
||||
ok(false, "readAll + writeAtomic did not create file");
|
||||
}
|
||||
}
|
||||
compare_files("test_readall_writeall_file (OS.File.readAll + writeAtomic)",
|
||||
src_file_name, tmp_file_name);
|
||||
exn = null;
|
||||
try {
|
||||
let stat = OS.File.stat(tmp_file_name + ".tmp");
|
||||
} catch (x) {
|
||||
exn = x;
|
||||
}
|
||||
ok(!!exn, "readAll + writeAtomic cleaned up after itself");
|
||||
|
||||
// File.writeAtomic on top of existing file
|
||||
// Remove content and set arbitrary size, to avoid potential false negatives
|
||||
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
dest.setPosition(1234);
|
||||
dest.close();
|
||||
|
||||
OS.File.writeAtomic(tmp_file_name, readResult,
|
||||
{tmpPath: tmp_file_name + ".tmp"});
|
||||
compare_files("test_readall_writeall_file (OS.File.readAll + writeAtomic 2)",
|
||||
src_file_name, tmp_file_name);
|
||||
|
||||
// File.writeAtomic on top of existing file but without overwritten the file
|
||||
exn = null;
|
||||
try {
|
||||
let view = new Uint8Array(readResult.buffer, 10, 200);
|
||||
OS.File.writeAtomic(tmp_file_name, view,
|
||||
{ tmpPath: tmp_file_name + ".tmp", noOverwrite: true});
|
||||
} catch (x) {
|
||||
exn = x;
|
||||
}
|
||||
ok(exn && exn instanceof OS.File.Error && exn.becauseExists, "writeAtomic fails if file already exists with noOverwrite option");
|
||||
// Check file was not overwritten.
|
||||
compare_files("test_readall_writeall_file (OS.File.readAll + writeAtomic check file was not overwritten)",
|
||||
src_file_name, tmp_file_name);
|
||||
|
||||
// Ensure that File.writeAtomic fails if no temporary file name is provided
|
||||
// (FIXME: Remove this test as part of bug 793660)
|
||||
|
||||
exn = null;
|
||||
try {
|
||||
OS.File.writeAtomic(tmp_file_name, readResult.buffer,
|
||||
{bytes: readResult.length});
|
||||
} catch (x) {
|
||||
exn = x;
|
||||
}
|
||||
ok(!!exn && exn instanceof TypeError, "writeAtomic fails if tmpPath is not provided");
|
||||
|
||||
// Check that writeAtomic fails when destination path is undefined
|
||||
exn = null;
|
||||
try {
|
||||
let path = undefined;
|
||||
let options = {tmpPath: tmp_file_name};
|
||||
OS.File.writeAtomic(path, readResult.buffer, options);
|
||||
} catch (x) {
|
||||
exn = x;
|
||||
}
|
||||
ok(!!exn && exn instanceof TypeError, "writeAtomic fails if path is undefined");
|
||||
|
||||
// Check that writeAtomic fails when destination path is an empty string
|
||||
exn = null;
|
||||
try {
|
||||
let path = "";
|
||||
let options = {tmpPath: tmp_file_name};
|
||||
OS.File.writeAtomic(path, readResult.buffer, options);
|
||||
} catch (x) {
|
||||
exn = x;
|
||||
}
|
||||
ok(!!exn && exn instanceof TypeError, "writeAtomic fails if path is an empty string");
|
||||
|
||||
// Cleanup.
|
||||
OS.File.remove(tmp_file_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that copying a file using |copy| works.
|
||||
*/
|
||||
|
@ -9,7 +9,7 @@ function run_test() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure that {bytes:} in options to |readTo| and |write| are correctly
|
||||
* Test to ensure that {bytes:} in options to |write| is correctly
|
||||
* preserved.
|
||||
*/
|
||||
add_task(function* test_bytes() {
|
||||
@ -23,12 +23,7 @@ add_task(function* test_bytes() {
|
||||
yield file.write(new Uint8Array(2048), {bytes: 1024});
|
||||
do_check_eq((yield file.stat()).size, 1024);
|
||||
|
||||
// 2. Test same for |readTo|.
|
||||
yield file.setPosition(0, OS.File.POS_START);
|
||||
let read = yield file.readTo(new Uint8Array(1024), {bytes: 512});
|
||||
do_check_eq(read, 512);
|
||||
|
||||
// 3. Test that passing nullish values for |options| still works.
|
||||
// 2. Test that passing nullish values for |options| still works.
|
||||
yield file.setPosition(0, OS.File.POS_END);
|
||||
yield file.write(new Uint8Array(1024), null);
|
||||
yield file.write(new Uint8Array(1024), undefined);
|
||||
|
@ -116,7 +116,15 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
* @return {Array}
|
||||
*/
|
||||
get docShells() {
|
||||
let docShellsEnum = this.tabActor.originalDocShell.getDocShellEnumerator(
|
||||
let originalDocShell;
|
||||
|
||||
if (this.tabActor.isRootActor) {
|
||||
originalDocShell = this.tabActor.docShell;
|
||||
} else {
|
||||
originalDocShell = this.tabActor.originalDocShell;
|
||||
}
|
||||
|
||||
let docShellsEnum = originalDocShell.getDocShellEnumerator(
|
||||
Ci.nsIDocShellTreeItem.typeAll,
|
||||
Ci.nsIDocShell.ENUMERATE_FORWARDS
|
||||
);
|
||||
|
@ -955,6 +955,12 @@ CycleCollectedJSRuntime::SetPendingException(nsIException* aException)
|
||||
mPendingException = aException;
|
||||
}
|
||||
|
||||
nsTArray<nsRefPtr<nsIRunnable>>&
|
||||
CycleCollectedJSRuntime::GetPromiseMicroTaskQueue()
|
||||
{
|
||||
return mPromiseMicroTaskQueue;
|
||||
}
|
||||
|
||||
nsCycleCollectionParticipant*
|
||||
CycleCollectedJSRuntime::GCThingParticipant()
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
class nsCycleCollectionNoteRootCallback;
|
||||
class nsIException;
|
||||
class nsIRunnable;
|
||||
|
||||
namespace js {
|
||||
struct Class;
|
||||
@ -257,6 +258,8 @@ public:
|
||||
already_AddRefed<nsIException> GetPendingException() const;
|
||||
void SetPendingException(nsIException* aException);
|
||||
|
||||
nsTArray<nsRefPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
|
||||
|
||||
nsCycleCollectionParticipant* GCThingParticipant();
|
||||
nsCycleCollectionParticipant* ZoneParticipant();
|
||||
|
||||
@ -306,6 +309,8 @@ private:
|
||||
|
||||
nsCOMPtr<nsIException> mPendingException;
|
||||
|
||||
nsTArray<nsRefPtr<nsIRunnable>> mPromiseMicroTaskQueue;
|
||||
|
||||
OOMState mOutOfMemoryState;
|
||||
OOMState mLargeAllocationFailureState;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user