From c418c8786fe7112b659e8a8a76ed8623f67de202 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 23 Apr 2013 13:31:03 -0600 Subject: [PATCH 001/142] Bug 864216 - Allow generating IonScriptCounts for asm.js compiled functions, r=luke. --- js/src/ion/AsmJS.cpp | 21 ++++++++++++++++- js/src/ion/AsmJSModule.h | 17 ++++++++++++++ js/src/ion/CodeGenerator.cpp | 45 +++++++++++++++++++++++------------- js/src/ion/CodeGenerator.h | 10 ++++++++ js/src/jsdbgapi.cpp | 32 +++++++++++++++++++++++++ js/src/jsopcode.cpp | 30 ++++++++++++++---------- js/src/jsopcode.h | 9 ++++++++ 7 files changed, 135 insertions(+), 29 deletions(-) diff --git a/js/src/ion/AsmJS.cpp b/js/src/ion/AsmJS.cpp index eb553bd244ef..4108635cc5de 100644 --- a/js/src/ion/AsmJS.cpp +++ b/js/src/ion/AsmJS.cpp @@ -1321,7 +1321,9 @@ class ModuleCompiler return false; return exits_.add(p, Move(exitDescriptor), *exitIndex); } - + bool addFunctionCounts(IonScriptCounts *counts) { + return module_->addFunctionCounts(counts); + } void setSecondPassComplete() { JS_ASSERT(currentPass_ == 2); @@ -2311,6 +2313,12 @@ js::AsmJSModuleObjectToModule(JSObject *obj) return *(AsmJSModule *)obj->getReservedSlot(ASM_CODE_RESERVED_SLOT).toPrivate(); } +bool +js::IsAsmJSModuleObject(JSObject *obj) +{ + return obj->getClass() == &AsmJSModuleClass; +} + static const unsigned ASM_MODULE_FUNCTION_MODULE_OBJECT_SLOT = 0; JSObject & @@ -4431,6 +4439,12 @@ GenerateAsmJSCode(ModuleCompiler &m, ModuleCompiler::Func &func, if (!m.collectAccesses(mirGen)) return false; + ion::IonScriptCounts *counts = codegen->extractUnassociatedScriptCounts(); + if (counts && !m.addFunctionCounts(counts)) { + js_delete(counts); + return false; + } + // A single MacroAssembler is reused for all function compilations so // that there is a single linear code segment for each module. To avoid // spiking memory, a LifoAllocScope in the caller frees all MIR/LIR @@ -5564,3 +5578,8 @@ js::IsAsmJSCompilationAvailable(JSContext *cx, unsigned argc, Value *vp) return true; } +AsmJSModule::~AsmJSModule() +{ + for (size_t i = 0; i < numFunctionCounts(); i++) + js_delete(functionCounts(i)); +} diff --git a/js/src/ion/AsmJSModule.h b/js/src/ion/AsmJSModule.h index 72bf651c5c6a..69e81cc813fd 100644 --- a/js/src/ion/AsmJSModule.h +++ b/js/src/ion/AsmJSModule.h @@ -305,6 +305,7 @@ class AsmJSModule #if defined(JS_CPU_ARM) typedef Vector BoundsCheckVector; #endif + typedef Vector FunctionCountsVector; GlobalVector globals_; ExitVector exits_; @@ -334,6 +335,8 @@ class AsmJSModule PostLinkFailureInfo postLinkFailureInfo_; + FunctionCountsVector functionCounts_; + public: explicit AsmJSModule(JSContext *cx) : numGlobalVars_(0), @@ -350,6 +353,8 @@ class AsmJSModule postLinkFailureInfo_(cx) {} + ~AsmJSModule(); + void trace(JSTracer *trc) { for (unsigned i = 0; i < globals_.length(); i++) globals_[i].trace(trc); @@ -425,6 +430,9 @@ class AsmJSModule *exitIndex = unsigned(exits_.length()); return exits_.append(Exit(ffiIndex)); } + bool addFunctionCounts(ion::IonScriptCounts *counts) { + return functionCounts_.append(counts); + } bool addExportedFunction(RawFunction fun, PropertyName *maybeFieldName, MoveRef argCoercions, ReturnType returnType) @@ -468,6 +476,12 @@ class AsmJSModule const Exit &exit(unsigned i) const { return exits_[i]; } + unsigned numFunctionCounts() const { + return functionCounts_.length(); + } + ion::IonScriptCounts *functionCounts(unsigned i) { + return functionCounts_[i]; + } // An Exit holds bookkeeping information about an exit; the ExitDatum // struct overlays the actual runtime data stored in the global data @@ -661,6 +675,9 @@ class AsmJSModule extern AsmJSModule & AsmJSModuleObjectToModule(JSObject *obj); +extern bool +IsAsmJSModuleObject(JSObject *obj); + extern JSObject & AsmJSModuleObject(JSFunction *moduleFun); diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index cb55a5cb825f..dcacb933fc2c 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -127,10 +127,16 @@ MNewStringObject::templateObj() const { } CodeGenerator::CodeGenerator(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm) - : CodeGeneratorSpecific(gen, graph, masm) + : CodeGeneratorSpecific(gen, graph, masm), + unassociatedScriptCounts_(NULL) { } +CodeGenerator::~CodeGenerator() +{ + js_delete(unassociatedScriptCounts_); +} + bool CodeGenerator::visitValueToInt32(LValueToInt32 *lir) { @@ -2093,15 +2099,12 @@ CodeGenerator::maybeCreateScriptCounts() CompileInfo *outerInfo = &gen->info(); RawScript script = outerInfo->script(); - if (!script) - return NULL; - - if (cx->runtime->profilingScripts && !script->hasScriptCounts) { - if (!script->initScriptCounts(cx)) + if (cx->runtime->profilingScripts) { + if (script && !script->hasScriptCounts && !script->initScriptCounts(cx)) return NULL; } - if (!script->hasScriptCounts) + if (script && !script->hasScriptCounts) return NULL; counts = js_new(); @@ -2110,19 +2113,23 @@ CodeGenerator::maybeCreateScriptCounts() return NULL; } - script->addIonCounts(counts); + if (script) + script->addIonCounts(counts); for (size_t i = 0; i < graph.numBlocks(); i++) { MBasicBlock *block = graph.getBlock(i)->mir(); - // Find a PC offset in the outermost script to use. If this block is - // from an inlined script, find a location in the outer script to - // associate information about the inling with. - MResumePoint *resume = block->entryResumePoint(); - while (resume->caller()) - resume = resume->caller(); - uint32_t offset = resume->pc() - script->code; - JS_ASSERT(offset < script->length); + uint32_t offset = 0; + if (script) { + // Find a PC offset in the outermost script to use. If this block + // is from an inlined script, find a location in the outer script + // to associate information about the inlining with. + MResumePoint *resume = block->entryResumePoint(); + while (resume->caller()) + resume = resume->caller(); + uint32_t offset = resume->pc() - script->code; + JS_ASSERT(offset < script->length); + } if (!counts->block(i).init(block->id(), offset, block->numSuccessors())) return NULL; @@ -2130,6 +2137,12 @@ CodeGenerator::maybeCreateScriptCounts() counts->block(i).setSuccessor(j, block->getSuccessor(j)->id()); } + if (!script) { + // Compiling code for Asm.js. Leave the counts on the CodeGenerator to + // be picked up by the AsmJSModule after generation finishes. + unassociatedScriptCounts_ = counts; + } + return counts; } diff --git a/js/src/ion/CodeGenerator.h b/js/src/ion/CodeGenerator.h index 17a2b12f706b..175fe24e27c7 100644 --- a/js/src/ion/CodeGenerator.h +++ b/js/src/ion/CodeGenerator.h @@ -41,6 +41,7 @@ class CodeGenerator : public CodeGeneratorSpecific public: CodeGenerator(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm = NULL); + ~CodeGenerator(); public: bool generate(); @@ -260,6 +261,12 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitNameIC(OutOfLineUpdateCache *ool, NameIC *ic); bool visitCallsiteCloneIC(OutOfLineUpdateCache *ool, CallsiteCloneIC *ic); + IonScriptCounts *extractUnassociatedScriptCounts() { + IonScriptCounts *counts = unassociatedScriptCounts_; + unassociatedScriptCounts_ = NULL; // prevent delete in dtor + return counts; + } + private: bool addGetPropertyCache(LInstruction *ins, RegisterSet liveRegs, Register objReg, PropertyName *name, TypedOrValueRegister output, @@ -301,6 +308,9 @@ class CodeGenerator : public CodeGeneratorSpecific // Bailout if an element about to be written to is a hole. bool emitStoreHoleCheck(Register elements, const LAllocation *index, LSnapshot *snapshot); + + // Script counts created when compiling code with no associated JSScript. + IonScriptCounts *unassociatedScriptCounts_; }; } // namespace ion diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index a3ed445bd705..bc379ea23f83 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -36,6 +36,10 @@ #include "vm/Debugger.h" #include "vm/Shape.h" +#ifdef JS_ASMJS +#include "ion/AsmJSModule.h" +#endif + #include "jsatominlines.h" #include "jsinferinlines.h" #include "jsobjinlines.h" @@ -917,6 +921,34 @@ JS_DumpCompartmentPCCounts(JSContext *cx) if (script->hasScriptCounts && script->enclosingScriptsCompiledSuccessfully()) JS_DumpPCCounts(cx, script); } + +#if defined(JS_ASMJS) && defined(DEBUG) + for (unsigned thingKind = FINALIZE_OBJECT0; thingKind < FINALIZE_OBJECT_LIMIT; thingKind++) { + for (CellIter i(cx->zone(), (AllocKind) thingKind); !i.done(); i.next()) { + JSObject *obj = i.get(); + if (obj->compartment() != cx->compartment) + continue; + + if (IsAsmJSModuleObject(obj)) { + AsmJSModule &module = AsmJSModuleObjectToModule(obj); + + Sprinter sprinter(cx); + if (!sprinter.init()) + return; + + fprintf(stdout, "--- Asm.js Module ---\n"); + + for (size_t i = 0; i < module.numFunctionCounts(); i++) { + ion::IonScriptCounts *counts = module.functionCounts(i); + DumpIonScriptCounts(&sprinter, counts); + } + + fputs(sprinter.string(), stdout); + fprintf(stdout, "--- END Asm.js Module ---\n"); + } + } + } +#endif } JS_PUBLIC_API(JSObject *) diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 2e7665481112..699c49968e52 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -268,6 +268,23 @@ PCCounts::countName(JSOp op, size_t which) #ifdef DEBUG +void +js::DumpIonScriptCounts(Sprinter *sp, ion::IonScriptCounts *ionCounts) +{ + Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks()); + for (size_t i = 0; i < ionCounts->numBlocks(); i++) { + const ion::IonBlockCounts &block = ionCounts->block(i); + if (block.hitCount() < 10) + continue; + Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset()); + for (size_t j = 0; j < block.numSuccessors(); j++) + Sprint(sp, " -> #%lu", block.successor(j)); + Sprint(sp, " :: %llu hits %u instruction bytes %u spill bytes\n", + block.hitCount(), block.instructionBytes(), block.spillBytes()); + Sprint(sp, "%s\n", block.code()); + } +} + void js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp) { @@ -305,18 +322,7 @@ js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp) ion::IonScriptCounts *ionCounts = script->getIonCounts(); while (ionCounts) { - Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks()); - for (size_t i = 0; i < ionCounts->numBlocks(); i++) { - const ion::IonBlockCounts &block = ionCounts->block(i); - if (block.hitCount() < 10) - continue; - Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset()); - for (size_t j = 0; j < block.numSuccessors(); j++) - Sprint(sp, " -> #%lu", block.successor(j)); - Sprint(sp, " :: %llu hits %u instruction bytes %u spill bytes\n", - block.hitCount(), block.instructionBytes(), block.spillBytes()); - Sprint(sp, "%s\n", block.code()); - } + DumpIonScriptCounts(sp, ionCounts); ionCounts = ionCounts->previous(); } } diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index f0ec5c580cb3..66f6d4a1c7ec 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -632,6 +632,15 @@ js_Disassemble1(JSContext *cx, JS::Handle script, jsbytecode *pc, uns void js_DumpPCCounts(JSContext *cx, JS::Handle script, js::Sprinter *sp); + +#ifdef JS_ION +namespace js { +namespace ion { struct IonScriptCounts; } +void +DumpIonScriptCounts(js::Sprinter *sp, ion::IonScriptCounts *ionCounts); +} +#endif + #endif #endif /* jsopcode_h___ */ From 108e062a3ee0fe9ef2bbbc5f4ef26739171b04de Mon Sep 17 00:00:00 2001 From: Benjamin Smedberg Date: Tue, 23 Apr 2013 16:02:12 -0400 Subject: [PATCH 002/142] Bug 418615 - Neuter the code which tries to reframe existing plugin instances when navigator.plugins.refresh(true) is called. Instead, only scan for new plugins, unload unused plugins. The DOM code will continue to refresh the current page which calls navigator.plugins.refresh(true). r=jschoenick sr=bz --- docshell/base/nsWebNavigationInfo.cpp | 2 +- dom/base/nsPluginArray.cpp | 2 +- dom/plugins/base/nsIPluginHost.idl | 7 +- dom/plugins/base/nsNPAPIPlugin.cpp | 2 +- dom/plugins/base/nsPluginHost.cpp | 100 ++---------------- dom/plugins/base/nsPluginHost.h | 2 +- dom/plugins/test/unit/head_plugins.js | 3 +- .../test/unit/test_nice_plugin_name.js | 3 - 8 files changed, 14 insertions(+), 107 deletions(-) diff --git a/docshell/base/nsWebNavigationInfo.cpp b/docshell/base/nsWebNavigationInfo.cpp index 6be73dce4b7b..4ff477fbd652 100644 --- a/docshell/base/nsWebNavigationInfo.cpp +++ b/docshell/base/nsWebNavigationInfo.cpp @@ -57,7 +57,7 @@ nsWebNavigationInfo::IsTypeSupported(const nsACString& aType, if (pluginHost) { // false will ensure that currently running plugins will not // be shut down - rv = pluginHost->ReloadPlugins(false); + rv = pluginHost->ReloadPlugins(); if (NS_SUCCEEDED(rv)) { // OK, we reloaded plugins and there were new ones // (otherwise NS_ERROR_PLUGINS_PLUGINSNOTCHANGED would have diff --git a/dom/base/nsPluginArray.cpp b/dom/base/nsPluginArray.cpp index acda27a29b12..23a2537b395a 100644 --- a/dom/base/nsPluginArray.cpp +++ b/dom/base/nsPluginArray.cpp @@ -211,7 +211,7 @@ nsPluginArray::Refresh(bool aReloadDocuments) if(mPluginHost) { res = GetLength(¤tPluginCount); NS_ENSURE_SUCCESS(res, res); - nsresult reloadResult = mPluginHost->ReloadPlugins(aReloadDocuments); + nsresult reloadResult = mPluginHost->ReloadPlugins(); // currentPluginCount is as reported by nsPluginHost. mPluginCount is // essentially a cache of this value, and may be out of date. pluginsNotChanged = (reloadResult == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED && diff --git a/dom/plugins/base/nsIPluginHost.idl b/dom/plugins/base/nsIPluginHost.idl index b79bcab4c0db..5cc165f6b3b9 100644 --- a/dom/plugins/base/nsIPluginHost.idl +++ b/dom/plugins/base/nsIPluginHost.idl @@ -20,17 +20,14 @@ interface nsIPluginPlayPreviewInfo : nsISupports readonly attribute AUTF8String redirectURL; }; -[scriptable, uuid(67ebff01-0dce-48f7-b6a5-6235fc78382b)] +[scriptable, uuid(15f97490-7bdf-4947-885c-9258072af878)] interface nsIPluginHost : nsISupports { /** * Causes the plugins directory to be searched again for new plugin * libraries. - * - * @param reloadPages - indicates whether currently visible pages should - * also be reloaded */ - void reloadPlugins(in boolean reloadPages); + void reloadPlugins(); void getPluginTags([optional] out unsigned long aPluginCount, [retval, array, size_is(aPluginCount)] out nsIPluginTag aResults); diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index 563a1a5fe393..7b087f122ffc 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -1145,7 +1145,7 @@ _reloadplugins(NPBool reloadPages) if (!pluginHost) return; - pluginHost->ReloadPlugins(reloadPages); + pluginHost->ReloadPlugins(); } void NP_CALLBACK diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp index 69f403646aef..c1d4c28e02e2 100644 --- a/dom/plugins/base/nsPluginHost.cpp +++ b/dom/plugins/base/nsPluginHost.cpp @@ -268,52 +268,6 @@ bool ReadSectionHeader(nsPluginManifestLineReader& reader, const char *token) return false; } -// Little helper struct to asynchronously reframe any presentations (embedded) -// or reload any documents (full-page), that contained plugins -// which were shutdown as a result of a plugins.refresh(1) -class nsPluginDocReframeEvent: public nsRunnable { -public: - nsPluginDocReframeEvent(nsTArray >& aDocs) { mDocs.SwapElements(aDocs); } - - NS_DECL_NSIRUNNABLE - - nsTArray > mDocs; -}; - -NS_IMETHODIMP nsPluginDocReframeEvent::Run() { - uint32_t c = mDocs.Length(); - - // for each document (which previously had a running instance), tell - // the frame constructor to rebuild - for (uint32_t i = 0; i < c; i++) { - nsIDocument* doc = mDocs[i]; - if (doc) { - nsIPresShell *shell = doc->GetShell(); - - // if this document has a presentation shell, then it has frames and can be reframed - if (shell) { - /* A reframe will cause a fresh object frame, instance owner, and instance - * to be created. Reframing of the entire document is necessary as we may have - * recently found new plugins and we want a shot at trying to use them instead - * of leaving alternate renderings. - * We do not want to completely reload all the documents that had running plugins - * because we could possibly trigger a script to run in the unload event handler - * which may want to access our defunct plugin and cause us to crash. - */ - - shell->ReconstructFrames(); // causes reframe of document - } else { // no pres shell --> full-page plugin - - NS_NOTREACHED("all plugins should have a pres shell!"); - - } - } - } - - mDocs.Clear(); - return NS_OK; -} - static bool UnloadPluginsASAP() { return Preferences::GetBool("dom.ipc.plugins.unloadASAP", false); @@ -404,11 +358,10 @@ bool nsPluginHost::IsRunningPlugin(nsPluginTag * aPluginTag) return false; } -nsresult nsPluginHost::ReloadPlugins(bool reloadPages) +nsresult nsPluginHost::ReloadPlugins() { PLUGIN_LOG(PLUGIN_LOG_NORMAL, - ("nsPluginHost::ReloadPlugins Begin reloadPages=%d, active_instance_count=%d\n", - reloadPages, mInstances.Length())); + ("nsPluginHost::ReloadPlugins Begin\n")); nsresult rv = NS_OK; @@ -432,14 +385,6 @@ nsresult nsPluginHost::ReloadPlugins(bool reloadPages) if (!pluginschanged) return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED; - nsTArray > instsToReload; - if (reloadPages) { - - // Then stop any running plugin instances but hold on to the documents in the array - // We are going to need to restart the instances in these documents later - DestroyRunningInstances(&instsToReload, nullptr); - } - // shutdown plugins and kill the list if there are no running plugins nsRefPtr prev; nsRefPtr next; @@ -473,18 +418,8 @@ nsresult nsPluginHost::ReloadPlugins(bool reloadPages) // load them again rv = LoadPlugins(); - // If we have shut down any plugin instances, we've now got to restart them. - // Post an event to do the rest as we are going to be destroying the frame tree and we also want - // any posted unload events to finish - if (reloadPages && !instsToReload.IsEmpty()){ - nsCOMPtr ev = new nsPluginDocReframeEvent(instsToReload); - if (ev) - NS_DispatchToCurrentThread(ev); - } - PLUGIN_LOG(PLUGIN_LOG_NORMAL, - ("nsPluginHost::ReloadPlugins End active_instance_count=%d\n", - mInstances.Length())); + ("nsPluginHost::ReloadPlugins End\n")); return rv; } @@ -794,7 +729,7 @@ nsresult nsPluginHost::UnloadPlugins() // we should call nsIPluginInstance::Stop and nsIPluginInstance::SetWindow // for those plugins who want it - DestroyRunningInstances(nullptr, nullptr); + DestroyRunningInstances(nullptr); nsPluginTag *pluginTag; for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { @@ -1027,7 +962,7 @@ nsresult nsPluginHost::SetUpPluginInstance(const char *aMimeType, mCurrentDocument = do_GetWeakReference(document); // Don't try to set up an instance again if nothing changed. - if (ReloadPlugins(false) == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) { + if (ReloadPlugins() == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) { return rv; } @@ -2397,15 +2332,6 @@ nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag) return NS_OK; } - nsTArray > instsToReload; - DestroyRunningInstances(&instsToReload, aPluginTag); - - if (!instsToReload.IsEmpty()) { - nsCOMPtr ev = new nsPluginDocReframeEvent(instsToReload); - if (ev) - NS_DispatchToCurrentThread(ev); - } - return NS_OK; } @@ -3765,8 +3691,7 @@ nsPluginHost::InstanceArray() } void -nsPluginHost::DestroyRunningInstances(nsTArray >* aReloadDocs, - nsPluginTag* aPluginTag) +nsPluginHost::DestroyRunningInstances(nsPluginTag* aPluginTag) { for (int32_t i = mInstances.Length(); i > 0; i--) { nsNPAPIPluginInstance *instance = mInstances[i - 1]; @@ -3774,19 +3699,6 @@ nsPluginHost::DestroyRunningInstances(nsTArray >* aReloadD instance->SetWindow(nullptr); instance->Stop(); - // If we've been passed an array to return, lets collect all our documents, - // removing duplicates. These will be reframed (embedded) or reloaded (full-page) later - // to kickstart our instances. - if (aReloadDocs) { - nsRefPtr owner = instance->GetOwner(); - if (owner) { - nsCOMPtr doc; - owner->GetDocument(getter_AddRefs(doc)); - if (doc && !aReloadDocs->Contains(doc)) // don't allow for duplicates - aReloadDocs->AppendElement(doc); - } - } - // Get rid of all the instances without the possibility of caching. nsPluginTag* pluginTag = TagForPlugin(instance->GetPlugin()); instance->SetWindow(nullptr); diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h index d27f8ab9ad28..d1748f30eaed 100644 --- a/dom/plugins/base/nsPluginHost.h +++ b/dom/plugins/base/nsPluginHost.h @@ -175,7 +175,7 @@ public: nsTArray< nsRefPtr > *InstanceArray(); - void DestroyRunningInstances(nsTArray >* aReloadDocs, nsPluginTag* aPluginTag); + void DestroyRunningInstances(nsPluginTag* aPluginTag); // Return the tag for |aLibrary| if found, nullptr if not. nsPluginTag* FindTagForLibrary(PRLibrary* aLibrary); diff --git a/dom/plugins/test/unit/head_plugins.js b/dom/plugins/test/unit/head_plugins.js index 8b6feeccb486..0c5e0ef33fba 100644 --- a/dom/plugins/test/unit/head_plugins.js +++ b/dom/plugins/test/unit/head_plugins.js @@ -8,7 +8,8 @@ const Ci = Components.interfaces; const gIsWindows = ("@mozilla.org/windows-registry-key;1" in Cc); const gIsOSX = ("nsILocalFileMac" in Ci); -const gIsLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc); +const gIsLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc) || + ("@mozilla.org/gio-service;1" in Cc); // Finds the test plugin library function get_test_plugin() { diff --git a/dom/plugins/test/unit/test_nice_plugin_name.js b/dom/plugins/test/unit/test_nice_plugin_name.js index e713c724dada..a71c3b268b02 100644 --- a/dom/plugins/test/unit/test_nice_plugin_name.js +++ b/dom/plugins/test/unit/test_nice_plugin_name.js @@ -60,9 +60,6 @@ let gDirSvc = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties); let gPluginHost = null; -let gIsWindows = null; -let gIsOSX = null; -let gIsLinux = null; function test_expected_permission_string(aPermString) { gPluginHost.reloadPlugins(false); From 9f2d3e54e2b865c015fe306ccc2bd39afd5399de Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Mon, 22 Apr 2013 16:43:43 -0700 Subject: [PATCH 003/142] Bug 854082 - Attribute bug 621618 assertions to the proper test. r=gfritzsche --- dom/plugins/test/mochitest/test_bug813906.html | 9 ++++++++- dom/plugins/test/mochitest/test_cocoa_focus.html | 2 -- dom/plugins/test/mochitest/test_cookies.html | 3 --- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dom/plugins/test/mochitest/test_bug813906.html b/dom/plugins/test/mochitest/test_bug813906.html index 7d5a1814b86d..8dca8db9255d 100644 --- a/dom/plugins/test/mochitest/test_bug813906.html +++ b/dom/plugins/test/mochitest/test_bug813906.html @@ -28,6 +28,9 @@ function f() { diff --git a/dom/plugins/test/mochitest/test_cocoa_focus.html b/dom/plugins/test/mochitest/test_cocoa_focus.html index 51d41a3d9634..34026fd21b14 100644 --- a/dom/plugins/test/mochitest/test_cocoa_focus.html +++ b/dom/plugins/test/mochitest/test_cocoa_focus.html @@ -6,8 +6,6 @@ + + + + + + + + + From 907389c47dc3b99a6be9684790abf7672eacfbc6 Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Wed, 17 Apr 2013 14:13:09 -0700 Subject: [PATCH 005/142] Bug 854082 - Restore SetFrame(null) call to avoid instance owners pointing to dead frames. r=bsmedberg --- layout/generic/nsObjectFrame.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/layout/generic/nsObjectFrame.cpp b/layout/generic/nsObjectFrame.cpp index ca614f90676f..3757864e468e 100644 --- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -308,6 +308,10 @@ nsObjectFrame::DestroyFrom(nsIFrame* aDestructRoot) // Tell content owner of the instance to disconnect its frame. nsCOMPtr objContent(do_QueryInterface(mContent)); NS_ASSERTION(objContent, "Why not an object loading content?"); + + if (mInstanceOwner) { + mInstanceOwner->SetFrame(nullptr); + } objContent->HasNewFrame(nullptr); if (mBackgroundSink) { From c65ba6e05b747dd2b9a11e07a1175ef151781ce0 Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Wed, 17 Apr 2013 15:11:21 -0700 Subject: [PATCH 006/142] Bug 854082 - Cleanup plugin frame ownership, prevent losing our frame due to re-entrance. r=bsmedberg --- content/base/src/nsObjectLoadingContent.cpp | 16 +++++++++------- dom/plugins/base/nsPluginInstanceOwner.cpp | 5 ++--- layout/generic/nsObjectFrame.cpp | 11 ++++++++--- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 759b0b1d9530..e24435f9ae6a 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -770,6 +770,13 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading) rv = pluginHost->InstantiatePluginInstance(mContentType.get(), mURI.get(), this, getter_AddRefs(mInstanceOwner)); + // Ensure the frame did not change during instantiation re-entry (common). + // HasNewFrame would not have mInstanceOwner yet, so the new frame would be + // dangling. (Bug 854082) + nsIFrame* frame = thisContent->GetPrimaryFrame(); + if (frame && mInstanceOwner) { + mInstanceOwner->SetFrame(static_cast(frame)); + } if (appShell) { appShell->ResumeNative(); @@ -1011,16 +1018,10 @@ nsObjectLoadingContent::HasNewFrame(nsIObjectFrame* aFrame) } // Otherwise, we're just changing frames - mInstanceOwner->SetFrame(nullptr); - // Set up relationship between instance owner and frame. nsObjectFrame *objFrame = static_cast(aFrame); mInstanceOwner->SetFrame(objFrame); - // Set up new frame to draw. - objFrame->FixupWindow(objFrame->GetContentRectRelativeToSelf().Size()); - objFrame->InvalidateFrame(); - return NS_OK; } @@ -2294,7 +2295,6 @@ nsObjectLoadingContent::PluginDestroyed() // plugins in plugin host. Invalidate instance owner / prototype but otherwise // don't take any action. TeardownProtoChain(); - mInstanceOwner->SetFrame(nullptr); mInstanceOwner->Destroy(); mInstanceOwner = nullptr; return NS_OK; @@ -2578,6 +2578,8 @@ nsObjectLoadingContent::StopPluginInstance() CloseChannel(); } + // We detach the instance owner's frame before destruction, but don't destroy + // the instance owner until the plugin is stopped. mInstanceOwner->SetFrame(nullptr); bool delayedStop = false; diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index 4f704827bc87..077ca1ca2d9b 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -2504,8 +2504,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const nsGUIEvent& anEvent) nsresult nsPluginInstanceOwner::Destroy() { - if (mObjectFrame) - mObjectFrame->SetInstanceOwner(nullptr); + SetFrame(nullptr); #ifdef XP_MACOSX RemoveFromCARefreshTimer(); @@ -3377,7 +3376,7 @@ void nsPluginInstanceOwner::SetFrame(nsObjectFrame *aFrame) } mObjectFrame->FixupWindow(mObjectFrame->GetContentRectRelativeToSelf().Size()); mObjectFrame->InvalidateFrame(); - + nsFocusManager* fm = nsFocusManager::GetFocusManager(); const nsIContent* content = aFrame->GetContent(); if (fm && content) { diff --git a/layout/generic/nsObjectFrame.cpp b/layout/generic/nsObjectFrame.cpp index 3757864e468e..396a4aa89503 100644 --- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -309,6 +309,9 @@ nsObjectFrame::DestroyFrom(nsIFrame* aDestructRoot) nsCOMPtr objContent(do_QueryInterface(mContent)); NS_ASSERTION(objContent, "Why not an object loading content?"); + // The content might not have a reference to the instance owner any longer in + // the case of re-entry during instantiation or teardown, so make sure we're + // dissociated. if (mInstanceOwner) { mInstanceOwner->SetFrame(nullptr); } @@ -318,8 +321,6 @@ nsObjectFrame::DestroyFrom(nsIFrame* aDestructRoot) mBackgroundSink->Destroy(); } - SetInstanceOwner(nullptr); - nsObjectFrameSuper::DestroyFrom(aDestructRoot); } @@ -779,6 +780,10 @@ nsObjectFrame::UnregisterPluginForGeometryUpdates() void nsObjectFrame::SetInstanceOwner(nsPluginInstanceOwner* aOwner) { + // The ownership model here is historically fuzzy. This should only be called + // by nsPluginInstanceOwner when it is given a new frame, and + // nsObjectLoadingContent should be arbitrating frame-ownership via its + // HasNewFrame callback. mInstanceOwner = aOwner; if (mInstanceOwner) { return; @@ -878,7 +883,7 @@ nsObjectFrame::DidReflow(nsPresContext* aPresContext, // The view is created hidden; once we have reflowed it and it has been // positioned then we show it. - if (aStatus != nsDidReflowStatus::FINISHED) + if (aStatus != nsDidReflowStatus::FINISHED) return rv; if (HasView()) { From 050bdecf779182adb2bf0544b1c8a13d4674679a Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Wed, 17 Apr 2013 17:11:58 -0700 Subject: [PATCH 007/142] Bug 863792 - Handle re-entry during plugin instantiation. r=josh --- content/base/src/nsObjectLoadingContent.cpp | 43 ++++++++++++++++----- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index e24435f9ae6a..831be08a7052 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -745,6 +745,8 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading) // Flush layout so that the frame is created if possible and the plugin is // initialized with the latest information. doc->FlushPendingNotifications(Flush_Layout); + // Flushing layout may have re-entered and loaded something underneath us + NS_ENSURE_TRUE(mInstantiating, NS_OK); if (!thisContent->GetPrimaryFrame()) { LOG(("OBJLC [%p]: Not instantiating plugin with no frame", this)); @@ -767,9 +769,36 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading) appShell->SuspendNative(); } + nsRefPtr newOwner; rv = pluginHost->InstantiatePluginInstance(mContentType.get(), mURI.get(), this, - getter_AddRefs(mInstanceOwner)); + getter_AddRefs(newOwner)); + + // XXX(johns): We don't suspend native inside stopping plugins... + if (appShell) { + appShell->ResumeNative(); + } + + if (!mInstantiating || NS_FAILED(rv)) { + LOG(("OBJLC [%p]: Plugin instantiation failed or re-entered, " + "killing old instance", this)); + // XXX(johns): This needs to be de-duplicated with DoStopPlugin, but we + // don't want to touch the protochain or delayed stop. + // (Bug 767635) + if (newOwner) { + nsRefPtr inst; + newOwner->GetInstance(getter_AddRefs(inst)); + newOwner->SetFrame(nullptr); + if (inst) { + pluginHost->StopPluginInstance(inst); + } + newOwner->Destroy(); + } + return NS_OK; + } + + mInstanceOwner = newOwner; + // Ensure the frame did not change during instantiation re-entry (common). // HasNewFrame would not have mInstanceOwner yet, so the new frame would be // dangling. (Bug 854082) @@ -778,14 +807,6 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading) mInstanceOwner->SetFrame(static_cast(frame)); } - if (appShell) { - appShell->ResumeNative(); - } - - if (NS_FAILED(rv)) { - return rv; - } - // Set up scripting interfaces. NotifyContentObjectWrapper(); @@ -2157,6 +2178,10 @@ nsObjectLoadingContent::UnloadObject(bool aResetState) mOriginalContentType.Truncate(); } + // InstantiatePluginInstance checks this after re-entrant calls and aborts if + // it was cleared from under it + mInstantiating = false; + mScriptRequested = false; // This call should be last as it may re-enter From 8b35537ee6141f1b4dedc02d2829e9f868ac2a6a Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Fri, 19 Apr 2013 12:01:08 -0700 Subject: [PATCH 008/142] Bug 863792 - Test. r=josh --- dom/plugins/test/mochitest/Makefile.in | 2 + .../test/mochitest/file_bug863792.html | 45 +++++++++++++++++++ .../test/mochitest/test_bug863792.html | 38 ++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 dom/plugins/test/mochitest/file_bug863792.html create mode 100644 dom/plugins/test/mochitest/test_bug863792.html diff --git a/dom/plugins/test/mochitest/Makefile.in b/dom/plugins/test/mochitest/Makefile.in index e0b3e80db663..170331b24d0a 100644 --- a/dom/plugins/test/mochitest/Makefile.in +++ b/dom/plugins/test/mochitest/Makefile.in @@ -68,6 +68,8 @@ MOCHITEST_FILES = \ test_bug813906.html \ test_bug784131.html \ test_bug854082.html \ + test_bug863792.html \ + file_bug863792.html \ test_enumerate.html \ test_npruntime_construct.html \ 307-xo-redirect.sjs \ diff --git a/dom/plugins/test/mochitest/file_bug863792.html b/dom/plugins/test/mochitest/file_bug863792.html new file mode 100644 index 000000000000..b4d3dc2e074c --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug863792.html @@ -0,0 +1,45 @@ + + + + File for Bug 863792 + + + + + + + + diff --git a/dom/plugins/test/mochitest/test_bug863792.html b/dom/plugins/test/mochitest/test_bug863792.html new file mode 100644 index 000000000000..0b0b8412a060 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug863792.html @@ -0,0 +1,38 @@ + + + + Test for Bug 863792 + + + + + + + + + From da4b69a221e991eb1ea561cfe70dff765ae4e068 Mon Sep 17 00:00:00 2001 From: Scott Johnson Date: Tue, 23 Apr 2013 15:46:08 -0500 Subject: [PATCH 009/142] Bug 857324: Make column set reflow continue without balancing rather than restarting if computed height is exceeded. [r=mats] --- layout/generic/nsColumnSetFrame.cpp | 306 ++++++++++-------- layout/generic/nsColumnSetFrame.h | 19 +- .../columns/columnfill-auto-2-ref.html | 48 +++ .../reftests/columns/columnfill-auto-2.html | 48 +++ layout/reftests/columns/reftest.list | 1 + 5 files changed, 277 insertions(+), 145 deletions(-) create mode 100644 layout/reftests/columns/columnfill-auto-2-ref.html create mode 100644 layout/reftests/columns/columnfill-auto-2.html diff --git a/layout/generic/nsColumnSetFrame.cpp b/layout/generic/nsColumnSetFrame.cpp index a0d95e6321f6..2778ed2d8383 100644 --- a/layout/generic/nsColumnSetFrame.cpp +++ b/layout/generic/nsColumnSetFrame.cpp @@ -301,6 +301,35 @@ nsColumnSetFrame::ChooseColumnStrategy(const nsHTMLReflowState& aReflowState, return config; } +bool +nsColumnSetFrame::ReflowColumns(nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aReflowStatus, + ReflowConfig& aConfig, + bool aLastColumnUnbounded, + nsCollapsingMargin* aCarriedOutBottomMargin, + ColumnBalanceData& aColData) +{ + bool feasible = ReflowChildren(aDesiredSize, aReflowState, + aReflowStatus, aConfig, aLastColumnUnbounded, + aCarriedOutBottomMargin, aColData); + + if (aColData.mHasExcessHeight) { + aConfig = ChooseColumnStrategy(aReflowState, true); + MOZ_ASSERT(aReflowState.ComputedHeight() != NS_INTRINSICSIZE, + "computed height cannot be of intrinsic size if we have " + "excess height from a previous reflow"); + + // We need to reflow our children again because our size may have changed. + // Return value doesn't matter when !mIsBalancing. + ReflowChildren(aDesiredSize, aReflowState, aReflowStatus, + aConfig, aLastColumnUnbounded, + aCarriedOutBottomMargin, aColData); + } + + return feasible; +} + // XXX copied from nsBlockFrame, should this be moved to nsContainerFrame? static void PlaceFrameView(nsIFrame* aFrame) @@ -617,14 +646,18 @@ nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize, kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); } - - if ((contentBottom > aReflowState.mComputedMaxHeight || + // XXX_jwir3: + // There are specific situations where contentBottom can exceed + // NS_INTRINSICSIZE, which causes a problem, e.g. + // in layout/generic/crashtests/479938-1.html + if (contentBottom <= NS_INTRINSICSIZE && + (contentBottom > aReflowState.mComputedMaxHeight || contentBottom > aReflowState.ComputedHeight()) && aConfig.mBalanceColCount < INT32_MAX) { - // We overflowed vertically, but have not exceeded the number - // of columns. If we're balancing, then we should try reverting - // to auto instead. - aColData.mShouldRevertToAuto = true; + // We overflowed vertically, but have not exceeded the number of + // columns. We're going to go into overflow columns now, so balancing + // no longer applies. + aColData.mHasExcessHeight = true; } if (columnCount >= aConfig.mBalanceColCount) { @@ -794,170 +827,163 @@ nsColumnSetFrame::Reflow(nsPresContext* aPresContext, bool unboundedLastColumn = config.mIsBalancing && !nextInFlow; nsCollapsingMargin carriedOutBottomMargin; ColumnBalanceData colData; - colData.mShouldRevertToAuto = false; + colData.mHasExcessHeight = false; - // This loop exists in order to try balancing initially. If the balancing - // overflows, then we want to revert to column-fill: auto. + bool feasible = ReflowColumns(aDesiredSize, aReflowState, aStatus, config, + unboundedLastColumn, &carriedOutBottomMargin, + colData); - // Our loop invariant is: colData.mShouldRevertToAuto is true if and only - // if we've reflowed our children, and during the most recent reflow of - // children, we were balancing and we overflowed in the block direction. - do { - if (colData.mShouldRevertToAuto) { - config = ChooseColumnStrategy(aReflowState, true); - config.mIsBalancing = false; - } + if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) { + nscoord availableContentHeight = GetAvailableContentHeight(aReflowState); - bool feasible = ReflowChildren(aDesiredSize, aReflowState, - aStatus, config, unboundedLastColumn, &carriedOutBottomMargin, colData); + // Termination of the algorithm below is guaranteed because + // config.mKnownFeasibleHeight - config.mKnownInfeasibleHeight decreases + // in every iteration. - if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) { - nscoord availableContentHeight = GetAvailableContentHeight(aReflowState); + // We set this flag when we detect that we may contain a frame + // that can break anywhere (thus foiling the linear decrease-by-one + // search) + bool maybeContinuousBreakingDetected = false; - // Termination of the algorithm below is guaranteed because - // config.mKnownFeasibleHeight - config.mKnownInfeasibleHeight decreases - // in every iteration. + while (!aPresContext->HasPendingInterrupt()) { + nscoord lastKnownFeasibleHeight = config.mKnownFeasibleHeight; - // We set this flag when we detect that we may contain a frame - // that can break anywhere (thus foiling the linear decrease-by-one - // search) - bool maybeContinuousBreakingDetected = false; + // Record what we learned from the last reflow + if (feasible) { + // maxHeight is feasible. Also, mLastBalanceHeight is feasible. + config.mKnownFeasibleHeight = std::min(config.mKnownFeasibleHeight, + colData.mMaxHeight); + config.mKnownFeasibleHeight = std::min(config.mKnownFeasibleHeight, + mLastBalanceHeight); - while (!aPresContext->HasPendingInterrupt()) { - nscoord lastKnownFeasibleHeight = config.mKnownFeasibleHeight; + // Furthermore, no height less than the height of the last + // column can ever be feasible. (We might be able to reduce the + // height of a non-last column by moving content to a later column, + // but we can't do that with the last column.) + if (mFrames.GetLength() == config.mBalanceColCount) { + config.mKnownInfeasibleHeight = + std::max(config.mKnownInfeasibleHeight, colData.mLastHeight - 1); + } + } else { + config.mKnownInfeasibleHeight = + std::max(config.mKnownInfeasibleHeight, mLastBalanceHeight); + // If a column didn't fit in its available height, then its current + // height must be the minimum height for unbreakable content in + // the column, and therefore no smaller height can be feasible. + config.mKnownInfeasibleHeight = + std::max(config.mKnownInfeasibleHeight, + colData.mMaxOverflowingHeight - 1); - // Record what we learned from the last reflow - if (feasible) { - // maxHeight is feasible. Also, mLastBalanceHeight is feasible. + if (unboundedLastColumn) { + // The last column is unbounded, so all content got reflowed, so the + // mColMaxHeight is feasible. config.mKnownFeasibleHeight = std::min(config.mKnownFeasibleHeight, colData.mMaxHeight); - config.mKnownFeasibleHeight = std::min(config.mKnownFeasibleHeight, - mLastBalanceHeight); - - // Furthermore, no height less than the height of the last - // column can ever be feasible. (We might be able to reduce the - // height of a non-last column by moving content to a later column, - // but we can't do that with the last column.) - if (mFrames.GetLength() == config.mBalanceColCount) { - config.mKnownInfeasibleHeight = - std::max(config.mKnownInfeasibleHeight, colData.mLastHeight - 1); - } - } else { - config.mKnownInfeasibleHeight = - std::max(config.mKnownInfeasibleHeight, mLastBalanceHeight); - // If a column didn't fit in its available height, then its current - // height must be the minimum height for unbreakable content in - // the column, and therefore no smaller height can be feasible. - config.mKnownInfeasibleHeight = - std::max(config.mKnownInfeasibleHeight, - colData.mMaxOverflowingHeight - 1); - - if (unboundedLastColumn) { - // The last column is unbounded, so all content got reflowed, so the - // mColMaxHeight is feasible. - config.mKnownFeasibleHeight = std::min(config.mKnownFeasibleHeight, - colData.mMaxHeight); - } } + } #ifdef DEBUG_roc - printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n", - config.mKnownInfeasibleHeight, config.mKnownFeasibleHeight); + printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n", + config.mKnownInfeasibleHeight, config.mKnownFeasibleHeight); #endif - if (config.mKnownInfeasibleHeight >= config.mKnownFeasibleHeight - 1) { - // config.mKnownFeasibleHeight is where we want to be - break; + if (config.mKnownInfeasibleHeight >= config.mKnownFeasibleHeight - 1) { + // config.mKnownFeasibleHeight is where we want to be + break; - } - if (config.mKnownInfeasibleHeight >= availableContentHeight) { - break; - } + } + if (config.mKnownInfeasibleHeight >= availableContentHeight) { + break; + } - if (lastKnownFeasibleHeight - config.mKnownFeasibleHeight == 1) { - // We decreased the feasible height by one twip only. This could - // indicate that there is a continuously breakable child frame - // that we are crawling through. - maybeContinuousBreakingDetected = true; - } + if (lastKnownFeasibleHeight - config.mKnownFeasibleHeight == 1) { + // We decreased the feasible height by one twip only. This could + // indicate that there is a continuously breakable child frame + // that we are crawling through. + maybeContinuousBreakingDetected = true; + } - nscoord nextGuess = - (config.mKnownFeasibleHeight + config.mKnownInfeasibleHeight)/2; - // The constant of 600 twips is arbitrary. It's about two line-heights. - if (config.mKnownFeasibleHeight - nextGuess < 600 && - !maybeContinuousBreakingDetected) { - // We're close to our target, so just try shrinking just the - // minimum amount that will cause one of our columns to break - // differently. - nextGuess = config.mKnownFeasibleHeight - 1; - } else if (unboundedLastColumn) { - // Make a guess by dividing that into N columns. Add some slop - // to try to make it on the feasible side. The constant of - // 600 twips is arbitrary. It's about two line-heights. - nextGuess = colData.mSumHeight/config.mBalanceColCount + 600; - // Sanitize it - nextGuess = clamped(nextGuess, config.mKnownInfeasibleHeight + 1, - config.mKnownFeasibleHeight - 1); - } else if (config.mKnownFeasibleHeight == NS_INTRINSICSIZE) { - // This can happen when we had a next-in-flow so we didn't - // want to do an unbounded height measuring step. Let's just increase - // from the infeasible height by some reasonable amount. - nextGuess = config.mKnownInfeasibleHeight*2 + 600; - } - // Don't bother guessing more than our height constraint. - nextGuess = std::min(availableContentHeight, nextGuess); + nscoord nextGuess = + (config.mKnownFeasibleHeight + config.mKnownInfeasibleHeight)/2; + // The constant of 600 twips is arbitrary. It's about two line-heights. + if (config.mKnownFeasibleHeight - nextGuess < 600 && + !maybeContinuousBreakingDetected) { + // We're close to our target, so just try shrinking just the + // minimum amount that will cause one of our columns to break + // differently. + nextGuess = config.mKnownFeasibleHeight - 1; + } else if (unboundedLastColumn) { + // Make a guess by dividing that into N columns. Add some slop + // to try to make it on the feasible side. The constant of + // 600 twips is arbitrary. It's about two line-heights. + nextGuess = colData.mSumHeight/config.mBalanceColCount + 600; + // Sanitize it + nextGuess = clamped(nextGuess, config.mKnownInfeasibleHeight + 1, + config.mKnownFeasibleHeight - 1); + } else if (config.mKnownFeasibleHeight == NS_INTRINSICSIZE) { + // This can happen when we had a next-in-flow so we didn't + // want to do an unbounded height measuring step. Let's just increase + // from the infeasible height by some reasonable amount. + nextGuess = config.mKnownInfeasibleHeight*2 + 600; + } + // Don't bother guessing more than our height constraint. + nextGuess = std::min(availableContentHeight, nextGuess); #ifdef DEBUG_roc - printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess); + printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess); #endif - config.mColMaxHeight = nextGuess; + config.mColMaxHeight = nextGuess; - unboundedLastColumn = false; + unboundedLastColumn = false; + AddStateBits(NS_FRAME_IS_DIRTY); + feasible = ReflowColumns(aDesiredSize, aReflowState, aStatus, config, false, + &carriedOutBottomMargin, colData); + + if (!config.mIsBalancing) { + // Looks like we had excess height when balancing, so we gave up on + // trying to balance. + break; + } + } + + if (config.mIsBalancing && !feasible && + !aPresContext->HasPendingInterrupt()) { + // We may need to reflow one more time at the feasible height to + // get a valid layout. + bool skip = false; + if (config.mKnownInfeasibleHeight >= availableContentHeight) { + config.mColMaxHeight = availableContentHeight; + if (mLastBalanceHeight == availableContentHeight) { + skip = true; + } + } else { + config.mColMaxHeight = config.mKnownFeasibleHeight; + } + if (!skip) { + // If our height is unconstrained, make sure that the last column is + // allowed to have arbitrary height here, even though we were balancing. + // Otherwise we'd have to split, and it's not clear what we'd do with + // that. AddStateBits(NS_FRAME_IS_DIRTY); - feasible = ReflowChildren(aDesiredSize, aReflowState, - aStatus, config, false, - &carriedOutBottomMargin, colData); - } - - if (!feasible && !aPresContext->HasPendingInterrupt()) { - // We may need to reflow one more time at the feasible height to - // get a valid layout. - bool skip = false; - if (config.mKnownInfeasibleHeight >= availableContentHeight) { - config.mColMaxHeight = availableContentHeight; - if (mLastBalanceHeight == availableContentHeight) { - skip = true; - } - } else { - config.mColMaxHeight = config.mKnownFeasibleHeight; - } - if (!skip) { - // If our height is unconstrained, make sure that the last column is - // allowed to have arbitrary height here, even though we were balancing. - // Otherwise we'd have to split, and it's not clear what we'd do with - // that. - AddStateBits(NS_FRAME_IS_DIRTY); - ReflowChildren(aDesiredSize, aReflowState, aStatus, config, - availableContentHeight == NS_UNCONSTRAINEDSIZE, - &carriedOutBottomMargin, colData); - } + ReflowColumns(aDesiredSize, aReflowState, aStatus, config, + availableContentHeight == NS_UNCONSTRAINEDSIZE, + &carriedOutBottomMargin, colData); } } + } - } while (colData.mShouldRevertToAuto); + if (aPresContext->HasPendingInterrupt() && + aReflowState.availableHeight == NS_UNCONSTRAINEDSIZE) { + // In this situation, we might be lying about our reflow status, because + // our last kid (the one that got interrupted) was incomplete. Fix that. + aStatus = NS_FRAME_COMPLETE; + } - if (aPresContext->HasPendingInterrupt() && - aReflowState.availableHeight == NS_UNCONSTRAINEDSIZE) { - // In this situation, we might be lying about our reflow status, because - // our last kid (the one that got interrupted) was incomplete. Fix that. - aStatus = NS_FRAME_COMPLETE; - } + FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus, false); - FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus, false); - - aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin; + aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin; NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); diff --git a/layout/generic/nsColumnSetFrame.h b/layout/generic/nsColumnSetFrame.h index 9c81517bcc57..baf00847e99c 100644 --- a/layout/generic/nsColumnSetFrame.h +++ b/layout/generic/nsColumnSetFrame.h @@ -137,13 +137,14 @@ protected: // The maximum "content height" of all columns that overflowed // their available height nscoord mMaxOverflowingHeight; - // Whether or not we should revert back to 'auto' setting for column-fill. - // This happens if we overflow our columns such that we no longer have - // enough room to keep balancing. - bool mShouldRevertToAuto; + // This flag determines whether the last reflow of children exceeded the + // computed height of the column set frame. If so, we set the height to + // this maximum allowable height, and continue reflow without balancing. + bool mHasExcessHeight; + void Reset() { mMaxHeight = mSumHeight = mLastHeight = mMaxOverflowingHeight = 0; - mShouldRevertToAuto = false; + mHasExcessHeight = false; } }; @@ -154,6 +155,14 @@ protected: */ void DrainOverflowColumns(); + bool ReflowColumns(nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aReflowStatus, + ReflowConfig& aConfig, + bool aLastColumnUnbounded, + nsCollapsingMargin* aCarriedOutBottomMargin, + ColumnBalanceData& aColData); + /** * The basic reflow strategy is to call this function repeatedly to * obtain specific parameters that determine the layout of the diff --git a/layout/reftests/columns/columnfill-auto-2-ref.html b/layout/reftests/columns/columnfill-auto-2-ref.html new file mode 100644 index 000000000000..f00c41745e0a --- /dev/null +++ b/layout/reftests/columns/columnfill-auto-2-ref.html @@ -0,0 +1,48 @@ + + + + + + + +
+
To Mrs. Aville, England St. Petersburgh, Dec. 11th, 17- You will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday; and my first task is to assure my dear sister of my welfare, and increasing confidence in the success of my undertaking. I am already far north of London; and as I walk in the streets of Petersburgh. I feel a cold northern breeze play upon my cheeks, which braces my nerves, and fills me with delight.
+
+ + diff --git a/layout/reftests/columns/columnfill-auto-2.html b/layout/reftests/columns/columnfill-auto-2.html new file mode 100644 index 000000000000..c4d3d74183e3 --- /dev/null +++ b/layout/reftests/columns/columnfill-auto-2.html @@ -0,0 +1,48 @@ + + + + + + + +
+
To Mrs. Aville, England St. Petersburgh, Dec. 11th, 17- You will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday; and my first task is to assure my dear sister of my welfare, and increasing confidence in the success of my undertaking. I am already far north of London; and as I walk in the streets of Petersburgh. I feel a cold northern breeze play upon my cheeks, which braces my nerves, and fills me with delight.
+
+ + diff --git a/layout/reftests/columns/reftest.list b/layout/reftests/columns/reftest.list index 117583277096..711f20fc0699 100644 --- a/layout/reftests/columns/reftest.list +++ b/layout/reftests/columns/reftest.list @@ -19,6 +19,7 @@ == column-box-alignment-rtl.html column-box-alignment-rtl-ref.html HTTP(..) == columnfill-balance.html columnfill-balance-ref.html HTTP(..) == columnfill-auto.html columnfill-auto-ref.html +HTTP(..) == columnfill-auto-2.html columnfill-auto-2-ref.html skip-if(B2G) == columnrule-basic.html columnrule-basic-ref.html # bug 773482 skip-if(B2G) == columnrule-complex.html columnrule-complex-ref.html # bug 773482 != columnrule-linestyles.html columnrule-linestyles-notref.html From 02364d25bf6a86718849d24e14dcf1a22dc0068e Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Mon, 8 Oct 2012 15:52:24 -0400 Subject: [PATCH 010/142] Bug 781702 - silence warning spew from nsXBLProtoImplMethod.cpp; r=bz --- content/xbl/src/nsXBLProtoImplMethod.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/xbl/src/nsXBLProtoImplMethod.cpp b/content/xbl/src/nsXBLProtoImplMethod.cpp index 9f001564e1ff..902dcc202917 100644 --- a/content/xbl/src/nsXBLProtoImplMethod.cpp +++ b/content/xbl/src/nsXBLProtoImplMethod.cpp @@ -312,7 +312,8 @@ nsXBLProtoImplAnonymousMethod::Execute(nsIContent* aBoundElement) // Make sure to do this before entering the compartment, since pushing Push() // may call JS_SaveFrameChain(), which puts us back in an unentered state. nsCxPusher pusher; - NS_ENSURE_STATE(pusher.Push(aBoundElement)); + if (!pusher.Push(aBoundElement)) + return NS_ERROR_UNEXPECTED; MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); JS::Rooted thisObject(cx, &v.toObject()); From 1275a7058a762b398a6d63fb6c72548bb52c331a Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Mon, 22 Apr 2013 21:59:51 -0400 Subject: [PATCH 011/142] Bug 864605 - Take the input chunk's volume into account in the nodes that buffer their input; r=padenot --HG-- extra : rebase_source : 7590e20cbb1ac347f3988aaa5dbc586012747221 --- content/media/AudioNodeEngine.cpp | 4 +- content/media/AudioNodeEngine.h | 6 +- content/media/webaudio/DelayNode.cpp | 2 +- .../media/webaudio/ScriptProcessorNode.cpp | 7 ++- content/media/webaudio/test/Makefile.in | 1 + .../media/webaudio/test/test_delayNode.html | 22 +++++-- .../webaudio/test/test_delayNodeWithGain.html | 62 +++++++++++++++++++ .../media/webaudio/test/test_gainNode.html | 20 ++++-- content/media/webaudio/test/webaudio.js | 2 +- 9 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 content/media/webaudio/test/test_delayNodeWithGain.html diff --git a/content/media/AudioNodeEngine.cpp b/content/media/AudioNodeEngine.cpp index 15bfcbceb6c4..6d4f3f36e7b2 100644 --- a/content/media/AudioNodeEngine.cpp +++ b/content/media/AudioNodeEngine.cpp @@ -56,9 +56,9 @@ AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE], } void -AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE], +AudioBlockCopyChannelWithScale(const float* aInput, float aScale, - float aOutput[WEBAUDIO_BLOCK_SIZE]) + float* aOutput) { if (aScale == 1.0f) { memcpy(aOutput, aInput, WEBAUDIO_BLOCK_SIZE*sizeof(float)); diff --git a/content/media/AudioNodeEngine.h b/content/media/AudioNodeEngine.h index 768f5354d519..2e4eae0393af 100644 --- a/content/media/AudioNodeEngine.h +++ b/content/media/AudioNodeEngine.h @@ -100,10 +100,12 @@ void AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE], /** * Pointwise copy-scaled operation. aScale == 1.0f should be optimized. + * + * Buffer size is implicitly assumed to be WEBAUDIO_BLOCK_SIZE. */ -void AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE], +void AudioBlockCopyChannelWithScale(const float* aInput, float aScale, - float aOutput[WEBAUDIO_BLOCK_SIZE]); + float* aOutput); /** * Vector copy-scaled operation. diff --git a/content/media/webaudio/DelayNode.cpp b/content/media/webaudio/DelayNode.cpp index 223f2002e714..4cc30dc2f4c3 100644 --- a/content/media/webaudio/DelayNode.cpp +++ b/content/media/webaudio/DelayNode.cpp @@ -200,7 +200,7 @@ public: // Write the input sample to the correct location in our buffer if (input) { - buffer[writeIndex] = input[i]; + buffer[writeIndex] = input[i] * aInput.mVolume; } // Now, determine the correct read position. We adjust the read position to be diff --git a/content/media/webaudio/ScriptProcessorNode.cpp b/content/media/webaudio/ScriptProcessorNode.cpp index 33552bbcb835..1c85d8958af8 100644 --- a/content/media/webaudio/ScriptProcessorNode.cpp +++ b/content/media/webaudio/ScriptProcessorNode.cpp @@ -201,9 +201,10 @@ public: aInput.GetDuration()); } else { mSeenNonSilenceInput = true; - PodCopy(mInputChannels[i] + mInputWriteIndex, - static_cast(aInput.mChannelData[i]), - aInput.GetDuration()); + MOZ_ASSERT(aInput.GetDuration() == WEBAUDIO_BLOCK_SIZE, "sanity check"); + AudioBlockCopyChannelWithScale(static_cast(aInput.mChannelData[i]), + aInput.mVolume, + mInputChannels[i] + mInputWriteIndex); } } mInputWriteIndex += aInput.GetDuration(); diff --git a/content/media/webaudio/test/Makefile.in b/content/media/webaudio/test/Makefile.in index 9dfb35a504ab..5b192eb66fe4 100644 --- a/content/media/webaudio/test/Makefile.in +++ b/content/media/webaudio/test/Makefile.in @@ -29,6 +29,7 @@ MOCHITEST_FILES := \ test_biquadFilterNode.html \ test_currentTime.html \ test_delayNode.html \ + test_delayNodeWithGain.html \ test_decodeAudioData.html \ test_dynamicsCompressorNode.html \ test_gainNode.html \ diff --git a/content/media/webaudio/test/test_delayNode.html b/content/media/webaudio/test/test_delayNode.html index 4f2139fca6f6..736b007b8148 100644 --- a/content/media/webaudio/test/test_delayNode.html +++ b/content/media/webaudio/test/test_delayNode.html @@ -19,6 +19,10 @@ addLoadEvent(function() { for (var i = 0; i < 2048; ++i) { buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); } + var expectedBuffer = context.createBuffer(1, 2048 * 2, context.sampleRate); + for (var i = 2048; i < 2048 * 2; ++i) { + expectedBuffer.getChannelData(0)[i] = buffer.getChannelData(0)[i - 2048]; + } var destination = context.destination; @@ -32,7 +36,9 @@ addLoadEvent(function() { source.buffer = buffer; source.connect(delay); - delay.connect(destination); + var sp = context.createScriptProcessor(2048 * 2, 1); + delay.connect(sp); + sp.connect(destination); ok(delay.delayTime, "The audioparam member must exist"); is(delay.delayTime.value, 0, "Correct initial value"); @@ -62,15 +68,19 @@ addLoadEvent(function() { }, DOMException.NOT_SUPPORTED_ERR); context.createDelay(1); // should not throw + // Delay the source stream by 2048 frames + delay.delayTime.value = 2048 / context.sampleRate; + source.start(0); - SimpleTest.executeSoon(function() { - source.stop(0); - source.disconnect(); - delay.disconnect(); + sp.onaudioprocess = function(e) { + is(e.inputBuffer.numberOfChannels, 1, "Correct input channel count"); + compareBuffers(e.inputBuffer.getChannelData(0), expectedBuffer.getChannelData(0)); + + sp.onaudioprocess = null; SpecialPowers.clearUserPref("media.webaudio.enabled"); SimpleTest.finish(); - }); + }; }); diff --git a/content/media/webaudio/test/test_delayNodeWithGain.html b/content/media/webaudio/test/test_delayNodeWithGain.html new file mode 100644 index 000000000000..9ae2a616639a --- /dev/null +++ b/content/media/webaudio/test/test_delayNodeWithGain.html @@ -0,0 +1,62 @@ + + + + Test DelayNode with a GainNode + + + + +
+
+
+
+ + diff --git a/content/media/webaudio/test/test_gainNode.html b/content/media/webaudio/test/test_gainNode.html index bdecacf6612b..c772811ce11e 100644 --- a/content/media/webaudio/test/test_gainNode.html +++ b/content/media/webaudio/test/test_gainNode.html @@ -3,6 +3,7 @@ Test GainNode + @@ -18,6 +19,10 @@ addLoadEvent(function() { for (var i = 0; i < 2048; ++i) { buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); } + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = buffer.getChannelData(0)[i] / 2; + } var destination = context.destination; @@ -31,7 +36,9 @@ addLoadEvent(function() { source.buffer = buffer; source.connect(gain); - gain.connect(destination); + var sp = context.createScriptProcessor(2048, 1); + gain.connect(sp); + sp.connect(destination); ok(gain.gain, "The audioparam member must exist"); is(gain.gain.value, 1.0, "Correct initial value"); @@ -41,14 +48,15 @@ addLoadEvent(function() { is(gain.gain.defaultValue, 1.0, "Correct default value"); source.start(0); - SimpleTest.executeSoon(function() { - source.stop(0); - source.disconnect(); - gain.disconnect(); + sp.onaudioprocess = function(e) { + is(e.inputBuffer.numberOfChannels, 1, "Correct input channel count"); + compareBuffers(e.inputBuffer.getChannelData(0), expectedBuffer.getChannelData(0)); + + sp.onaudioprocess = null; SpecialPowers.clearUserPref("media.webaudio.enabled"); SimpleTest.finish(); - }); + }; }); diff --git a/content/media/webaudio/test/webaudio.js b/content/media/webaudio/test/webaudio.js index 22c9b95f4715..fb42a455b661 100644 --- a/content/media/webaudio/test/webaudio.js +++ b/content/media/webaudio/test/webaudio.js @@ -24,7 +24,7 @@ function expectTypeError(func) { } function fuzzyCompare(a, b) { - return Math.abs(a - b) < 1e-5; + return Math.abs(a - b) < 5e-5; } function compareBuffers(buf1, buf2, From 5d8d818bacb2d94f355e1ee1044c46a27df3731a Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Mon, 22 Apr 2013 22:05:16 -0400 Subject: [PATCH 012/142] Bug 864606 - Take the gain node's input chunk volume into account when we have automation events for changing the gain value; r=padenot --HG-- extra : rebase_source : 0d8c538a22ba764559a30c09e436eaaf38ac63fd --- content/media/webaudio/GainNode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/media/webaudio/GainNode.cpp b/content/media/webaudio/GainNode.cpp index 286756edc6cf..3f155e19c8dd 100644 --- a/content/media/webaudio/GainNode.cpp +++ b/content/media/webaudio/GainNode.cpp @@ -77,7 +77,7 @@ public: float computedGain[WEBAUDIO_BLOCK_SIZE]; for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) { TrackTicks tick = aStream->GetCurrentPosition() + counter; - computedGain[counter] = mGain.GetValueAtTime(tick); + computedGain[counter] = mGain.GetValueAtTime(tick) * aInput.mVolume; } // Apply the gain to the output buffer From 12e6ca7affdb9c3f851503e03725a8bbc9d08d0d Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Mon, 22 Apr 2013 22:07:49 -0400 Subject: [PATCH 013/142] Bug 864609 - Take the panner node's input chunk volume into account; r=padenot --HG-- extra : rebase_source : 4ccebeb02cd8a52721b5bff5f5c77f9a7201312b --- content/media/webaudio/PannerNode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/media/webaudio/PannerNode.cpp b/content/media/webaudio/PannerNode.cpp index c78917939046..f7dd93255319 100644 --- a/content/media/webaudio/PannerNode.cpp +++ b/content/media/webaudio/PannerNode.cpp @@ -278,8 +278,8 @@ PannerNodeEngine::EqualPowerPanningFunction(const AudioChunk& aInput, distanceGain = (this->*mDistanceModelFunction)(distance); // Actually compute the left and right gain. - gainL = cos(0.5 * M_PI * normalizedAzimuth); - gainR = sin(0.5 * M_PI * normalizedAzimuth); + gainL = cos(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume; + gainR = sin(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume; // Compute the output. if (inputChannels == 1) { From 28ae28e917b9d3d35aad404879b146a28a750577 Mon Sep 17 00:00:00 2001 From: Chris AtLee Date: Tue, 23 Apr 2013 09:43:40 -0400 Subject: [PATCH 014/142] Bug 838321: Use b2g manifests for panda/unagi builds. r=rail --HG-- extra : rebase_source : 8b1b1275fbb23e15b77c811d7d0bbd6605ddd9dc --- b2g/config/panda/config.json | 5 +++-- b2g/config/panda/releng-pandaboard.tt | 6 ------ b2g/config/unagi/config.json | 6 ++++-- b2g/config/unagi/releng-unagi.tt | 12 +++++++++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/b2g/config/panda/config.json b/b2g/config/panda/config.json index 36a818bb52d5..d0e4e9e79a94 100644 --- a/b2g/config/panda/config.json +++ b/b2g/config/panda/config.json @@ -1,8 +1,8 @@ { - "config_version": 1, + "config_version": 2, "tooltool_manifest": "releng-pandaboard.tt", "mock_target": "mozilla-centos6-i386", - "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel"], + "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "git"], "mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]], "build_targets": ["boottarball", "systemtarball", "userdatatarball", "package-tests"], "upload_files": [ @@ -12,6 +12,7 @@ "{srcdir}/b2g/config/panda/README", "{workdir}/sources.xml" ], + "b2g_manifest": "pandaboard.xml", "gecko_l10n_root": "http://hg.mozilla.org/l10n-central", "gaia": { "vcs": "hgtool", diff --git a/b2g/config/panda/releng-pandaboard.tt b/b2g/config/panda/releng-pandaboard.tt index 690096cafe96..a4783278cadd 100644 --- a/b2g/config/panda/releng-pandaboard.tt +++ b/b2g/config/panda/releng-pandaboard.tt @@ -1,11 +1,5 @@ [ { -"size": 678265436, -"digest": "36d05d77831be476e639095c04f25557171bb61c6764b2f6a49e253471aac8855adff17989089f1dce790d7e860c91d0b1d0f268fbc8fc661fca0c83ca7d65f5", -"algorithm": "sha512", -"filename": "gonk.tar.xz" -}, -{ "size": 2116507, "digest": "be67a012963a5c162834f9fcb989bcebd2d047dcb4e17ee23031b694dcf7cdfd6d7a6545d7a1f5e7293b6d24415403972f4ea1ab8c6c78fefcabfaf3f6875214", "algorithm": "sha512", diff --git a/b2g/config/unagi/config.json b/b2g/config/unagi/config.json index 4d14a1b2bf68..fdbc60dc8a47 100644 --- a/b2g/config/unagi/config.json +++ b/b2g/config/unagi/config.json @@ -1,8 +1,8 @@ { - "config_version": 1, + "config_version": 2, "tooltool_manifest": "releng-unagi.tt", "mock_target": "mozilla-centos6-i386", - "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "java-1.6.0-openjdk-devel"], + "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "java-1.6.0-openjdk-devel", "git"], "mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]], "build_targets": [], "upload_files": [ @@ -10,6 +10,8 @@ "{objdir}/dist/b2g-*.crashreporter-symbols.zip", "{workdir}/sources.xml" ], + "b2g_manifest": "unagi.xml", + "additional_source_tarballs": ["backup-unagi.tar.bz2"], "zip_files": [ ["{workdir}/out/target/product/unagi/*.img", "out/target/product/unagi/"], ["{workdir}/boot.img", "out/target/product/unagi/"], diff --git a/b2g/config/unagi/releng-unagi.tt b/b2g/config/unagi/releng-unagi.tt index 58763d146fef..716076e947a4 100644 --- a/b2g/config/unagi/releng-unagi.tt +++ b/b2g/config/unagi/releng-unagi.tt @@ -1,9 +1,15 @@ [ { -"size": 833424196, -"digest": "f47e040bac9a0e872dc7289993093c3d1be1befbab2d7caad17645a222147398573aa563f5485ca00ccfbf8c3cefc12d09fe91bf10499baa6d373e80de6bdd70", +"size": 84128995, +"digest": "b833dae269b02fec9a0549f467a78717a7b2bf96512caafa3736efe72610b50c5d2073b68afcdb2fea0779e2007e5ec9efc25b14d94f06e194e4ac66d49c676e", "algorithm": "sha512", -"filename": "gonk.tar.xz" +"filename": "backup-unagi.tar.bz2" +}, +{ +"size": 1570553, +"digest": "ea03de74df73b05e939c314cd15c54aac7b5488a407b7cc4f5f263f3049a1f69642c567dd35c43d0bc3f0d599d0385a26ab2dd947a6b18f9044e4918b382eea7", +"algorithm": "sha512", +"filename": "Adreno200-AU_LINUX_ANDROID_ICS_CHOCO_CS.04.00.03.06.001.zip" }, { "size": 8622080, From ce05f3ebe24cdfee44a80d2d8bf595f842e9ca96 Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Tue, 23 Apr 2013 08:51:02 -0500 Subject: [PATCH 015/142] Bug 862054 - Move translateToTopLevelWindow to Util. r=bbondy --- browser/metro/base/content/Util.js | 24 +++++++++++++++++ .../contenthandlers/ContextMenuHandler.js | 27 +------------------ 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/browser/metro/base/content/Util.js b/browser/metro/base/content/Util.js index eef44e22d19c..b1491e939cea 100644 --- a/browser/metro/base/content/Util.js +++ b/browser/metro/base/content/Util.js @@ -315,6 +315,30 @@ let Util = { * Screen and layout utilities */ + /* + * translateToTopLevelWindow - Given an element potentially within + * a subframe, calculate the offsets up to the top level browser. + */ + translateToTopLevelWindow: function translateToTopLevelWindow(aElement) { + let offsetX = 0; + let offsetY = 0; + let element = aElement; + while (element && + element.ownerDocument && + element.ownerDocument.defaultView != content) { + element = element.ownerDocument.defaultView.frameElement; + let rect = element.getBoundingClientRect(); + offsetX += rect.left; + offsetY += rect.top; + } + let win = null; + if (element == aElement) + win = content; + else + win = element.contentDocument.defaultView; + return { targetWindow: win, offsetX: offsetX, offsetY: offsetY }; + }, + get displayDPI() { delete this.displayDPI; return this.displayDPI = this.getWindowUtils(window).displayDPI; diff --git a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js index 530d4fdb77ba..2b3637f03df9 100644 --- a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js +++ b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js @@ -159,31 +159,6 @@ var ContextMenuHandler = { * Utility routines */ - /* - * _translateToTopLevelWindow - Given a potential coordinate set within - * a subframe, translate up to the parent window which is what front - * end code expect. - */ - _translateToTopLevelWindow: function _translateToTopLevelWindow(aPopupNode) { - let offsetX = 0; - let offsetY = 0; - let element = aPopupNode; - while (element && - element.ownerDocument && - element.ownerDocument.defaultView != content) { - element = element.ownerDocument.defaultView.frameElement; - let rect = element.getBoundingClientRect(); - offsetX += rect.left; - offsetY += rect.top; - } - let win = null; - if (element == aPopupNode) - win = content; - else - win = element.contentDocument.defaultView; - return { targetWindow: win, offsetX: offsetX, offsetY: offsetY }; - }, - /* * _processPopupNode - Generate and send a Content:ContextMenu message * to browser detailing the underlying content types at this.popupNode. @@ -197,7 +172,7 @@ var ContextMenuHandler = { let { targetWindow: targetWindow, offsetX: offsetX, offsetY: offsetY } = - this._translateToTopLevelWindow(aPopupNode); + Util.translateToTopLevelWindow(aPopupNode); let popupNode = this.popupNode = aPopupNode; let imageUrl = ""; From 6e28ade960debd2ec170a73a50be0d5312aa5833 Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Tue, 23 Apr 2013 08:51:02 -0500 Subject: [PATCH 016/142] Bug 862054 - Get rid of ElementTouchHelper and our custom elementFromPoint function. r=mbrubeck --- .../base/content/contenthandlers/Content.js | 170 +----------------- 1 file changed, 5 insertions(+), 165 deletions(-) diff --git a/browser/metro/base/content/contenthandlers/Content.js b/browser/metro/base/content/contenthandlers/Content.js index da6e0801026c..06f66e32f0c3 100644 --- a/browser/metro/base/content/contenthandlers/Content.js +++ b/browser/metro/base/content/contenthandlers/Content.js @@ -46,161 +46,6 @@ const kReferenceDpi = 240; // standard "pixel" size used in some preferences const kStateActive = 0x00000001; // :active pseudoclass for elements -/* - * ElementTouchHelper - * - * Assists users by watching for mouse clicks in content and redirect - * them to the best found target. - */ -const ElementTouchHelper = { - get radius() { - let prefs = Services.prefs; - delete this.radius; - return this.radius = { "top": prefs.getIntPref("ui.touch.radius.topmm"), - "right": prefs.getIntPref("ui.touch.radius.rightmm"), - "bottom": prefs.getIntPref("ui.touch.radius.bottommm"), - "left": prefs.getIntPref("ui.touch.radius.leftmm") - }; - }, - - get weight() { - delete this.weight; - return this.weight = { "visited": Services.prefs.getIntPref("ui.touch.radius.visitedWeight") - }; - }, - - /* Retrieve the closest element to a point by looking at borders position */ - getClosest: function getClosest(aWindowUtils, aX, aY) { - if (!this.dpiRatio) - this.dpiRatio = aWindowUtils.displayDPI / kReferenceDpi; - - let dpiRatio = this.dpiRatio; - - let target = aWindowUtils.elementFromPoint(aX, aY, - true, /* ignore root scroll frame*/ - false); /* don't flush layout */ - - // return early if the click is just over a clickable element - if (this._isElementClickable(target)) - return target; - - let nodes = aWindowUtils.nodesFromRect(aX, aY, this.radius.top * dpiRatio, - this.radius.right * dpiRatio, - this.radius.bottom * dpiRatio, - this.radius.left * dpiRatio, true, false); - - let threshold = Number.POSITIVE_INFINITY; - for (let i = 0; i < nodes.length; i++) { - let current = nodes[i]; - if (!current.mozMatchesSelector || !this._isElementClickable(current)) - continue; - - let rect = current.getBoundingClientRect(); - let distance = this._computeDistanceFromRect(aX, aY, rect); - - // increase a little bit the weight for already visited items - if (current && current.mozMatchesSelector("*:visited")) - distance *= (this.weight.visited / 100); - - if (distance < threshold) { - target = current; - threshold = distance; - } - } - - return target; - }, - - _isElementClickable: function _isElementClickable(aElement) { - const selector = "a,:link,:visited,[role=button],button,input,select,textarea,label"; - for (let elem = aElement; elem; elem = elem.parentNode) { - if (this._hasMouseListener(elem)) - return true; - if (elem.mozMatchesSelector && elem.mozMatchesSelector(selector)) - return true; - } - return false; - }, - - _computeDistanceFromRect: function _computeDistanceFromRect(aX, aY, aRect) { - let x = 0, y = 0; - let xmost = aRect.left + aRect.width; - let ymost = aRect.top + aRect.height; - - // compute horizontal distance from left/right border depending if X is - // before/inside/after the element's rectangle - if (aRect.left < aX && aX < xmost) - x = Math.min(xmost - aX, aX - aRect.left); - else if (aX < aRect.left) - x = aRect.left - aX; - else if (aX > xmost) - x = aX - xmost; - - // compute vertical distance from top/bottom border depending if Y is - // above/inside/below the element's rectangle - if (aRect.top < aY && aY < ymost) - y = Math.min(ymost - aY, aY - aRect.top); - else if (aY < aRect.top) - y = aRect.top - aY; - if (aY > ymost) - y = aY - ymost; - - return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); - }, - - _els: Cc["@mozilla.org/eventlistenerservice;1"].getService(Ci.nsIEventListenerService), - _clickableEvents: ["mousedown", "mouseup", "click"], - _hasMouseListener: function _hasMouseListener(aElement) { - let els = this._els; - let listeners = els.getListenerInfoFor(aElement, {}); - for (let i = 0; i < listeners.length; i++) { - if (this._clickableEvents.indexOf(listeners[i].type) != -1) - return true; - } - return false; - } -}; - - -/* - * Global functions - */ - -/* - * elementFromPoint - find the closes element at a point. searches - * sub-frames. - * - * @param aX, aY browser coordinates - * @return - * element - element at the position, or null if no active browser or - * element was found. - * frameX - x position within the subframe element was found. aX if no - * sub-frame was found. - * frameY - y position within the subframe element was found. aY if no - * sub-frame was found. - */ -function elementFromPoint(aX, aY) { - // browser's elementFromPoint expect browser-relative client coordinates. - // subtract browser's scroll values to adjust - let cwu = Util.getWindowUtils(content); - let elem = ElementTouchHelper.getClosest(cwu, aX, aY); - - // step through layers of IFRAMEs and FRAMES to find innermost element - while (elem && (elem instanceof HTMLIFrameElement || - elem instanceof HTMLFrameElement)) { - // adjust client coordinates' origin to be top left of iframe viewport - let rect = elem.getBoundingClientRect(); - aX -= rect.left; - aY -= rect.top; - let windowUtils = elem.contentDocument - .defaultView - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - elem = ElementTouchHelper.getClosest(windowUtils, aX, aY); - } - return { element: elem, frameX: aX, frameY: aY }; -} - /* * getBoundingContentRect * @@ -349,8 +194,7 @@ let Content = { break; case "touchstart": - let touch = aEvent.changedTouches[0]; - this._onTouchStart(touch.clientX, touch.clientY); + this._onTouchStart(aEvent); break; } }, @@ -403,10 +247,8 @@ let Content = { * Event handlers */ - _onTouchStart: function _onTouchStart(x, y) { - let { element } = elementFromPoint(x, y); - if (!element) - return; + _onTouchStart: function _onTouchStart(aEvent) { + let element = aEvent.target; // There is no need to have a feedback for disabled element let isDisabled = element instanceof HTMLOptionElement ? @@ -419,11 +261,9 @@ let Content = { }, _onClickCapture: function _onClickCapture(aEvent) { - ContextMenuHandler.reset(); + let element = aEvent.target; - let { element: element } = elementFromPoint(aEvent.clientX, aEvent.clientY); - if (!element) - return; + ContextMenuHandler.reset(); // Only show autocomplete after the item is clicked if (!this.lastClickElement || this.lastClickElement != element) { From 61a31ce65754526b34872a5cf4947f4cc6d5f5bb Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Tue, 23 Apr 2013 08:51:03 -0500 Subject: [PATCH 017/142] Bug 862054 - Update bounds utility methods in SelectionHandler to properly calculate offsets for form inputs in sub frames. r=bbondy --- .../base/content/contenthandlers/Content.js | 5 ++-- .../contenthandlers/SelectionHandler.js | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/browser/metro/base/content/contenthandlers/Content.js b/browser/metro/base/content/contenthandlers/Content.js index 06f66e32f0c3..e7948ba921c2 100644 --- a/browser/metro/base/content/contenthandlers/Content.js +++ b/browser/metro/base/content/contenthandlers/Content.js @@ -282,9 +282,10 @@ let Content = { // A tap on a form input triggers touch input caret selection if (Util.isTextInput(element) && aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) { + let { offsetX, offsetY } = Util.translateToTopLevelWindow(element); sendAsyncMessage("Content:SelectionCaret", { - xPos: aEvent.clientX, - yPos: aEvent.clientY + xPos: aEvent.clientX + offsetX, + yPos: aEvent.clientY + offsetY }); } }, diff --git a/browser/metro/base/content/contenthandlers/SelectionHandler.js b/browser/metro/base/content/contenthandlers/SelectionHandler.js index c8d89ee56d79..0f64c19e5947 100644 --- a/browser/metro/base/content/contenthandlers/SelectionHandler.js +++ b/browser/metro/base/content/contenthandlers/SelectionHandler.js @@ -574,7 +574,8 @@ var SelectionHandler = { _restrictSelectionRectToEditBounds: function _restrictSelectionRectToEditBounds() { if (!this._targetIsEditable) return; - let bounds = this._getTargetClientRect(); + + let bounds = this._getTargetBrowserRect(); if (this._cache.start.xPos < bounds.left) this._cache.start.xPos = bounds.left; if (this._cache.end.xPos < bounds.left) @@ -794,7 +795,7 @@ var SelectionHandler = { * @return new constrained point struct */ _constrainPointWithinControl: function _cpwc(aPoint, aHalfLineHeight) { - let bounds = this._getTargetClientRect(); + let bounds = this._getTargetBrowserRect(); let point = { xPos: aPoint.xPos, yPos: aPoint.yPos }; if (point.xPos <= bounds.left) point.xPos = bounds.left + 2; @@ -815,7 +816,7 @@ var SelectionHandler = { * Works on client coordinates. */ _pointOrientationToRect: function _pointOrientationToRect(aPoint) { - let bounds = this._targetElement.getBoundingClientRect(); + let bounds = this._getTargetBrowserRect(); let result = { left: 0, right: 0, top: 0, bottom: 0 }; if (aPoint.xPos <= bounds.left) result.left = bounds.left - aPoint.xPos; @@ -1103,7 +1104,7 @@ var SelectionHandler = { // height of the target element let targetHeight = this._cache.element.bottom - this._cache.element.top; // height of the browser view. - let viewBottom = this._targetElement.ownerDocument.defaultView.innerHeight; + let viewBottom = content.innerHeight; // If the target is shorter than the new content height, we can go ahead // and center it. @@ -1294,10 +1295,27 @@ var SelectionHandler = { return seldata; }, + /* + * Returns bounds of the element relative to the inner sub frame it sits + * in. + */ _getTargetClientRect: function _getTargetClientRect() { return this._targetElement.getBoundingClientRect(); }, + /* + * Returns bounds of the element relative to the top level browser. + */ + _getTargetBrowserRect: function _getTargetBrowserRect() { + let client = this._getTargetClientRect(); + return { + left: client.left + this._contentOffset.x, + top: client.top + this._contentOffset.y, + right: client.right + this._contentOffset.x, + bottom: client.bottom + this._contentOffset.y + }; + }, + /* * Translate a top level client point to frame relative client point. */ From 0f0de846c3f2ff3f436a6d379dec83f2c0d46fd6 Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Tue, 23 Apr 2013 08:51:03 -0500 Subject: [PATCH 018/142] Bug 864533 - test helper addTab should use pageShowPromise. r=sfoster --- browser/metro/base/tests/mochitest/head.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/browser/metro/base/tests/mochitest/head.js b/browser/metro/base/tests/mochitest/head.js index bda4c8c5d8f7..63e0012e26ac 100644 --- a/browser/metro/base/tests/mochitest/head.js +++ b/browser/metro/base/tests/mochitest/head.js @@ -184,7 +184,7 @@ function addTab(aUrl) { return Task.spawn(function() { info("Opening "+aUrl+" in a new tab"); let tab = Browser.addTab(aUrl, true); - yield waitForEvent(tab.browser, "pageshow"); + yield tab.pageShowPromise; is(tab.browser.currentURI.spec, aUrl, aUrl + " is loaded"); registerCleanupFunction(function() Browser.closeTab(tab)); @@ -213,7 +213,6 @@ function addTab(aUrl) { * @returns a Promise that resolves to the received event, or to an Error */ function waitForEvent(aSubject, aEventName, aTimeoutMs) { - info("waitForEvent: on " + aSubject + " event: " + aEventName); let eventDeferred = Promise.defer(); let timeoutMs = aTimeoutMs || kDefaultWait; let timerID = setTimeout(function wfe_canceller() { From 3e3059d9612e11e00419f64397de8615fdbb2cc9 Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Tue, 23 Apr 2013 08:51:03 -0500 Subject: [PATCH 019/142] Bug 863739 - Add text input selection related mochitests. r=rsilveira --- .../metro/base/tests/mochitest/Makefile.in | 2 + .../mochitest/browser_selection_inputs.html | 8 + .../mochitest/browser_selection_inputs.js | 204 ++++++++++++++++++ browser/metro/base/tests/mochitest/head.js | 3 + 4 files changed, 217 insertions(+) create mode 100644 browser/metro/base/tests/mochitest/browser_selection_inputs.html create mode 100644 browser/metro/base/tests/mochitest/browser_selection_inputs.js diff --git a/browser/metro/base/tests/mochitest/Makefile.in b/browser/metro/base/tests/mochitest/Makefile.in index d4042de6ccbd..5615f9747872 100644 --- a/browser/metro/base/tests/mochitest/Makefile.in +++ b/browser/metro/base/tests/mochitest/Makefile.in @@ -42,6 +42,8 @@ BROWSER_TESTS += \ browser_selection_textarea.html \ browser_selection_frame_content.js \ browser_selection_frame_content.html \ + browser_selection_inputs.js \ + browser_selection_inputs.html \ $(NULL) endif diff --git a/browser/metro/base/tests/mochitest/browser_selection_inputs.html b/browser/metro/base/tests/mochitest/browser_selection_inputs.html new file mode 100644 index 000000000000..d182a291990f --- /dev/null +++ b/browser/metro/base/tests/mochitest/browser_selection_inputs.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/browser/metro/base/tests/mochitest/browser_selection_inputs.js b/browser/metro/base/tests/mochitest/browser_selection_inputs.js new file mode 100644 index 000000000000..e9b0337b1fbf --- /dev/null +++ b/browser/metro/base/tests/mochitest/browser_selection_inputs.js @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let gWindow = null; +var gInput = null; + +const kMarkerOffsetY = 12; +const kCommonWaitMs = 5000; +const kCommonPollMs = 100; + +/////////////////////////////////////////////////// +// form input tests +/////////////////////////////////////////////////// + +function setUpAndTearDown() { + emptyClipboard(); + if (gWindow) + clearSelection(gWindow); + if (gInput) + clearSelection(gInput); + yield waitForCondition(function () { + return !SelectionHelperUI.isSelectionUIVisible; + }, kCommonWaitMs, kCommonPollMs); + yield hideContextUI(); +} + +/* + 5px top margin + 25px tall text input + 300px wide +*/ + +gTests.push({ + desc: "normalize browser", + setUp: setUpAndTearDown, + tearDown: setUpAndTearDown, + run: function test() { + info(chromeRoot + "browser_selection_inputs.html"); + yield addTab(chromeRoot + "browser_selection_inputs.html"); + + yield waitForCondition(function () { + return !StartUI.isStartPageVisible; + }, 10000, 100); + + gWindow = Browser.selectedTab.browser.contentWindow; + gInput = gWindow.document.getElementById("a"); + InputSourceHelper.isPrecise = false; + }, +}); + +gTests.push({ + desc: "basic text input selection", + setUp: setUpAndTearDown, + tearDown: setUpAndTearDown, + run: function test() { + gInput.focus(); + gInput.selectionStart = gInput.selectionEnd = 0; + + let promise = waitForEvent(document, "popupshown"); + sendContextMenuClick(200, 17); + yield promise; + + checkContextUIMenuItemVisibility(["context-select", + "context-select-all"]); + + let menuItem = document.getElementById("context-select"); + ok(menuItem, "menu item exists"); + ok(!menuItem.hidden, "menu item visible"); + let popupPromise = waitForEvent(document, "popuphidden"); + EventUtils.synthesizeMouse(menuItem, 10, 10, {}, gWindow); + yield popupPromise; + + yield waitForCondition(function () { + return SelectionHelperUI.isSelectionUIVisible; + }, kCommonWaitMs, kCommonPollMs); + + is(getTrimmedSelection(gInput).toString(), "went", "selection test"); + }, +}); + +gTests.push({ + desc: "drag left to scroll", + setUp: setUpAndTearDown, + tearDown: setUpAndTearDown, + run: function test() { + gInput.selectionStart = gInput.selectionEnd = gInput.value.length; + + let promise = waitForEvent(document, "popupshown"); + sendContextMenuClick(190, 17); + yield promise; + + checkContextUIMenuItemVisibility(["context-select", + "context-select-all"]); + + let menuItem = document.getElementById("context-select"); + ok(menuItem, "menu item exists"); + ok(!menuItem.hidden, "menu item visible"); + let popupPromise = waitForEvent(document, "popuphidden"); + EventUtils.synthesizeMouse(menuItem, 10, 10, {}, gWindow); + yield popupPromise; + + yield waitForCondition(function () { + return SelectionHelperUI.isSelectionUIVisible; + }, kCommonWaitMs, kCommonPollMs); + + // check text selection + is(getTrimmedSelection(gInput).toString(), "way", "selection test"); + + // to the left + let xpos = SelectionHelperUI.startMark.xPos; + let ypos = SelectionHelperUI.startMark.yPos + 10; + var touchdrag = new TouchDragAndHold(); + yield touchdrag.start(gWindow, xpos, ypos, 10, ypos); + yield waitForCondition(function () { + return getTrimmedSelection(gInput).toString() == + "The rabbit-hole went straight on like a tunnel for some way"; + }, 6000, 2000); + touchdrag.end(); + + yield waitForCondition(function () { + return !SelectionHelperUI.hasActiveDrag; + }, kCommonWaitMs, kCommonPollMs); + yield SelectionHelperUI.pingSelectionHandler(); + }, +}); + +gTests.push({ + desc: "drag right to scroll and bug 862025", + setUp: setUpAndTearDown, + tearDown: setUpAndTearDown, + run: function test() { + gInput.selectionStart = gInput.selectionEnd = 0; + + let promise = waitForEvent(document, "popupshown"); + sendContextMenuClick(230, 17); + yield promise; + + checkContextUIMenuItemVisibility(["context-select", + "context-select-all"]); + + let menuItem = document.getElementById("context-select"); + ok(menuItem, "menu item exists"); + ok(!menuItem.hidden, "menu item visible"); + let popupPromise = waitForEvent(document, "popuphidden"); + EventUtils.synthesizeMouse(menuItem, 10, 10, {}, gWindow); + yield popupPromise; + + yield waitForCondition(function () { + return SelectionHelperUI.isSelectionUIVisible; + }, kCommonWaitMs, kCommonPollMs); + + // check text selection + is(getTrimmedSelection(gInput).toString(), "straight", "selection test"); + + // to the right + let xpos = SelectionHelperUI.endMark.xPos; + let ypos = SelectionHelperUI.endMark.yPos + 10; + var touchdrag = new TouchDragAndHold(); + yield touchdrag.start(gWindow, xpos, ypos, 510, ypos); + yield waitForCondition(function () { + return getTrimmedSelection(gInput).toString() == + "straight on like a tunnel for some way and then dipped suddenly down"; + }, 6000, 2000); + touchdrag.end(); + + yield waitForCondition(function () { + return !SelectionHelperUI.hasActiveDrag; + }, kCommonWaitMs, kCommonPollMs); + yield SelectionHelperUI.pingSelectionHandler(); + + // down - selection shouldn't change + let xpos = SelectionHelperUI.endMark.xPos; + let ypos = SelectionHelperUI.endMark.yPos + 10; + yield touchdrag.start(gWindow, xpos, ypos, xpos, ypos + 150); + yield waitForMs(2000); + touchdrag.end(); + + is(getTrimmedSelection(gInput).toString(), "straight on like a tunnel for some way and then dipped suddenly down", "selection test"); + + // left and up - selection should shrink + let xpos = SelectionHelperUI.endMark.xPos; + let ypos = SelectionHelperUI.endMark.yPos + 10; + yield touchdrag.start(gWindow, xpos, ypos, 105, 25); + yield waitForCondition(function () { + return getTrimmedSelection(gInput).toString() == + "straight on like a tunnel for"; + }, 6000, 2000); + touchdrag.end(); + }, +}); + +function test() { + if (!isLandscapeMode()) { + todo(false, "browser_selection_tests need landscape mode to run."); + return; + } + + requestLongerTimeout(3); + runTests(); +} diff --git a/browser/metro/base/tests/mochitest/head.js b/browser/metro/base/tests/mochitest/head.js index 63e0012e26ac..166afc9bee37 100644 --- a/browser/metro/base/tests/mochitest/head.js +++ b/browser/metro/base/tests/mochitest/head.js @@ -547,8 +547,11 @@ TouchDragAndHold.prototype = { _timeoutStep: 2, _numSteps: 50, _debug: false, + _win: null, callback: function callback() { + if (this._win == null) + return; if (++this._step.steps >= this._numSteps) { EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos, { type: "touchmove" }, this._win); From 4f737808a2bccfed84a5a451c73dfdf52f1ce9cd Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Tue, 23 Apr 2013 08:51:03 -0500 Subject: [PATCH 020/142] Bug 864428 - Don't call hideContextUI in startup and tear down test helpers as it can cause random test timeouts. r=bbondy --- browser/metro/base/tests/mochitest/browser_selection_basic.js | 3 ++- .../base/tests/mochitest/browser_selection_frame_content.js | 3 ++- browser/metro/base/tests/mochitest/browser_selection_inputs.js | 3 ++- .../metro/base/tests/mochitest/browser_selection_textarea.js | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/browser/metro/base/tests/mochitest/browser_selection_basic.js b/browser/metro/base/tests/mochitest/browser_selection_basic.js index f1c510a2cbf5..bd81b820ad51 100644 --- a/browser/metro/base/tests/mochitest/browser_selection_basic.js +++ b/browser/metro/base/tests/mochitest/browser_selection_basic.js @@ -25,7 +25,6 @@ function setUpAndTearDown() { yield waitForCondition(function () { return !SelectionHelperUI.isSelectionUIVisible; }, kCommonWaitMs, kCommonPollMs); - yield hideContextUI(); } gTests.push({ @@ -40,6 +39,8 @@ gTests.push({ return !StartUI.isStartPageVisible; }, 10000, 100); + yield hideContextUI(); + gWindow = Browser.selectedTab.browser.contentWindow; InputSourceHelper.isPrecise = false; }, diff --git a/browser/metro/base/tests/mochitest/browser_selection_frame_content.js b/browser/metro/base/tests/mochitest/browser_selection_frame_content.js index 52351ed71dbb..c732d95772c5 100644 --- a/browser/metro/base/tests/mochitest/browser_selection_frame_content.js +++ b/browser/metro/base/tests/mochitest/browser_selection_frame_content.js @@ -25,7 +25,6 @@ function setUpAndTearDown() { yield waitForCondition(function () { return !SelectionHelperUI.isSelectionUIVisible; }, kCommonWaitMs, kCommonPollMs); - yield hideContextUI(); } gTests.push({ @@ -40,6 +39,8 @@ gTests.push({ return !StartUI.isStartPageVisible; }, 10000, 100); + yield hideContextUI(); + gWindow = Browser.selectedTab.browser.contentWindow; gFrame = gWindow.document.getElementById("frame1"); diff --git a/browser/metro/base/tests/mochitest/browser_selection_inputs.js b/browser/metro/base/tests/mochitest/browser_selection_inputs.js index e9b0337b1fbf..8fb4b297fceb 100644 --- a/browser/metro/base/tests/mochitest/browser_selection_inputs.js +++ b/browser/metro/base/tests/mochitest/browser_selection_inputs.js @@ -25,7 +25,6 @@ function setUpAndTearDown() { yield waitForCondition(function () { return !SelectionHelperUI.isSelectionUIVisible; }, kCommonWaitMs, kCommonPollMs); - yield hideContextUI(); } /* @@ -46,6 +45,8 @@ gTests.push({ return !StartUI.isStartPageVisible; }, 10000, 100); + yield hideContextUI(); + gWindow = Browser.selectedTab.browser.contentWindow; gInput = gWindow.document.getElementById("a"); InputSourceHelper.isPrecise = false; diff --git a/browser/metro/base/tests/mochitest/browser_selection_textarea.js b/browser/metro/base/tests/mochitest/browser_selection_textarea.js index 8c8d16f8fb6c..3d21ff522237 100644 --- a/browser/metro/base/tests/mochitest/browser_selection_textarea.js +++ b/browser/metro/base/tests/mochitest/browser_selection_textarea.js @@ -25,7 +25,6 @@ function setUpAndTearDown() { yield waitForCondition(function () { return !SelectionHelperUI.isSelectionUIVisible; }, kCommonWaitMs, kCommonPollMs); - yield hideContextUI(); } gTests.push({ @@ -40,6 +39,8 @@ gTests.push({ return !StartUI.isStartPageVisible; }, 10000, 100); + yield hideContextUI(); + gWindow = Browser.selectedTab.browser.contentWindow; InputSourceHelper.isPrecise = false; }, From 7de32e5729bf7be9f1cd55c3cce85e3c7d000189 Mon Sep 17 00:00:00 2001 From: Jonathan Watt Date: Tue, 23 Apr 2013 15:04:28 +0100 Subject: [PATCH 021/142] Bug 863634 - Update the position of the thumb for when script uses .value, .valueAsNumber, .stepUp() or .stepDown(). r=mounir --- content/html/content/src/HTMLInputElement.cpp | 7 +++++++ .../input/range/input-75pct-common-ref.html | 6 ++++++ .../range/input-75pct-unthemed-common-ref.html | 6 ++++++ .../input/range/input-stepDown-unthemed.html | 17 +++++++++++++++++ .../forms/input/range/input-stepDown.html | 17 +++++++++++++++++ .../input/range/input-stepUp-unthemed.html | 17 +++++++++++++++++ .../forms/input/range/input-stepUp.html | 17 +++++++++++++++++ .../input/range/input-value-prop-unthemed.html | 17 +++++++++++++++++ .../forms/input/range/input-value-prop.html | 17 +++++++++++++++++ .../input-valueAsNumber-prop-unthemed.html | 17 +++++++++++++++++ .../input/range/input-valueAsNumber-prop.html | 17 +++++++++++++++++ layout/reftests/forms/input/range/reftest.list | 10 ++++++++++ 12 files changed, 165 insertions(+) create mode 100644 layout/reftests/forms/input/range/input-75pct-common-ref.html create mode 100644 layout/reftests/forms/input/range/input-75pct-unthemed-common-ref.html create mode 100644 layout/reftests/forms/input/range/input-stepDown-unthemed.html create mode 100644 layout/reftests/forms/input/range/input-stepDown.html create mode 100644 layout/reftests/forms/input/range/input-stepUp-unthemed.html create mode 100644 layout/reftests/forms/input/range/input-stepUp.html create mode 100644 layout/reftests/forms/input/range/input-value-prop-unthemed.html create mode 100644 layout/reftests/forms/input/range/input-value-prop.html create mode 100644 layout/reftests/forms/input/range/input-valueAsNumber-prop-unthemed.html create mode 100644 layout/reftests/forms/input/range/input-valueAsNumber-prop.html diff --git a/content/html/content/src/HTMLInputElement.cpp b/content/html/content/src/HTMLInputElement.cpp index c91c74ad060b..9f0c337b4124 100644 --- a/content/html/content/src/HTMLInputElement.cpp +++ b/content/html/content/src/HTMLInputElement.cpp @@ -1277,6 +1277,13 @@ HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv) SetValueInternal(aValue, false, true); + if (mType == NS_FORM_INPUT_RANGE) { + nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame()); + if (frame) { + frame->UpdateForValueChange(); + } + } + if (mFocusedValue.Equals(currentValue)) { GetValueInternal(mFocusedValue); } diff --git a/layout/reftests/forms/input/range/input-75pct-common-ref.html b/layout/reftests/forms/input/range/input-75pct-common-ref.html new file mode 100644 index 000000000000..929887d65a3e --- /dev/null +++ b/layout/reftests/forms/input/range/input-75pct-common-ref.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/layout/reftests/forms/input/range/input-75pct-unthemed-common-ref.html b/layout/reftests/forms/input/range/input-75pct-unthemed-common-ref.html new file mode 100644 index 000000000000..a4b73e23ad6b --- /dev/null +++ b/layout/reftests/forms/input/range/input-75pct-unthemed-common-ref.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/layout/reftests/forms/input/range/input-stepDown-unthemed.html b/layout/reftests/forms/input/range/input-stepDown-unthemed.html new file mode 100644 index 000000000000..fbddb69ada55 --- /dev/null +++ b/layout/reftests/forms/input/range/input-stepDown-unthemed.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/input-stepDown.html b/layout/reftests/forms/input/range/input-stepDown.html new file mode 100644 index 000000000000..8e63ea8b9e76 --- /dev/null +++ b/layout/reftests/forms/input/range/input-stepDown.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/input-stepUp-unthemed.html b/layout/reftests/forms/input/range/input-stepUp-unthemed.html new file mode 100644 index 000000000000..613049e9ef40 --- /dev/null +++ b/layout/reftests/forms/input/range/input-stepUp-unthemed.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/input-stepUp.html b/layout/reftests/forms/input/range/input-stepUp.html new file mode 100644 index 000000000000..11af00b36cc6 --- /dev/null +++ b/layout/reftests/forms/input/range/input-stepUp.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/input-value-prop-unthemed.html b/layout/reftests/forms/input/range/input-value-prop-unthemed.html new file mode 100644 index 000000000000..46387af2ea81 --- /dev/null +++ b/layout/reftests/forms/input/range/input-value-prop-unthemed.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/input-value-prop.html b/layout/reftests/forms/input/range/input-value-prop.html new file mode 100644 index 000000000000..66be8370458f --- /dev/null +++ b/layout/reftests/forms/input/range/input-value-prop.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/input-valueAsNumber-prop-unthemed.html b/layout/reftests/forms/input/range/input-valueAsNumber-prop-unthemed.html new file mode 100644 index 000000000000..a2a6eb6cce75 --- /dev/null +++ b/layout/reftests/forms/input/range/input-valueAsNumber-prop-unthemed.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/input-valueAsNumber-prop.html b/layout/reftests/forms/input/range/input-valueAsNumber-prop.html new file mode 100644 index 000000000000..c00fe8bf4c56 --- /dev/null +++ b/layout/reftests/forms/input/range/input-valueAsNumber-prop.html @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/layout/reftests/forms/input/range/reftest.list b/layout/reftests/forms/input/range/reftest.list index 28ef373f80f1..ffb79ecd5613 100644 --- a/layout/reftests/forms/input/range/reftest.list +++ b/layout/reftests/forms/input/range/reftest.list @@ -14,6 +14,16 @@ default-preferences pref(dom.experimental_forms_range,true) != input-range-different-fraction-of-range-unthemed-1.html input-range-different-fraction-of-range-unthemed-1-notref.html == input-range-same-fraction-of-range-unthemed-1.html input-range-same-fraction-of-range-unthemed-1-ref.html +# dynamic value changes: +== input-value-prop-unthemed.html input-75pct-unthemed-common-ref.html +== input-value-prop.html input-75pct-common-ref.html +== input-valueAsNumber-prop-unthemed.html input-75pct-unthemed-common-ref.html +== input-valueAsNumber-prop.html input-75pct-common-ref.html +== input-stepDown-unthemed.html input-75pct-unthemed-common-ref.html +== input-stepDown.html input-75pct-common-ref.html +== input-stepUp-unthemed.html input-75pct-unthemed-common-ref.html +== input-stepUp.html input-75pct-common-ref.html + # 'direction' property: == input-range-direction-unthemed-1.html input-range-direction-unthemed-1-ref.html From d6fbc55a13ec3dd6a2031ae1d6dc9006c2d04b97 Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Tue, 23 Apr 2013 16:06:17 +0200 Subject: [PATCH 022/142] Bug 748894 - Move the bookmark star button outside of the location bar. r=Mano --- browser/base/content/browser-places.js | 588 +++++++++--------- browser/base/content/browser.css | 17 +- browser/base/content/browser.js | 11 +- browser/base/content/browser.xul | 166 +++-- .../base/content/test/browser_bug432599.js | 18 +- .../base/content/test/browser_bug581253.js | 16 +- .../base/content/test/browser_bug624734.js | 3 +- browser/components/nsBrowserGlue.js | 29 +- browser/themes/linux/Toolbar-small.png | Bin 4740 -> 5429 bytes browser/themes/linux/Toolbar.png | Bin 7397 -> 8925 bytes browser/themes/linux/browser.css | 57 +- browser/themes/linux/jar.mn | 3 +- browser/themes/linux/places/pageStarred.png | Bin 767 -> 0 bytes browser/themes/linux/places/star-icons.png | Bin 0 -> 1106 bytes browser/themes/linux/places/starPage.png | Bin 723 -> 0 bytes browser/themes/osx/Toolbar-lion.png | Bin 10575 -> 8478 bytes browser/themes/osx/Toolbar-lion@2x.png | Bin 18766 -> 23908 bytes browser/themes/osx/Toolbar.png | Bin 6679 -> 8670 bytes browser/themes/osx/browser.css | 82 ++- browser/themes/windows/Toolbar-inverted.png | Bin 4824 -> 4653 bytes browser/themes/windows/Toolbar.png | Bin 11288 -> 9704 bytes browser/themes/windows/browser-aero.css | 5 +- browser/themes/windows/browser.css | 43 +- browser/themes/windows/jar.mn | 2 - .../themes/windows/places/editBookmark.png | Bin 1642 -> 0 bytes 25 files changed, 541 insertions(+), 499 deletions(-) delete mode 100644 browser/themes/linux/places/pageStarred.png create mode 100644 browser/themes/linux/places/star-icons.png delete mode 100644 browser/themes/linux/places/starPage.png delete mode 100644 browser/themes/windows/places/editBookmark.png diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index 9371534ce269..6420a5c0263b 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -2,6 +2,8 @@ # 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/. +//////////////////////////////////////////////////////////////////////////////// +//// StarUI var StarUI = { _itemId: -1, @@ -218,10 +220,6 @@ var StarUI = { gEditItemOverlay.uninitPanel(true); }, - editButtonCommand: function SU_editButtonCommand() { - this.showEditBookmarkPopup(); - }, - cancelButtonOnCommand: function SU_cancelButtonOnCommand() { this._actionOnHide = "cancel"; this.panel.hidePopup(); @@ -241,6 +239,9 @@ var StarUI = { } } +//////////////////////////////////////////////////////////////////////////////// +//// PlacesCommandHook + var PlacesCommandHook = { /** * Adds a bookmark to the page loaded in the given browser. @@ -293,28 +294,37 @@ var PlacesCommandHook = { PlacesUtils.bookmarks.DEFAULT_INDEX, title, null, [descAnno]); PlacesUtils.transactionManager.doTransaction(txn); + itemId = txn.item.id; // Set the character-set if (charset && !PrivateBrowsingUtils.isWindowPrivate(aBrowser.contentWindow)) PlacesUtils.setCharsetForURI(uri, charset); - itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); } // Revert the contents of the location bar if (gURLBar) gURLBar.handleRevert(); - // dock the panel to the star icon when possible, otherwise dock - // it to the content area - if (aBrowser.contentWindow == window.content) { - var starIcon = aBrowser.ownerDocument.getElementById("star-button"); - if (starIcon && isElementVisible(starIcon)) { - if (aShowEditUI) - StarUI.showEditBookmarkPopup(itemId, starIcon, "bottomcenter topright"); - return; - } + // If it was not requested to open directly in "edit" mode, we are done. + if (!aShowEditUI) + return; + + // Try to dock the panel to: + // 1. the bookmarks menu button + // 2. the page-proxy-favicon + // 3. the content area + if (BookmarksMenuButton.anchor) { + StarUI.showEditBookmarkPopup(itemId, BookmarksMenuButton.anchor, + "bottomcenter topright"); + return; } - StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap"); + let pageProxyFavicon = document.getElementById("page-proxy-favicon"); + if (isElementVisible(pageProxyFavicon)) { + StarUI.showEditBookmarkPopup(itemId, pageProxyFavicon, + "bottomcenter topright"); + } else { + StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap"); + } }, /** @@ -460,6 +470,9 @@ var PlacesCommandHook = { } }; +//////////////////////////////////////////////////////////////////////////////// +//// HistoryMenu + // View for the history menu. function HistoryMenu(aPopupShowingEvent) { // Workaround for Bug 610187. The sidebar does not include all the Places @@ -686,6 +699,9 @@ HistoryMenu.prototype = { } }; +//////////////////////////////////////////////////////////////////////////////// +//// BookmarksEventHandler + /** * Functions for handling events in the Bookmarks Toolbar and menu. */ @@ -811,6 +827,8 @@ var BookmarksEventHandler = { } }; +//////////////////////////////////////////////////////////////////////////////// +//// PlacesMenuDNDHandler // Handles special drag and drop functionality for Places menus that are not // part of a Places view (e.g. the bookmarks menu in the menubar). @@ -829,26 +847,37 @@ var PlacesMenuDNDHandler = { if (!this._isStaticContainer(event.target)) return; + let popup = event.target.lastChild; + if (this._loadTimer || popup.state === "showing" || popup.state === "open") + return; + this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - this._loadTimer.initWithCallback(function() { - PlacesMenuDNDHandler._loadTimer = null; - event.target.lastChild.setAttribute("autoopened", "true"); - event.target.lastChild.showPopup(event.target.lastChild); + this._loadTimer.initWithCallback(() => { + this._loadTimer = null; + popup.setAttribute("autoopened", "true"); + popup.showPopup(popup); }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT); event.preventDefault(); event.stopPropagation(); }, /** - * Handles dragexit on the element. + * Handles dragleave on the element. * @returns true if the element is a container element (menu or * menu-toolbarbutton), false otherwise. */ - onDragExit: function PMDH_onDragExit(event) { + onDragLeave: function PMDH_onDragLeave(event) { + // Handle menu-button separate targets. + if (event.relatedTarget === event.currentTarget || + event.relatedTarget.parentNode === event.currentTarget) + return; + // Closing menus in a Places popup is handled by the view itself. if (!this._isStaticContainer(event.target)) return; + let popup = event.target.lastChild; + if (this._loadTimer) { this._loadTimer.cancel(); this._loadTimer = null; @@ -862,10 +891,9 @@ var PlacesMenuDNDHandler = { inHierarchy = node == event.target; node = node.parentNode; } - if (!inHierarchy && event.target.lastChild && - event.target.lastChild.hasAttribute("autoopened")) { - event.target.lastChild.removeAttribute("autoopened"); - event.target.lastChild.hidePopup(); + if (!inHierarchy && popup && popup.hasAttribute("autoopened")) { + popup.removeAttribute("autoopened"); + popup.hidePopup(); } }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT); }, @@ -878,7 +906,8 @@ var PlacesMenuDNDHandler = { _isStaticContainer: function PMDH__isContainer(node) { let isMenu = node.localName == "menu" || (node.localName == "toolbarbutton" && - node.getAttribute("type") == "menu"); + (node.getAttribute("type") == "menu" || + node.getAttribute("type") == "menu-button")); let isStatic = !("_placesNode" in node) && node.lastChild && node.lastChild.hasAttribute("placespopup") && !node.parentNode.hasAttribute("placespopup"); @@ -915,179 +944,13 @@ var PlacesMenuDNDHandler = { } }; +//////////////////////////////////////////////////////////////////////////////// +//// PlacesToolbarHelper -var PlacesStarButton = { - _hasBookmarksObserver: false, - uninit: function PSB_uninit() - { - if (this._hasBookmarksObserver) { - PlacesUtils.removeLazyBookmarkObserver(this); - } - if (this._pendingStmt) { - this._pendingStmt.cancel(); - delete this._pendingStmt; - } - }, - - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsINavBookmarkObserver - ]), - - get _starredTooltip() - { - delete this._starredTooltip; - return this._starredTooltip = - gNavigatorBundle.getString("starButtonOn.tooltip"); - }, - get _unstarredTooltip() - { - delete this._unstarredTooltip; - return this._unstarredTooltip = - gNavigatorBundle.getString("starButtonOff.tooltip"); - }, - - updateState: function PSB_updateState() - { - this._starIcon = document.getElementById("star-button"); - if (!this._starIcon || (this._uri && gBrowser.currentURI.equals(this._uri))) { - return; - } - - // Reset tracked values. - this._uri = gBrowser.currentURI; - this._itemIds = []; - - if (this._pendingStmt) { - this._pendingStmt.cancel(); - delete this._pendingStmt; - } - - // We can load about:blank before the actual page, but there is no point in handling that page. - if (isBlankPageURL(this._uri.spec)) { - return; - } - - this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, function (aItemIds, aURI) { - // Safety check that the bookmarked URI equals the tracked one. - if (!aURI.equals(this._uri)) { - Components.utils.reportError("PlacesStarButton did not receive current URI"); - return; - } - - // It's possible that onItemAdded gets called before the async statement - // calls back. For such an edge case, retain all unique entries from both - // arrays. - this._itemIds = this._itemIds.filter( - function (id) aItemIds.indexOf(id) == -1 - ).concat(aItemIds); - this._updateStateInternal(); - - // Start observing bookmarks if needed. - if (!this._hasBookmarksObserver) { - try { - PlacesUtils.addLazyBookmarkObserver(this); - this._hasBookmarksObserver = true; - } catch(ex) { - Components.utils.reportError("PlacesStarButton failed adding a bookmarks observer: " + ex); - } - } - - delete this._pendingStmt; - }, this); - }, - - _updateStateInternal: function PSB__updateStateInternal() - { - if (!this._starIcon) { - return; - } - - if (this._itemIds.length > 0) { - this._starIcon.setAttribute("starred", "true"); - this._starIcon.setAttribute("tooltiptext", this._starredTooltip); - } - else { - this._starIcon.removeAttribute("starred"); - this._starIcon.setAttribute("tooltiptext", this._unstarredTooltip); - } - }, - - onClick: function PSB_onClick(aEvent) - { - // Ignore clicks on the star while we update its state. - if (aEvent.button == 0 && !this._pendingStmt) { - PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0); - } - // Don't bubble to the textbox, to avoid unwanted selection of the address. - aEvent.stopPropagation(); - }, - - // nsINavBookmarkObserver - onItemAdded: - function PSB_onItemAdded(aItemId, aFolder, aIndex, aItemType, aURI) - { - if (!this._starIcon) { - return; - } - - if (aURI && aURI.equals(this._uri)) { - // If a new bookmark has been added to the tracked uri, register it. - if (this._itemIds.indexOf(aItemId) == -1) { - this._itemIds.push(aItemId); - this._updateStateInternal(); - } - } - }, - - onItemRemoved: - function PSB_onItemRemoved(aItemId, aFolder, aIndex, aItemType) - { - if (!this._starIcon) { - return; - } - - let index = this._itemIds.indexOf(aItemId); - // If one of the tracked bookmarks has been removed, unregister it. - if (index != -1) { - this._itemIds.splice(index, 1); - this._updateStateInternal(); - } - }, - - onItemChanged: - function PSB_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, - aNewValue, aLastModified, aItemType) - { - if (!this._starIcon) { - return; - } - - if (aProperty == "uri") { - let index = this._itemIds.indexOf(aItemId); - // If the changed bookmark was tracked, check if it is now pointing to - // a different uri and unregister it. - if (index != -1 && aNewValue != this._uri.spec) { - this._itemIds.splice(index, 1); - this._updateStateInternal(); - } - // If another bookmark is now pointing to the tracked uri, register it. - else if (index == -1 && aNewValue == this._uri.spec) { - this._itemIds.push(aItemId); - this._updateStateInternal(); - } - } - }, - - onBeginUpdateBatch: function () {}, - onEndUpdateBatch: function () {}, - onItemVisited: function () {}, - onItemMoved: function () {} -}; - - -// This object handles the initialization and uninitialization of the bookmarks -// toolbar. updateState is called when the browser window is opened and -// after closing the toolbar customization dialog. +/** + * This object handles the initialization and uninitialization of the bookmarks + * toolbar. + */ let PlacesToolbarHelper = { _place: "place:folder=TOOLBAR", @@ -1127,58 +990,95 @@ let PlacesToolbarHelper = { } }; +//////////////////////////////////////////////////////////////////////////////// +//// BookmarksMenuButton -// Handles the bookmarks menu button shown when the main menubar is hidden. +/** + * Handles the bookmarks menu-button in the toolbar. + */ let BookmarksMenuButton = { get button() { - return document.getElementById("bookmarks-menu-button"); + if (!this._button) { + this._button = document.getElementById("bookmarks-menu-button"); + } + return this._button; }, - get buttonContainer() { - return document.getElementById("bookmarks-menu-button-container"); + get star() { + if (!this._star && this.button) { + this._star = document.getAnonymousElementByAttribute(this.button, + "anonid", + "button"); + } + return this._star; }, - get personalToolbar() { - delete this.personalToolbar; - return this.personalToolbar = document.getElementById("PersonalToolbar"); + get anchor() { + if (!this._anchor && this.star && isElementVisible(this.star)) { + // Anchor to the icon, so the panel looks more natural. + this._anchor = document.getAnonymousElementByAttribute(this.star, + "class", + "toolbarbutton-icon"); + } + return this._anchor; }, - get bookmarksToolbarItem() { - return document.getElementById("personal-bookmarks"); + STATUS_UPDATING: -1, + STATUS_UNSTARRED: 0, + STATUS_STARRED: 1, + get status() { + if (this._pendingStmt) + return this.STATUS_UPDATING; + return this.button && + this.button.hasAttribute("starred") ? this.STATUS_STARRED + : this.STATUS_UNSTARRED; }, - init: function BMB_init() { - this.updatePosition(); - - // Any other stuff that does not regard the button itself should be - // handled in the onPopupShowing handler, so it does not hit Ts. + get _starredTooltip() + { + delete this._starredTooltip; + return this._starredTooltip = + gNavigatorBundle.getString("starButtonOn.tooltip"); + }, + + get _unstarredTooltip() + { + delete this._unstarredTooltip; + return this._unstarredTooltip = + gNavigatorBundle.getString("starButtonOff.tooltip"); + }, + + /** + * The popup contents must be updated when the user customizes the UI, or + * changes the personal toolbar collapsed status. In such a case, any needed + * change should be handled in the popupshowing helper, for performance + * reasons. + */ + _popupNeedsUpdate: true, + onToolbarVisibilityChange: function BMB_onToolbarVisibilityChange() { + this._popupNeedsUpdate = true; }, - _popupNeedsUpdate: {}, onPopupShowing: function BMB_onPopupShowing(event) { // Don't handle events for submenus. if (event.target != event.currentTarget) return; - let popup = event.target; - let needsUpdate = this._popupNeedsUpdate[popup.id]; - - // Check if popup contents need to be updated. Note that if needsUpdate is - // undefined we have never seen the popup, thus it should be updated. - if (needsUpdate === false) + if (!this._popupNeedsUpdate) return; - this._popupNeedsUpdate[popup.id] = false; + this._popupNeedsUpdate = false; - function getPlacesAnonymousElement(aAnonId) - document.getAnonymousElementByAttribute(popup.parentNode, - "placesanonid", - aAnonId); + let popup = event.target; + let getPlacesAnonymousElement = + aAnonId => document.getAnonymousElementByAttribute(popup.parentNode, + "placesanonid", + aAnonId); let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar"); if (viewToolbarMenuitem) { // Update View bookmarks toolbar checkbox menuitem. - viewToolbarMenuitem.setAttribute("checked", - !this.personalToolbar.collapsed); + let personalToolbar = document.getElementById("PersonalToolbar"); + viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed); } let toolbarMenuitem = getPlacesAnonymousElement("toolbar-autohide"); @@ -1186,68 +1086,44 @@ let BookmarksMenuButton = { // If bookmarks items are visible, hide Bookmarks Toolbar menu and the // separator after it. toolbarMenuitem.collapsed = toolbarMenuitem.nextSibling.collapsed = - isElementVisible(this.bookmarksToolbarItem); + isElementVisible(document.getElementById("personal-bookmarks")); } }, - updatePosition: function BMB_updatePosition() { - // Popups will have to be updated when the user customizes the UI, or - // changes personal toolbar collapsed status. Both of those location call - // updatePosition(), so this is the only point asking for popup updates. - for (let popupId in this._popupNeedsUpdate) { - this._popupNeedsUpdate[popupId] = true; + /** + * Handles star styling based on page proxy state changes. + */ + onPageProxyStateChanged: function BMB_onPageProxyStateChanged(aState) { + if (!this.star) { + return; } - let button = this.button; - if (!button) - return; - - // If the toolbar containing bookmarks is visible, we want to move the - // button to bookmarksToolbarItem. - let bookmarksToolbarItem = this.bookmarksToolbarItem; - let bookmarksOnVisibleToolbar = bookmarksToolbarItem && - !bookmarksToolbarItem.parentNode.collapsed && - bookmarksToolbarItem.parentNode.getAttribute("autohide") != "true"; - - // If the container has been moved by the user to the toolbar containing - // bookmarks, we want to preserve the desired position. - let container = this.buttonContainer; - let containerNearBookmarks = container && bookmarksToolbarItem && - container.parentNode == bookmarksToolbarItem.parentNode; - - if (bookmarksOnVisibleToolbar && !containerNearBookmarks) { - if (button.parentNode != bookmarksToolbarItem) { - this._uninitView(); - bookmarksToolbarItem.appendChild(button); - } + if (aState == "invalid") { + this.star.setAttribute("disabled", "true"); + this.button.removeAttribute("starred"); } else { - if (container && button.parentNode != container) { - this._uninitView(); - container.appendChild(button); - } + this.star.removeAttribute("disabled"); } this._updateStyle(); }, _updateStyle: function BMB__updateStyle() { - let button = this.button; - if (!button) + if (!this.star) { return; + } - let container = this.buttonContainer; - let containerOnPersonalToolbar = container && - (container.parentNode == this.personalToolbar || - container.parentNode.parentNode == this.personalToolbar); + let personalToolbar = document.getElementById("PersonalToolbar"); + let onPersonalToolbar = this.button.parentNode == personalToolbar || + this.button.parentNode.parentNode == personalToolbar; - if (button.parentNode == this.bookmarksToolbarItem || - containerOnPersonalToolbar) { - button.classList.add("bookmark-item"); - button.classList.remove("toolbarbutton-1"); + if (onPersonalToolbar) { + this.button.classList.add("bookmark-item"); + this.button.classList.remove("toolbarbutton-1"); } else { - button.classList.remove("bookmark-item"); - button.classList.add("toolbarbutton-1"); + this.button.classList.remove("bookmark-item"); + this.button.classList.add("toolbarbutton-1"); } }, @@ -1255,20 +1131,13 @@ let BookmarksMenuButton = { // When an element with a placesView attached is removed and re-inserted, // XBL reapplies the binding causing any kind of issues and possible leaks, // so kill current view and let popupshowing generate a new one. - let button = this.button; - if (button && button._placesView) - button._placesView.uninit(); + if (this.button && this.button._placesView) { + this.button._placesView.uninit(); + } }, customizeStart: function BMB_customizeStart() { this._uninitView(); - let button = this.button; - let container = this.buttonContainer; - if (button && container && button.parentNode != container) { - // Move button back to the container, so user can move or remove it. - container.appendChild(button); - this._updateStyle(); - } }, customizeChange: function BMB_customizeChange() { @@ -1276,6 +1145,159 @@ let BookmarksMenuButton = { }, customizeDone: function BMB_customizeDone() { - this.updatePosition(); - } + delete this._button; + delete this._star; + delete this._anchor; + this.onToolbarVisibilityChange(); + this._updateStyle(); + }, + + _hasBookmarksObserver: false, + uninit: function BMB_uninit() { + this._uninitView(); + + if (this._hasBookmarksObserver) { + PlacesUtils.removeLazyBookmarkObserver(this); + } + + if (this._pendingStmt) { + this._pendingStmt.cancel(); + delete this._pendingStmt; + } + }, + + updateStarState: function BMB_updateStarState() { + if (!this.button || (this._uri && gBrowser.currentURI.equals(this._uri))) { + return; + } + + // Reset tracked values. + this._uri = gBrowser.currentURI; + this._itemIds = []; + + if (this._pendingStmt) { + this._pendingStmt.cancel(); + delete this._pendingStmt; + } + + // We can load about:blank before the actual page, but there is no point in handling that page. + if (isBlankPageURL(this._uri.spec)) { + return; + } + + this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, function (aItemIds, aURI) { + // Safety check that the bookmarked URI equals the tracked one. + if (!aURI.equals(this._uri)) { + Components.utils.reportError("BookmarksMenuButton did not receive current URI"); + return; + } + + // It's possible that onItemAdded gets called before the async statement + // calls back. For such an edge case, retain all unique entries from both + // arrays. + this._itemIds = this._itemIds.filter( + function (id) aItemIds.indexOf(id) == -1 + ).concat(aItemIds); + + this._updateStar(); + + // Start observing bookmarks if needed. + if (!this._hasBookmarksObserver) { + try { + PlacesUtils.addLazyBookmarkObserver(this); + this._hasBookmarksObserver = true; + } catch(ex) { + Components.utils.reportError("BookmarksMenuButton failed adding a bookmarks observer: " + ex); + } + } + + delete this._pendingStmt; + }, this); + }, + + _updateStar: function BMB__updateStar() { + if (!this.button) { + return; + } + + if (this._itemIds.length > 0) { + this.button.setAttribute("starred", "true"); + this.button.setAttribute("tooltiptext", this._starredTooltip); + } + else { + this.button.removeAttribute("starred"); + this.button.setAttribute("tooltiptext", this._unstarredTooltip); + } + }, + + onCommand: function BMB_onCommand(aEvent) { + if (aEvent.target != aEvent.currentTarget) { + return; + } + // Ignore clicks on the star if we are updating its state. + if (!this._pendingStmt) { + PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0); + } + }, + + // nsINavBookmarkObserver + onItemAdded: function BMB_onItemAdded(aItemId, aParentId, aIndex, aItemType, + aURI) { + if (!this.button) { + return; + } + + if (aURI && aURI.equals(this._uri)) { + // If a new bookmark has been added to the tracked uri, register it. + if (this._itemIds.indexOf(aItemId) == -1) { + this._itemIds.push(aItemId); + this._updateStar(); + } + } + }, + + onItemRemoved: function BMB_onItemRemoved(aItemId) { + if (!this.button) { + return; + } + + let index = this._itemIds.indexOf(aItemId); + // If one of the tracked bookmarks has been removed, unregister it. + if (index != -1) { + this._itemIds.splice(index, 1); + this._updateStar(); + } + }, + + onItemChanged: function BMB_onItemChanged(aItemId, aProperty, + aIsAnnotationProperty, aNewValue) { + if (!this.button) { + return; + } + + if (aProperty == "uri") { + let index = this._itemIds.indexOf(aItemId); + // If the changed bookmark was tracked, check if it is now pointing to + // a different uri and unregister it. + if (index != -1 && aNewValue != this._uri.spec) { + this._itemIds.splice(index, 1); + this._updateStar(); + } + // If another bookmark is now pointing to the tracked uri, register it. + else if (index == -1 && aNewValue == this._uri.spec) { + this._itemIds.push(aItemId); + this._updateStar(); + } + } + }, + + onBeginUpdateBatch: function () {}, + onEndUpdateBatch: function () {}, + onBeforeItemRemoved: function () {}, + onItemVisited: function () {}, + onItemMoved: function () {}, + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavBookmarkObserver + ]), }; diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index a2d7b6b1d778..de211c88c284 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -304,19 +304,20 @@ toolbarbutton.bookmark-item { max-width: 13em; } -%ifdef MENUBAR_CAN_AUTOHIDE -#toolbar-menubar:not([autohide="true"]) ~ #nav-bar > #bookmarks-menu-button-container, -#toolbar-menubar:not([autohide="true"]) ~ toolbar > #personal-bookmarks > #bookmarks-menu-button, -#toolbar-menubar:not([autohide="true"]) > #personal-bookmarks > #bookmarks-menu-button { - display: none; -} -%endif - #editBMPanel_tagsSelector { /* override default listbox width from xul.css */ width: auto; } +/* The star doesn't make sense as text */ +toolbar[mode="text"] #bookmarks-menu-button > .toolbarbutton-menubutton-button > .toolbarbutton-icon { + display: -moz-box !important; +} +toolbar[mode="text"] #bookmarks-menu-button > .toolbarbutton-menubutton-button > .toolbarbutton-text, +toolbar[mode="full"] #bookmarks-menu-button.bookmark-item > .toolbarbutton-menubutton-button > .toolbarbutton-text { + display: none; +} + menupopup[emptyplacesresult="true"] > .hide-if-empty-places-result { display: none; } diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 158495347ee9..8d00f27ec943 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -946,7 +946,6 @@ var gBrowserInit = { // Misc. inits. CombinedStopReload.init(); TabsOnTop.init(); - BookmarksMenuButton.init(); gPrivateBrowsingUI.init(); TabsInTitlebar.init(); retrieveToolbarIconsizesFromTheme(); @@ -1317,7 +1316,7 @@ var gBrowserInit = { } catch (ex) { } - PlacesStarButton.uninit(); + BookmarksMenuButton.uninit(); TabsOnTop.uninit(); @@ -2235,6 +2234,8 @@ function UpdatePageProxyState() function SetPageProxyState(aState) { + BookmarksMenuButton.onPageProxyStateChanged(aState); + if (!gURLBar) return; @@ -3406,7 +3407,7 @@ function BrowserToolboxCustomizeDone(aToolboxChanged) { if (gURLBar) { URLBarSetURI(); XULBrowserWindow.asyncUpdateUI(); - PlacesStarButton.updateState(); + BookmarksMenuButton.updateStarState(); SocialShareButton.updateShareState(); } @@ -3880,7 +3881,7 @@ var XULBrowserWindow = { URLBarSetURI(aLocationURI); // Update starring UI - PlacesStarButton.updateState(); + BookmarksMenuButton.updateStarState(); SocialShareButton.updateShareState(); } @@ -4498,7 +4499,7 @@ function setToolbarVisibility(toolbar, isVisible) { document.persist(toolbar.id, hidingAttribute); PlacesToolbarHelper.init(); - BookmarksMenuButton.updatePosition(); + BookmarksMenuButton.onToolbarVisibilityChange(); gBrowser.updateWindowResizers(); #ifdef MENUBAR_CAN_AUTOHIDE diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 47e32ac80d6f..3e25e649f04f 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -523,7 +523,7 @@ toolbarname="&navbarCmd.label;" accesskey="&navbarCmd.accesskey;" fullscreentoolbar="true" mode="icons" customizable="true" iconsize="large" - defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,webrtc-status-button,downloads-button,home-button,bookmarks-menu-button-container,window-controls" + defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,webrtc-status-button,bookmarks-menu-button,downloads-button,home-button,window-controls" context="toolbar-context-menu"> - - - - - - - - - - - - - - - - - - - - - - - -